summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrank Ch. Eigler <fche@redhat.com>2022-10-31 17:40:01 -0400
committerFrank Ch. Eigler <fche@redhat.com>2022-10-31 20:22:49 -0400
commit2f1216bd5e311c2ecfe1cbb1eaf2776d91ee14f8 (patch)
tree9c39dd752672f9a02198150967e5f26d5e157575
parent88430aeb79e42e134db4eae43a204d11941f1df9 (diff)
downloadelfutils-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.c46
-rw-r--r--debuginfod/debuginfod-find.c20
-rw-r--r--debuginfod/debuginfod.cxx200
-rw-r--r--debuginfod/debuginfod.h.in6
-rw-r--r--debuginfod/libdebuginfod.map1
-rw-r--r--doc/debuginfod-find.140
-rw-r--r--doc/debuginfod.818
-rw-r--r--doc/debuginfod_find_debuginfo.324
-rwxr-xr-xtests/run-debuginfod-find-metadata.sh14
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