diff options
author | Frank Ch. Eigler <fche@redhat.com> | 2022-10-31 17:40:01 -0400 |
---|---|---|
committer | Frank Ch. Eigler <fche@redhat.com> | 2022-10-31 20:22:49 -0400 |
commit | 2f1216bd5e311c2ecfe1cbb1eaf2776d91ee14f8 (patch) | |
tree | 9c39dd752672f9a02198150967e5f26d5e157575 | |
parent | 88430aeb79e42e134db4eae43a204d11941f1df9 (diff) | |
download | elfutils-2f1216bd5e311c2ecfe1cbb1eaf2776d91ee14f8.tar.gz |
PR29472: debuginfod metadata query reworkusers/fche/try-pr29472
Generalize lookup to "file" vs "glob" modes, propagate through API.
Add server side timeout for metadata query ops.
Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
-rw-r--r-- | debuginfod/debuginfod-client.c | 46 | ||||
-rw-r--r-- | debuginfod/debuginfod-find.c | 20 | ||||
-rw-r--r-- | debuginfod/debuginfod.cxx | 200 | ||||
-rw-r--r-- | debuginfod/debuginfod.h.in | 6 | ||||
-rw-r--r-- | debuginfod/libdebuginfod.map | 1 | ||||
-rw-r--r-- | doc/debuginfod-find.1 | 40 | ||||
-rw-r--r-- | doc/debuginfod.8 | 18 | ||||
-rw-r--r-- | doc/debuginfod_find_debuginfo.3 | 24 | ||||
-rwxr-xr-x | tests/run-debuginfod-find-metadata.sh | 14 |
9 files changed, 228 insertions, 141 deletions
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c index 2be9a07e..4a75eef2 100644 --- a/debuginfod/debuginfod-client.c +++ b/debuginfod/debuginfod-client.c @@ -57,7 +57,7 @@ int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b, int debuginfod_find_source (debuginfod_client *c, const unsigned char *b, int s, const char *f, char **p) { return -ENOSYS; } int debuginfod_find_metadata (debuginfod_client *c, - const char* p, char** m) { return -ENOSYS; } + const char *k, const char* v, char** m) { return -ENOSYS; } void debuginfod_set_progressfn(debuginfod_client *c, debuginfod_progressfn_t fn) { } void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { } @@ -1335,8 +1335,7 @@ debuginfod_query_server_by_buildid (debuginfod_client *c, else snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type); - r = init_handle(c, debuginfod_write_callback, header_callback, - data+i,i, timeout, vfd); + r = init_handle(c, debuginfod_write_callback, header_callback, &data[i], i, timeout, vfd); if(0 != r){ rc = r; if(filename) curl_free (escaped_string); @@ -1738,13 +1737,15 @@ int debuginfod_find_source(debuginfod_client *client, "source", filename, path); } + int debuginfod_find_metadata (debuginfod_client *client, - const char* path, char** metadata) + const char* key, const char* value, char** metadata) { (void) client; - (void) path; - if(NULL == metadata) return EPERM; - *metadata = strdup("[ ]"); // An empty JSON array + (void) key; + (void) value; + + if (NULL == metadata) return -EINVAL; #ifdef HAVE_JSON_C char *server_urls; char *urls_envvar; @@ -1757,13 +1758,13 @@ int debuginfod_find_metadata (debuginfod_client *client, goto out; } - if(NULL == path){ - rc = -ENOSYS; + if(NULL == value || NULL == key){ + rc = -EINVAL; goto out; } if (vfd >= 0) - dprintf (vfd, "debuginfod_find_metadata %s\n", path); + dprintf (vfd, "debuginfod_find_metadata %s %s\n", key, value); /* Without query-able URL, we can stop here*/ urls_envvar = getenv(DEBUGINFOD_URLS_ENV_VAR); @@ -1843,12 +1844,21 @@ int debuginfod_find_metadata (debuginfod_client *client, data[i].metadata = NULL; data[i].metadata_size = 0; - // At the moment only glob path querying is supported, but leave room for - // future expansion - const char *key = "glob"; - snprintf(data[i].url, PATH_MAX, "%s?%s=%s", server_url, key, path); - r = init_handle(client, metadata_callback, header_callback, - data+i, i, timeout, vfd); + // libcurl > 7.62ish has curl_url_set()/etc. to construct these things more properly. + // curl_easy_escape() is older + CURL *c = curl_easy_init(); + if (c) { + char *key_escaped = curl_easy_escape(c, key, 0); + char *value_escaped = curl_easy_escape(c, value, 0); + snprintf(data[i].url, PATH_MAX, "%s?key=%s&value=%s", server_url, + // fallback to unescaped values in unlikely case of error + key_escaped ?: key, value_escaped ?: value); + curl_free(value_escaped); + curl_free(key_escaped); + curl_easy_cleanup(c); + } + + r = init_handle(client, metadata_callback, header_callback, &data[i], i, timeout, vfd); if(0 != r){ rc = r; goto out2; @@ -1898,7 +1908,6 @@ int debuginfod_find_metadata (debuginfod_client *client, free (data[i].metadata); } - free(*metadata); *metadata = strdup(json_object_to_json_string_ext(json_metadata, JSON_C_TO_STRING_PRETTY)); free (data); @@ -1934,7 +1943,8 @@ out: client->user_agent_set_p = 0; return rc; -#else + +#else /* ! HAVE_JSON_C */ return -ENOSYS; #endif } diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c index ecaf954e..4136c567 100644 --- a/debuginfod/debuginfod-find.c +++ b/debuginfod/debuginfod-find.c @@ -52,7 +52,10 @@ static const char args_doc[] = N_("debuginfo BUILDID\n" "executable PATH\n" "source BUILDID /FILENAME\n" "source PATH /FILENAME\n" - "metadata GLOB"); +#ifdef HAVE_JSON_C + "metadata KEY VALUE" +#endif + ); /* Definitions of arguments for argp functions. */ @@ -144,16 +147,17 @@ main(int argc, char** argv) return 1; } +#ifdef HAVE_JSON_C if(strcmp(argv[remaining], "metadata") == 0){ - #ifdef HAVE_JSON_C - if (remaining+1 == argc) + if (remaining+2 == argc) { - fprintf(stderr, "If FILETYPE is \"metadata\" then GLOB must be given\n"); + fprintf(stderr, "Require KEY and VALUE for \"metadata\"\n"); return 1; } char* metadata; - int rc = debuginfod_find_metadata (client, argv[remaining+1], &metadata); + int rc = debuginfod_find_metadata (client, argv[remaining+1], argv[remaining+2], + &metadata); if (rc < 0) { @@ -164,12 +168,8 @@ main(int argc, char** argv) printf("%s\n", metadata); free(metadata); return 0; - #else - fprintf(stderr, "If FILETYPE is \"metadata\" then libjson-c must be available\n"); - return 1; - #endif - } +#endif /* If we were passed an ELF file name in the BUILDID slot, look in there. */ unsigned char* build_id = (unsigned char*) argv[remaining+1]; diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx index b9cf15ed..105c3908 100644 --- a/debuginfod/debuginfod.cxx +++ b/debuginfod/debuginfod.cxx @@ -176,7 +176,7 @@ static const char DEBUGINFOD_SQLITE_DDL[] = " foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n" " primary key (buildid, file, mtime)\n" " ) " WITHOUT_ROWID ";\n" - // Index for faster delete by file identifier + // Index for faster delete by file identifier and metadata searches "create index if not exists " BUILDIDS "_f_de_idx on " BUILDIDS "_f_de (file, mtime);\n" "create table if not exists " BUILDIDS "_f_s (\n" " buildid integer not null,\n" @@ -202,6 +202,8 @@ static const char DEBUGINFOD_SQLITE_DDL[] = " ) " WITHOUT_ROWID ";\n" // Index for faster delete by archive file identifier "create index if not exists " BUILDIDS "_r_de_idx on " BUILDIDS "_r_de (file, mtime);\n" + // Index for metadata searches + "create index if not exists " BUILDIDS "_r_de_idx2 on " BUILDIDS "_r_de (content);\n" "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm " buildid integer not null,\n" " artifactsrc integer not null,\n" @@ -389,6 +391,9 @@ static const struct argp_option options[] = { "passive", ARGP_KEY_PASSIVE, NULL, 0, "Do not scan or groom, read-only database.", 0 }, #define ARGP_KEY_DISABLE_SOURCE_SCAN 0x1009 { "disable-source-scan", ARGP_KEY_DISABLE_SOURCE_SCAN, NULL, 0, "Do not scan dwarf source info.", 0 }, +#define ARGP_KEY_METADATA_MAXTIME 0x100A + { "metadata-maxtime", ARGP_KEY_METADATA_MAXTIME, "SECONDS", 0, + "Number of seconds to limit metadata query run time, 0=unlimited.", 0 }, { NULL, 0, NULL, 0, NULL, 0 }, }; @@ -441,6 +446,8 @@ static unsigned forwarded_ttl_limit = 8; static bool scan_source_info = true; static string tmpdir; static bool passive_p = false; +static unsigned metadata_maxtime_s = 5; + static void set_metric(const string& key, double value); // static void inc_metric(const string& key); @@ -642,6 +649,9 @@ parse_opt (int key, char *arg, case ARGP_KEY_DISABLE_SOURCE_SCAN: scan_source_info = false; break; + case ARGP_KEY_METADATA_MAXTIME: + metadata_maxtime_s = (unsigned) atoi(arg); + break; // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK); default: return ARGP_ERR_UNKNOWN; } @@ -2277,74 +2287,101 @@ handle_metrics (off_t* size) return r; } + +#ifdef HAVE_JSON_C static struct MHD_Response* handle_metadata (MHD_Connection* conn, - string path, off_t* size) + string key, string value, off_t* size) { MHD_Response* r; - (void) conn; - (void) path; - (void) size; - #ifdef HAVE_JSON_C sqlite3 *thisdb = dbq; - json_object *metadata = json_object_new_array(); // Query locally for matching e, d and s files - if(metadata){ - sqlite_ps *pp = new sqlite_ps (thisdb, "mhd-query-m", - "select * from \n" - "(\n" - "select \"e\" as atype, mtime, buildid, sourcetype, source0, source1, null as artifactsrc, null as source0ref " - "from " BUILDIDS "_query_e " - "union all \n" - "select \"d\" as atype, mtime, buildid, sourcetype, source0, source1, null as artifactsrc, null as source0ref " - "from " BUILDIDS "_query_d " - "union all \n" - "select \"s\" as atype, mtime, buildid, sourcetype, source0, source1, artifactsrc, source0ref " - "from " BUILDIDS "_query_s " - "\n" - ")\n" - "where source1 glob ? "); - - pp->reset(); - pp->bind(1, path); - - unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return - - // consume all the rows - int rc; - while (SQLITE_DONE != (rc = pp->step())) + + string op; + if (key == "glob") + op = "glob"; + else if (key == "file") + op = "="; + else + throw reportable_exception("/metadata webapi error, unsupported key"); + + string sql = string( + // explicit query r_de and f_de once here, rather than the query_d and query_e + // separately, because they scan the same tables, so we'd double the work + "select d1.executable_p, d1.debuginfo_p, 0 as source_p, b1.hex, f1.name as file " + "from " BUILDIDS "_r_de d1, " BUILDIDS "_files f1, " BUILDIDS "_buildids b1 " + "where f1.id = d1.content and d1.buildid = b1.id and f1.name " + op + " ? " + "union all \n" + "select d2.executable_p, d2.debuginfo_p, 0, b2.hex, f2.name " + "from " BUILDIDS "_f_de d2, " BUILDIDS "_files f2, " BUILDIDS "_buildids b2 " + "where f2.id = d2.file and d2.buildid = b2.id and f2.name " + op + " ? " + "union all \n" + // delegate to query_s for this one + "select 0, 0, 1, q.buildid, q.artifactsrc " + "from " BUILDIDS "_query_s as q " + "where q.artifactsrc " + op + " ? "); + + sqlite_ps *pp = new sqlite_ps (thisdb, "mhd-query-meta-glob", sql); + pp->reset(); + pp->bind(1, value); + pp->bind(2, value); + pp->bind(3, value); + unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return + + json_object *metadata = json_object_new_array(); + if (!metadata) + throw libc_exception(ENOMEM, "json allocation"); + + // consume all the rows + struct timespec ts_start; + clock_gettime (CLOCK_MONOTONIC, &ts_start); + + int rc; + while (SQLITE_DONE != (rc = pp->step())) { + // break out of loop if we have searched too long + struct timespec ts_end; + clock_gettime (CLOCK_MONOTONIC, &ts_end); + double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9; + if (metadata_maxtime_s > 0 && deltas > metadata_maxtime_s) + break; // NB: no particular signal is given to the client about incompleteness + if (rc != SQLITE_ROW) throw sqlite_exception(rc, "step"); - auto get_column_str = [pp](int idx) { return string((const char*) sqlite3_column_text (*pp, idx) ?: ""); }; - - string atype = get_column_str(0); - string mtime = to_string(sqlite3_column_int64 (*pp, 1)); - string buildid = get_column_str(2); - string stype = get_column_str(3); - string source0 = get_column_str(4); - string source1 = get_column_str(5); - string artifactsrc = get_column_str(6); - string source0ref = get_column_str(7); - - json_object *entry = json_object_new_object(); - auto add_entry_metadata = [entry](const char* key, string value) { if(value != "") json_object_object_add(entry, key, json_object_new_string(value.c_str())); }; - - add_entry_metadata("atype", atype); - add_entry_metadata("mtime", mtime); - add_entry_metadata("buildid", buildid); - add_entry_metadata("stype", stype); - add_entry_metadata("source0", source0); - add_entry_metadata("source1", source1); - add_entry_metadata("artifactsrc", artifactsrc); - add_entry_metadata("source0ref", source0ref); - - json_object_array_add(metadata, json_object_get(entry)); // Increase ref count to switch its ownership - json_object_put(entry); + int m_executable_p = sqlite3_column_int (*pp, 0); + int m_debuginfo_p = sqlite3_column_int (*pp, 1); + int m_source_p = sqlite3_column_int (*pp, 2); + string m_buildid = (const char*) sqlite3_column_text (*pp, 3) ?: ""; // should always be non-null + string m_file = (const char*) sqlite3_column_text (*pp, 4) ?: ""; + + auto add_metadata = [metadata, m_buildid, m_file](const string& type) { + json_object* entry = json_object_new_object(); + if (NULL == entry) throw libc_exception (ENOMEM, "cannot allocate json"); + defer_dtor<json_object*,int> entry_d(entry, json_object_put); + + auto add_entry_metadata = [entry](const char* k, string v) { + json_object* s; + if(v != "") { + s = json_object_new_string(v.c_str()); + if (NULL == s) throw libc_exception (ENOMEM, "cannot allocate json"); + json_object_object_add(entry, k, s); + } + }; + + add_entry_metadata("type", type.c_str()); + add_entry_metadata("buildid", m_buildid); + add_entry_metadata("file", m_file); + json_object_array_add(metadata, json_object_get(entry)); // Increase ref count to switch its ownership + }; + + if (m_executable_p) add_metadata("executable"); + if (m_debuginfo_p) add_metadata("debuginfo"); + if (m_source_p) add_metadata("source"); + } - pp->reset(); - } + pp->reset(); + // Query upstream as well debuginfod_client *client = debuginfod_pool_begin(); if (metadata && client != NULL) @@ -2352,15 +2389,17 @@ handle_metadata (MHD_Connection* conn, add_client_federation_headers(client, conn); char * upstream_metadata; - if(0 == debuginfod_find_metadata(client, path.c_str(), &upstream_metadata)){ + if (0 == debuginfod_find_metadata(client, key.c_str(), value.c_str(), &upstream_metadata)) { json_object *upstream_metadata_json = json_tokener_parse(upstream_metadata); if(NULL != upstream_metadata_json) - for (int i = 0, n = json_object_array_length(upstream_metadata_json); i < n; i++) { + { + for (int i = 0, n = json_object_array_length(upstream_metadata_json); i < n; i++) { json_object *entry = json_object_array_get_idx(upstream_metadata_json, i); json_object_get(entry); // increment reference count json_object_array_add(metadata, entry); + } + json_object_put(upstream_metadata_json); } - json_object_put(upstream_metadata_json); free(upstream_metadata); } debuginfod_pool_end (client); @@ -2368,18 +2407,19 @@ handle_metadata (MHD_Connection* conn, const char* metadata_str = (metadata != NULL) ? json_object_to_json_string(metadata) : "[ ]" ; + if (! metadata_str) + throw libc_exception (ENOMEM, "cannot allocate json"); r = MHD_create_response_from_buffer (strlen(metadata_str), (void*) metadata_str, MHD_RESPMEM_MUST_COPY); *size = strlen(metadata_str); json_object_put(metadata); - #else - throw reportable_exception("webapi error, metadata querying not supported by server"); - #endif - if(r) + if (r) add_mhd_response_header(r, "Content-Type", "application/json"); return r; } +#endif + static struct MHD_Response* handle_root (off_t* size) @@ -2515,23 +2555,20 @@ handler_cb (void * /*cls*/, inc_metric("http_requests_total", "type", artifacttype); r = handle_metrics(& http_size); } +#ifdef HAVE_JSON_C else if (url1 == "/metadata") { tmp_inc_metric m ("thread_busy", "role", "http-metadata"); - - // At the moment only glob path querying is supported, but leave room for - // future expansion - const char *key = "glob"; - - const char* path = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, key); - if (NULL == path) - throw reportable_exception("/metadata webapi error, need glob"); + const char* key = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "key"); + const char* value = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "value"); + if (NULL == value || NULL == key) + throw reportable_exception("/metadata webapi error, need key and value"); artifacttype = "metadata"; inc_metric("http_requests_total", "type", artifacttype); - r = handle_metadata(connection, path, &http_size); - + r = handle_metadata(connection, key, value, &http_size); } +#endif else if (url1 == "/") { artifacttype = "/"; @@ -3819,12 +3856,13 @@ void groom() if (interrupted) return; // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G - sqlite_ps g1 (db, "incremental vacuum", "pragma incremental_vacuum"); - g1.reset().step_ok_done(); - sqlite_ps g2 (db, "optimize", "pragma optimize"); - g2.reset().step_ok_done(); - sqlite_ps g3 (db, "wal checkpoint", "pragma wal_checkpoint=truncate"); - g3.reset().step_ok_done(); + { sqlite_ps g (db, "incremental vacuum", "pragma incremental_vacuum"); g.reset().step_ok_done(); } + // https://www.sqlite.org/lang_analyze.html#approx + { sqlite_ps g (db, "analyze setup", "pragma analysis_limit = 1000;\n"); g.reset().step_ok_done(); } + { sqlite_ps g (db, "analyze", "analyze"); g.reset().step_ok_done(); } + { sqlite_ps g (db, "analyze reload", "analyze sqlite_schema"); g.reset().step_ok_done(); } + { sqlite_ps g (db, "optimize", "pragma optimize"); g.reset().step_ok_done(); } + { sqlite_ps g (db, "wal checkpoint", "pragma wal_checkpoint=truncate"); g.reset().step_ok_done(); } database_stats_report(); diff --git a/debuginfod/debuginfod.h.in b/debuginfod/debuginfod.h.in index 076f8869..4aa38abb 100644 --- a/debuginfod/debuginfod.h.in +++ b/debuginfod/debuginfod.h.in @@ -80,14 +80,14 @@ int debuginfod_find_source (debuginfod_client *client, char **path); /* Query the urls contained in $DEBUGINFOD_URLS for metadata - with paths matching the given, glob-supporting path. + with given query key/value. If successful, return 0, otherwise return a posix error code. If successful, set *metadata to a malloc'd json array with each entry being a json object of metadata for 1 file. - Caller must free() it later. metadata MUST be non-NULL */ + Caller must free() it later. metadata MUST be non-NULL. */ int debuginfod_find_metadata (debuginfod_client *client, - const char* path, char** metadata); + const char *key, const char* value, char** metadata); typedef int (*debuginfod_progressfn_t)(debuginfod_client *c, long a, long b); void debuginfod_set_progressfn(debuginfod_client *c, diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map index 93964167..6e4fe4b5 100644 --- a/debuginfod/libdebuginfod.map +++ b/debuginfod/libdebuginfod.map @@ -20,4 +20,5 @@ ELFUTILS_0.183 { } ELFUTILS_0.179; ELFUTILS_0.188 { debuginfod_get_headers; + debuginfod_find_metadata; } ELFUTILS_0.183; diff --git a/doc/debuginfod-find.1 b/doc/debuginfod-find.1 index caea2462..c44fbc76 100644 --- a/doc/debuginfod-find.1 +++ b/doc/debuginfod-find.1 @@ -30,7 +30,7 @@ debuginfod-find \- request debuginfo-related data .br .B debuginfod-find [\fIOPTION\fP]... source \fIPATH\fP \fI/FILENAME\fP .br -.B debuginfod-find [\fIOPTION\fP]... metadata \fIGLOB\fP +.B debuginfod-find [\fIOPTION\fP]... metadata \fIKEY\fP \fIVALUE\fP .SH DESCRIPTION \fBdebuginfod-find\fP queries one or more \fBdebuginfod\fP servers for @@ -108,20 +108,34 @@ l l. \../bar/foo.c AT_comp_dir=/zoo/ source BUILDID /zoo//../bar/foo.c .TE -.SS metadata \fIGLOB\fP +.SS metadata \fIKEY\fP \fIVALUE\fP -If the given path, which may be globbed, is known to the server, -this request will result in a json string being written to -\fBstdout\fP. The result will consist of a json array, with one -json object for each matching file's metadata. The result can be piped -into \fBjq\fP for easy processing. On failure, an empty array is written -as the output. Unlike finding by buildid no new file will be created or cached. +All designated debuginfod servers are queried for metadata about files +in their index. Different search keys may be supported by different +servers. -For a given file's metadata, the result is guaranteed to contain, \fBatype\fP -(the atrifact type), \fBbuildid\fP, \fBmtime\fP (time of last data modification, -seconds since Epoch), \fBstype\fP (the sourcetype), \fBsource0\fP (the path to -the file in the local filesystem), \fBsource1\fP (the path which is being globbed against). -Extra fields and duplicated metadata may appear. +.TS +l l l . +KEY VALUE DESCRIPTION + +\fBfile\fP path match exact \fIpath\fP, including in archives +\fBglob\fP pattern sqlite glob match \fIpattern\fP, including in archives +.TE + +The results of the search are output to \fBstdout\fP as a JSON array +of objects, supplying metadata about each match. This metadata report +may or may not be cached. It may be incomplete and may contain +duplicates. For each match, the result is a JSON object with these +fields. Additional fields may be present. + +.TS +l l l . +NAME TYPE DESCRIPTION + +\fBbuildid\fP string hexadecimal buildid associated with the file +\fBtype\fP string one of \fBdebuginfo\fP or \fBexecutable\fP or \fBsource\fP +\fBfile\fP string matched file name, outside or inside the archive +.TE .SH "OPTIONS" diff --git a/doc/debuginfod.8 b/doc/debuginfod.8 index 7c1dc3dd..9f529d0a 100644 --- a/doc/debuginfod.8 +++ b/doc/debuginfod.8 @@ -134,6 +134,14 @@ service load. Archive pattern options must still be given, so debuginfod can recognize file name extensions for unpacking. .TP +.B "\-\-metadata\-maxtime=SECONDS" +Impose a limit on the runtime of metadata webapi queries. These +queries, especially broad "glob" wildcards, can take a large amount of +time and produce large results. Public-facing servers may need to +throttle them. The default limit is 5 seconds. Set 0 to disable this +limit. + +.TP .B "\-D SQL" "\-\-ddl=SQL" Execute given sqlite statement after the database is opened and initialized as extra DDL (SQL data definition language). This may be @@ -371,6 +379,16 @@ The exact set of metrics and their meanings may change in future versions. Caution: configuration information (path names, versions) may be disclosed. +.SS /metadata?key=\fIKEY\fP&value=\fIVALUE\fP + +This endpoint triggers a search of the files in the index plus any +upstream federated servers, based on given key and value. If +successful, the result is a application/json textual array, listing +metadata for the matched files. See \fIdebuginfod-find(1)\fP for +documentation of the common key/value search parameters, and the +resulting data schema. + + .SH DATA MANAGEMENT debuginfod stores its index in an sqlite database in a densely packed diff --git a/doc/debuginfod_find_debuginfo.3 b/doc/debuginfod_find_debuginfo.3 index a7e8016c..f131813e 100644 --- a/doc/debuginfod_find_debuginfo.3 +++ b/doc/debuginfod_find_debuginfo.3 @@ -44,7 +44,8 @@ LOOKUP FUNCTIONS .BI " const char *" filename "," .BI " char ** " path ");" .BI "int debuginfod_find_metadata(debuginfod_client *" client "," -.BI " const char *" path "," +.BI " const char *" key "," +.BI " const char *" value "," .BI " char** " metadata ");" OPTIONAL FUNCTIONS @@ -113,10 +114,12 @@ A handle may be reused for a series of lookups, which can improve performance due to retention of connections and caches. .BR debuginfod_find_metadata (), -likewise queries the debuginfod server URLs contained in +likewise queries all debuginfod server URLs contained in .BR $DEBUGINFOD_URLS -but instead retrieves metadata. The given \fIpath\fP may contained -the standard glob characters. +but instead retrieves metadata. The query search mode is specified +in the \fIkey\fP parameter, and its parameter \fIvalue\fP. See +\fIdebuginfod-find(1)\fP for more information on the available +options for query key/value. .SH "RETURN VALUE" @@ -127,11 +130,14 @@ use with all other calls. On error \fBNULL\fP will be returned and If a find family function is successful, the resulting file is saved to the client cache and a file descriptor to that file is returned. The caller needs to \fBclose\fP() this descriptor. Otherwise, a -negative error code is returned. The one excpetion is \fBdebuginfod_find_metadata\fP -which likewise returns negative error codes, but on success returns 0 -and sets \fI*metadata\fP to a string-form json array of the found matching -metadata. This should be freed by the caller. See \fIdebuginfod-find(1)\fP for -more information on the metadata being returned. +negative error code is returned. + +The one exception is \fBdebuginfod_find_metadata\fP, which likewise +returns negative error codes, but on success returns 0 and sets +\fI*metadata\fP to a string-form JSON array of the found matching +metadata. This should be freed by the caller. See +\fIdebuginfod-find(1)\fP for more information on the metadata being +returned. .SH "OPTIONAL FUNCTIONS" diff --git a/tests/run-debuginfod-find-metadata.sh b/tests/run-debuginfod-find-metadata.sh index 18c57067..2e1999f5 100755 --- a/tests/run-debuginfod-find-metadata.sh +++ b/tests/run-debuginfod-find-metadata.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Copyright (C) 2019-2021 Red Hat, Inc. +# Copyright (C) 2022 Red Hat, Inc. # This file is part of elfutils. # # This file is free software; you can redistribute it and/or modify @@ -66,16 +66,16 @@ export DEBUGINFOD_URLS=http://127.0.0.1:$PORT2 tempfiles json.txt # Check that we find 11 files(which means that the local and upstream correctly reply to the query) -N_FOUND=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata "/?sr*" | jq '. | length'` +N_FOUND=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata glob "/?sr*" | jq '. | length'` test $N_FOUND -eq 11 # Query via the webapi as well -MTIME=$(stat -c %Y D/hithere_1.0-1_amd64.deb) -EXPECTED='[ { "atype": "e", "buildid": "f17a29b5a25bd4960531d82aa6b07c8abe84fa66", "mtime": "'$MTIME'", "stype": "R", "source0": "'$PWD'/D/hithere_1.0-1_amd64.deb", "source1": "/usr/bin/hithere"} ]' -test `curl http://127.0.0.1:$PORT2/metadata?glob=/usr/bin/*hi* | jq ". == $EXPECTED" ` = 'true' +EXPECTED='[ { "type": "executable", "buildid": "f17a29b5a25bd4960531d82aa6b07c8abe84fa66", "file": "/usr/bin/hithere"} ]' +curl http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' +test `curl http://127.0.0.1:$PORT2'/metadata?key=glob&value=/usr/bin/*hi*' | jq ". == $EXPECTED" ` = 'true' # An empty array is returned on server error or if the file DNE -test `env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata "/this/isnt/there" | jq ". == [ ]" ` = 'true' +test `env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata file "/this/isnt/there" | jq ". == [ ]" ` = 'true' kill $PID1 kill $PID2 @@ -84,6 +84,6 @@ wait $PID2 PID1=0 PID2=0 -test `env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata "/usr/bin/hithere" | jq ". == [ ]" ` = 'true' +test `env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod-find metadata file "/usr/bin/hithere" | jq ". == [ ]" ` = 'true' exit 0 |