From a80e895b0b23d478807377360b85b4bb1d7073b7 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 18 Feb 2007 04:16:24 +0000 Subject: Implemented discovery of plugin extension bundles. git-svn-id: http://svn.drobilla.net/lad/slv2@309 a436a847-0d15-0410-975c-d299462d15a1 --- src/Makefile.am | 3 +- src/plugin.c | 66 +++++++++-------------- src/plugininstance.c | 16 +++--- src/pluginlist.c | 148 ++++++++++++++++++++++++++++++++++----------------- src/port.c | 10 ++-- src/query.c | 33 +++++++++--- src/types.c | 54 +++++++++++++++++++ src/util.c | 23 +++----- src/util.h | 52 ------------------ 9 files changed, 224 insertions(+), 181 deletions(-) create mode 100644 src/types.c delete mode 100644 src/util.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 4f81d9a..5ad71df 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,4 +12,5 @@ libslv2_la_SOURCES = \ pluginlist.c \ util.c \ plugininstance.c \ - library.c + library.c \ + types.c diff --git a/src/plugin.c b/src/plugin.c index 970b82f..30e737a 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -16,6 +16,8 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _XOPEN_SOURCE 500 + #include #include #include @@ -24,7 +26,7 @@ #include #include #include -#include "util.h" +#include SLV2Plugin* @@ -34,8 +36,11 @@ slv2_plugin_duplicate(const SLV2Plugin* p) struct _Plugin* result = malloc(sizeof(struct _Plugin)); result->plugin_uri = p->plugin_uri; result->bundle_url = p->bundle_url; - result->data_url = p->data_url; - result->lib_url = p->lib_url; + result->lib_uri = p->lib_uri; + + result->data_uris = slv2_uri_list_new(); + for (int i=0; i < slv2_uri_list_size(p->data_uris); ++i) + raptor_sequence_push(result->data_uris, strdup(slv2_uri_list_get_at(p->data_uris, i))); return result; } @@ -48,41 +53,19 @@ slv2_plugin_get_uri(const SLV2Plugin* p) } -const char* -slv2_plugin_get_data_url(const SLV2Plugin* p) -{ - assert(p); - return p->data_url; -} - - -const char* -slv2_plugin_get_data_path(const SLV2Plugin* p) -{ - assert(p); - if (!strncmp((char*)p->data_url, "file://", (size_t)7)) - return (p->data_url) + 7; - else - return NULL; -} - - -const char* -slv2_plugin_get_library_url(const SLV2Plugin* p) +SLV2URIList +slv2_plugin_get_data_uris(const SLV2Plugin* p) { assert(p); - return p->lib_url; + return p->data_uris; } const char* -slv2_plugin_get_library_path(const SLV2Plugin* p) +slv2_plugin_get_library_uri(const SLV2Plugin* p) { assert(p); - if (!strncmp((char*)p->lib_url, "file://", (size_t)7)) - return (p->lib_url) + 7; - else - return NULL; + return p->lib_uri; } @@ -136,9 +119,9 @@ slv2_plugin_get_value(const SLV2Plugin* p, char* header = slv2_query_header(p); char* lang_filter = slv2_query_lang_filter("?value"); - char* query_string = strjoin( + char* query_string = slv2_strjoin( header, - "SELECT DISTINCT ?value FROM data: WHERE { \n", + "SELECT DISTINCT ?value WHERE { \n", "plugin: ", property, " ?value . \n", ((lang_filter != NULL) ? lang_filter : ""), "}", 0); @@ -146,8 +129,8 @@ slv2_plugin_get_value(const SLV2Plugin* p, free(header); free(lang_filter);*/ - char* query = strjoin( - "SELECT DISTINCT ?value FROM data: WHERE {\n" + char* query = slv2_strjoin( + "SELECT DISTINCT ?value WHERE {\n" "plugin: ", predicate, " ?value .\n" "}\n", NULL); @@ -177,9 +160,8 @@ uint32_t slv2_plugin_get_num_ports(const SLV2Plugin* p) { const char* const query = - "SELECT DISTINCT ?value FROM data: WHERE {\n" - "plugin: lv2:port ?value .\n" - "}\n"; + "SELECT DISTINCT ?value\n" + "WHERE { plugin: lv2:port ?value }\n"; SLV2Value results = slv2_plugin_simple_query(p, query, "value"); @@ -195,7 +177,7 @@ bool slv2_plugin_has_latency(const SLV2Plugin* p) { const char* const query = - "SELECT DISTINCT ?port FROM data: WHERE {\n" + "SELECT DISTINCT ?port WHERE {\n" " plugin: lv2:port ?port .\n" " ?port lv2:portHint lv2:reportsLatency .\n" "}\n"; @@ -214,7 +196,7 @@ uint32_t slv2_plugin_get_latency_port(const SLV2Plugin* p) { const char* const query = - "SELECT DISTINCT ?value FROM data: WHERE {\n" + "SELECT DISTINCT ?value WHERE {\n" " plugin: lv2:port ?port .\n" " ?port lv2:portHint lv2:reportsLatency ;\n" " lv2:index ?index .\n" @@ -236,7 +218,7 @@ SLV2Value slv2_plugin_get_supported_features(const SLV2Plugin* p) { const char* const query = - "SELECT DISTINCT ?feature FROM data: WHERE {\n" + "SELECT DISTINCT ?feature WHERE {\n" " { plugin: lv2:optionalHostFeature ?feature }\n" " UNION\n" " { plugin: lv2:requiredHostFeature ?feature }\n" @@ -252,7 +234,7 @@ SLV2Value slv2_plugin_get_optional_features(const SLV2Plugin* p) { const char* const query = - "SELECT DISTINCT ?feature FROM data: WHERE {\n" + "SELECT DISTINCT ?feature WHERE {\n" " plugin: lv2:optionalHostFeature ?feature .\n" "}\n"; @@ -266,7 +248,7 @@ SLV2Value slv2_plugin_get_required_features(const SLV2Plugin* p) { const char* const query = - "SELECT DISTINCT ?feature FROM data: WHERE {\n" + "SELECT DISTINCT ?feature WHERE {\n" " plugin: lv2:requiredHostFeature ?feature .\n" "}\n"; diff --git a/src/plugininstance.c b/src/plugininstance.c index 2c178a8..ed8abc7 100644 --- a/src/plugininstance.c +++ b/src/plugininstance.c @@ -25,7 +25,7 @@ #include #include #include -#include "util.h" +#include SLV2Instance* @@ -41,14 +41,14 @@ slv2_plugin_instantiate(const SLV2Plugin* plugin, host_features[0] = NULL; } - const char* const lib_path = slv2_plugin_get_library_path(plugin); - if (!lib_path) + const char* const lib_uri = slv2_plugin_get_library_uri(plugin); + if (!lib_uri || slv2_uri_to_path(lib_uri) == NULL) return NULL; dlerror(); - void* lib = dlopen((char*)lib_path, RTLD_NOW); + void* lib = dlopen(slv2_uri_to_path(lib_uri), RTLD_NOW); if (!lib) { - fprintf(stderr, "Unable to open library %s (%s)\n", lib_path, dlerror()); + fprintf(stderr, "Unable to open library %s (%s)\n", lib_uri, dlerror()); return NULL; } @@ -56,20 +56,20 @@ slv2_plugin_instantiate(const SLV2Plugin* plugin, if (!df) { fprintf(stderr, "Could not find symbol 'lv2_descriptor', " - "%s is not a LV2 plugin.\n", lib_path); + "%s is not a LV2 plugin.\n", lib_uri); dlclose(lib); return NULL; } else { // Search for plugin by URI - const char* const bundle_path = url2path(plugin->bundle_url); + const char* const bundle_path = slv2_uri_to_path(plugin->bundle_url); for (uint32_t i=0; 1; ++i) { const LV2_Descriptor* ld = df(i); if (!ld) { fprintf(stderr, "Did not find plugin %s in %s\n", - plugin->plugin_uri, plugin->lib_url); + plugin->plugin_uri, plugin->lib_uri); dlclose(lib); break; // return NULL } else if (!strcmp(ld->URI, (char*)plugin->plugin_uri)) { diff --git a/src/pluginlist.c b/src/pluginlist.c index 3c8e409..d9bbe7b 100644 --- a/src/pluginlist.c +++ b/src/pluginlist.c @@ -28,9 +28,24 @@ #include #include #include -#include "util.h" +#include +/* not exposed */ +struct _Plugin* +slv2_plugin_new() +{ + struct _Plugin* result = malloc(sizeof(struct _Plugin)); + result->plugin_uri = NULL; + result->bundle_url = NULL; + result->lib_uri = NULL; + + result->data_uris = slv2_uri_list_new(); + + return result; +} + + struct _PluginList* slv2_list_new() { @@ -62,17 +77,24 @@ slv2_list_load_all(SLV2List list) } else { const char* const home = getenv("HOME"); const char* const suffix = "/.lv2:/usr/local/lib/lv2:usr/lib/lv2"; - slv2_path = strjoin(home, suffix, NULL); + slv2_path = slv2_strjoin(home, suffix, NULL); fprintf(stderr, "$LV2_PATH is unset. Using default path %s\n", slv2_path); - slv2_list_load_path(list, slv2_path); + + /* pass 1: find all plugins */ + slv2_list_load_path(list, slv2_path); + + /* pass 2: find all data files for plugins */ + slv2_list_load_path(list, slv2_path); free(slv2_path); } } -/* This is the parser for manifest.ttl */ +/* This is the parser for manifest.ttl + * This is called twice on each bundle in the discovery process, which is (much) less + * efficient than it could be.... */ void slv2_list_load_bundle(SLV2List list, const char* bundle_base_url) @@ -89,14 +111,11 @@ slv2_list_load_bundle(SLV2List list, raptor_uri *base_url = raptor_new_uri(manifest_url); rasqal_query *rq = rasqal_new_query("sparql", NULL); + /* Get all plugins explicitly mentioned in the manifest (discovery pass 1) */ char* query_string = - "PREFIX rdfs: \n" - "PREFIX : \n\n" - - "SELECT DISTINCT $plugin_uri $data_url $lib_url FROM <> WHERE { \n" - "$plugin_uri :binary $lib_url . \n" - "OPTIONAL { $plugin_uri rdfs:seeAlso $data_url } \n" - "} \n"; + "PREFIX : \n\n" + "SELECT DISTINCT ?plugin_uri FROM <>\n" + "WHERE { ?plugin_uri a :Plugin }\n"; //printf("%s\n\n", query_string); @@ -104,48 +123,80 @@ slv2_list_load_bundle(SLV2List list, results = rasqal_query_execute(rq); while (!rasqal_query_results_finished(results)) { - - // Create a new plugin - struct _Plugin* new_plugin = malloc(sizeof(struct _Plugin)); - new_plugin->bundle_url = strdup(bundle_base_url); - rasqal_literal* literal = NULL; - - literal = rasqal_query_results_get_binding_value_by_name(results, (const unsigned char*)"plugin_uri"); + rasqal_literal* literal = rasqal_query_results_get_binding_value(results, 0); assert(literal); - new_plugin->plugin_uri = strdup((const char*)rasqal_literal_as_string(literal)); - - literal = rasqal_query_results_get_binding_value_by_name(results, (const unsigned char*)"lib_url"); - assert(literal); - new_plugin->lib_url = strdup((const char*)rasqal_literal_as_string(literal)); - - literal = rasqal_query_results_get_binding_value_by_name(results, (const unsigned char*)"data_url"); - if (literal) - new_plugin->data_url = strdup((const char*)rasqal_literal_as_string(literal)); - else - new_plugin->data_url = strdup((const char*)manifest_url); - - /* Add the plugin if it's valid */ - if (new_plugin->lib_url && new_plugin->data_url && new_plugin->plugin_uri - && slv2_plugin_verify(new_plugin)) { - /* Yes, this is disgusting, but it doesn't seem there's a way to know - * how many matches there are before iterating over them */ + + if (!slv2_list_get_plugin_by_uri(list, (const char*)rasqal_literal_as_string(literal))) { + /* Create a new plugin */ + struct _Plugin* new_plugin = slv2_plugin_new(); + new_plugin->plugin_uri = strdup((const char*)rasqal_literal_as_string(literal)); + new_plugin->bundle_url = strdup(bundle_base_url); + raptor_sequence_push(new_plugin->data_uris, strdup((const char*)manifest_url)); + + /* And add it to the list + * Yes, this is disgusting, but it doesn't seem there's a way to know + * how many matches there are before iterating over them.. */ list->num_plugins++; list->plugins = realloc(list->plugins, list->num_plugins * sizeof(struct _Plugin*)); list->plugins[list->num_plugins-1] = new_plugin; + } + + rasqal_query_results_next(results); + } + + if (results) + rasqal_free_query_results(results); + + rasqal_free_query(rq); + + rq = rasqal_new_query("sparql", NULL); + + /* Get all data files linked to plugins (discovery pass 2) */ + query_string = + "PREFIX rdfs: \n" + "PREFIX : \n\n" + "SELECT DISTINCT ?subject ?data_uri ?binary FROM <>\n" + "WHERE { ?subject rdfs:seeAlso ?data_uri\n" + "OPTIONAL { ?subject :binary ?binary } }\n"; + + //printf("%s\n\n", query_string); + + rasqal_query_prepare(rq, (unsigned char*)query_string, base_url); + results = rasqal_query_execute(rq); + + while (!rasqal_query_results_finished(results)) { + const char* subject = (const char*)rasqal_literal_as_string( + rasqal_query_results_get_binding_value(results, 0)); + + const char* data_uri = (const char*)rasqal_literal_as_string( + rasqal_query_results_get_binding_value(results, 1)); + + const char* binary = (const char*)rasqal_literal_as_string( + rasqal_query_results_get_binding_value(results, 2)); + + struct _Plugin* plugin = slv2_list_get_plugin_by_uri(list, subject); + + if (plugin && data_uri && !slv2_uri_list_contains(plugin->data_uris, data_uri)) + raptor_sequence_push(plugin->data_uris, strdup(data_uri)); + + if (plugin && binary && !plugin->lib_uri) + plugin->lib_uri = strdup(binary); + rasqal_query_results_next(results); + } - // FIXME: leaks? rasqal really doesn't handle missing files well.. - if (results) { + if (results) rasqal_free_query_results(results); - //rasqal_free_query(rq); // FIXME: crashes? leak? - raptor_free_uri(base_url); - } + + rasqal_free_query(rq); + + raptor_free_uri(base_url); free(manifest_url); } @@ -154,7 +205,7 @@ slv2_list_load_bundle(SLV2List list, * (Private helper function, not exposed in public API) */ void -add_plugins_from_dir(SLV2List list, const char* dir) +slv2_list_load_dir(SLV2List list, const char* dir) { DIR* pdir = opendir(dir); if (!pdir) @@ -165,8 +216,8 @@ add_plugins_from_dir(SLV2List list, const char* dir) if (!strcmp(pfile->d_name, ".") || !strcmp(pfile->d_name, "..")) continue; - char* bundle_path = (char*)strjoin(dir, "/", pfile->d_name, NULL); - char* bundle_url = (char*)strjoin("file://", dir, "/", pfile->d_name, NULL); + char* bundle_path = slv2_strjoin(dir, "/", pfile->d_name, NULL); + char* bundle_url = slv2_strjoin("file://", dir, "/", pfile->d_name, NULL); DIR* bundle_dir = opendir(bundle_path); if (bundle_dir != NULL) { @@ -184,21 +235,20 @@ add_plugins_from_dir(SLV2List list, const char* dir) void -slv2_list_load_path(SLV2List list, - const char* slv2_path) +slv2_list_load_path(SLV2List list, + const char* lv2_path) { - - char* path = (char*)strjoin(slv2_path, ":", NULL); + char* path = slv2_strjoin(lv2_path, ":", NULL); char* dir = path; // Pointer into path // Go through string replacing ':' with '\0', using the substring, - // then replacing it with 'X' and moving on. eg strtok on crack. + // then replacing it with 'X' and moving on. i.e. strtok on crack. while (strchr(path, ':') != NULL) { char* delim = strchr(path, ':'); *delim = '\0'; - add_plugins_from_dir(list, dir); + slv2_list_load_dir(list, dir); *delim = 'X'; dir = delim + 1; diff --git a/src/port.c b/src/port.c index a7691ea..b92e12b 100644 --- a/src/port.c +++ b/src/port.c @@ -24,7 +24,7 @@ #include #include #include -#include "util.h" +#include SLV2PortID @@ -111,8 +111,8 @@ slv2_port_get_value(SLV2Plugin* p, char index_str[16]; snprintf(index_str, (size_t)16, "%u", id.index); - char* query = strjoin( - "SELECT DISTINCT ?value FROM data: WHERE { \n" + char* query = slv2_strjoin( + "SELECT DISTINCT ?value WHERE { \n" "plugin: lv2:port ?port . \n" "?port lv2:index ", index_str, " ;\n\t", property, " ?value . \n}\n", NULL); @@ -122,8 +122,8 @@ slv2_port_get_value(SLV2Plugin* p, free(query); } else { - char* query = strjoin( - "SELECT DISTINCT ?value FROM data: WHERE { \n" + char* query = slv2_strjoin( + "SELECT DISTINCT ?value WHERE { \n" "plugin: lv2:port ?port . \n" "?port lv2:symbol ", id.symbol, " ;\n\t", property, " ?value . \n}\n", NULL); diff --git a/src/query.c b/src/query.c index 6149531..0d44533 100644 --- a/src/query.c +++ b/src/query.c @@ -16,27 +16,36 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include +#define _XOPEN_SOURCE 500 #include #include #include #include #include +#include char* slv2_query_header(const SLV2Plugin* p) { const char* const plugin_uri = slv2_plugin_get_uri(p); - const char* const data_file_url = slv2_plugin_get_data_url(p); + //SLV2URIList files = slv2_plugin_get_data_uris(p); - char* query_string = strjoin( + char* query_string = slv2_strjoin( "PREFIX rdf: \n" "PREFIX rdfs: \n" "PREFIX doap: \n" "PREFIX lv2: \n" - "PREFIX plugin: <", plugin_uri, ">\n", - "PREFIX data: <", data_file_url, ">\n\n", NULL); + "PREFIX plugin: <", plugin_uri, ">\n", NULL); + + /*for (int i=0; i < slv2_uri_list_size(files); ++i) { + const char* file_uri = slv2_uri_list_get_at(files, i); + slv2_strappend(&query_string, "PREFIX data: <"); + slv2_strappend(&query_string, file_uri); + slv2_strappend(&query_string, ">\n"); + }*/ + + slv2_strappend(&query_string, "\n"); return query_string; } @@ -49,7 +58,7 @@ slv2_query_lang_filter(const char* variable) char* const lang = (char*)getenv("LANG"); if (lang) { // FILTER( LANG(?value) = "en" || LANG(?value) = "" ) - result = strjoin( + result = slv2_strjoin( //"FILTER (lang(?value) = \"", lang, "\"\n"), 0); "FILTER( lang(?", variable, ") = \"", lang, "\" || lang(?", variable, ") = \"\" )\n", NULL); @@ -114,7 +123,7 @@ slv2_plugin_query(SLV2Plugin* plugin, rasqal_query *rq = rasqal_new_query("sparql", NULL); char* header = slv2_query_header(plugin); - char* query_str = strjoin(header, sparql_str, NULL); + char* query_str = slv2_strjoin(header, sparql_str, NULL); //printf("Query: \n%s\n\n", query_str); @@ -123,6 +132,14 @@ slv2_plugin_query(SLV2Plugin* plugin, // Add LV2 ontology to query sources rasqal_query_add_data_graph(rq, slv2_ontology_uri, NULL, RASQAL_DATA_GRAPH_BACKGROUND); + + // Add all plugin data files to query sources + for (int i=0; i < slv2_uri_list_size(plugin->data_uris); ++i) { + const char* file_uri_str = slv2_uri_list_get_at(plugin->data_uris, i); + raptor_uri* file_uri = raptor_new_uri((const unsigned char*)file_uri_str); + rasqal_query_add_data_graph(rq, file_uri, + NULL, RASQAL_DATA_GRAPH_BACKGROUND); + } rasqal_query_results* results = rasqal_query_execute(rq); assert(results); @@ -163,7 +180,7 @@ slv2_query_count_results(const SLV2Plugin* p, const char* query) { char* header = slv2_query_header(p); - char* query_str = strjoin(header, query, NULL); + char* query_str = slv2_strjoin(header, query, NULL); assert(p); assert(query_str); diff --git a/src/types.c b/src/types.c new file mode 100644 index 0000000..76521c0 --- /dev/null +++ b/src/types.c @@ -0,0 +1,54 @@ +/* SLV2 + * Copyright (C) 2007 Dave Robillard + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include + + +SLV2URIList +slv2_uri_list_new() +{ + return raptor_new_sequence(&free, NULL); +} + + +int +slv2_uri_list_size(const SLV2URIList list) +{ + return raptor_sequence_size(list); +} + + +char* +slv2_uri_list_get_at(const SLV2URIList list, int index) +{ + return (char*)raptor_sequence_get_at(list, index); +} + + +bool +slv2_uri_list_contains(const SLV2URIList list, const char* uri) +{ + for (int i=0; i < slv2_uri_list_size(list); ++i) + if (!strcmp(slv2_uri_list_get_at(list, i), uri)) + return true; + + return false; +} diff --git a/src/util.c b/src/util.c index 135c256..35624c1 100644 --- a/src/util.c +++ b/src/util.c @@ -18,15 +18,15 @@ #define _XOPEN_SOURCE 500 -#include #include #include #include #include +#include void -strappend(char** dst, const char* suffix) +slv2_strappend(char** dst, const char* suffix) { assert(dst); assert(*dst); @@ -40,7 +40,7 @@ strappend(char** dst, const char* suffix) char* -strjoin(const char* first, ...) +slv2_strjoin(const char* first, ...) { size_t len = strlen(first); char* result = NULL; @@ -73,23 +73,14 @@ strjoin(const char* first, ...) } -/** Convert a URL to a local filesystem path (ie by chopping off the - * leading "file://". - * - * Returns NULL if URL is not a valid URL on the local filesystem. - * Result is simply a pointer in to \a url and must not be free()'d. - */ const char* -url2path(const char* const url) +slv2_uri_to_path(const char* uri) { - /*assert(strlen((char*)url) > 8); - char* result = calloc(strlen((char*)url)-7+1, sizeof(char)); - strcpy(result, (char*)url+7); - return result;*/ - if (!strncmp((char*)url, "file://", (size_t)7)) - return (char*)url + 7; + if (!strncmp(uri, "file://", (size_t)7)) + return (char*)(uri + 7); else return NULL; } + diff --git a/src/util.h b/src/util.h deleted file mode 100644 index caa025e..0000000 --- a/src/util.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SLV2 - * Copyright (C) 2007 Dave Robillard - * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef __UTIL_H -#define __UTIL_H - -#define _XOPEN_SOURCE 500 -#include - -#include -#include - - -/** Append \a suffix to \a *dst, reallocating \a dst as necessary. - * - * \a dst will (possibly) be freed, it must be dynamically allocated with malloc - * or NULL. - */ -void -strappend(char** dst, const char* suffix); - - -/** Join all arguments into one string. - * - * Arguments are not modified, return value must be free()'d. - */ -char* -strjoin(const char* first, ...); - -char* -vstrjoin(const char** first, va_list args_list); - -const char* -url2path(const char* const url); - - -#endif -- cgit v1.2.1