From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 105345 invoked by alias); 12 Nov 2019 21:25:01 -0000 Mailing-List: contact elfutils-devel-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Post: List-Help: List-Subscribe: Sender: elfutils-devel-owner@sourceware.org Received: (qmail 104983 invoked by uid 89); 12 Nov 2019 21:24:55 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Checked: by ClamAV 0.100.3 on sourceware.org X-Virus-Found: No X-Spam-SWARE-Status: No, score=-17.2 required=5.0 tests=AWL,BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_SHORT,SPF_PASS,UNSUBSCRIBE_BODY,URIBL_SBL autolearn=ham version=3.3.1 spammy=destdir, certificates, f, Relative X-Spam-Status: No, score=-17.2 required=5.0 tests=AWL,BAYES_00,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,KAM_SHORT,SPF_PASS,UNSUBSCRIBE_BODY,URIBL_SBL autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on sourceware.org X-Spam-Level: X-HELO: gnu.wildebeest.org Received: from wildebeest.demon.nl (HELO gnu.wildebeest.org) (212.238.236.112) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Tue, 12 Nov 2019 21:24:47 +0000 Received: from tarox.wildebeest.org (tarox.wildebeest.org [172.31.17.39]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by gnu.wildebeest.org (Postfix) with ESMTPSA id 9969A3001768; Tue, 12 Nov 2019 22:24:41 +0100 (CET) Received: by tarox.wildebeest.org (Postfix, from userid 1000) id 1EB7040350FA; Tue, 12 Nov 2019 22:24:41 +0100 (CET) Message-ID: <9bc154bce6389be9b07f2db9dcdcc605ad4f39e3.camel@klomp.org> Subject: Re: patch 1/2 debuginfod client From: Mark Wielaard To: "Frank Ch. Eigler" , elfutils-devel@sourceware.org Cc: amerey@redhat.com Date: Tue, 12 Nov 2019 21:25:00 -0000 In-Reply-To: <20191028190602.GD14349@redhat.com> References: <20191028190438.GC14349@redhat.com> <20191028190602.GD14349@redhat.com> Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Mailer: Evolution 3.28.5 (3.28.5-5.el7) Mime-Version: 1.0 X-Spam-Flag: NO X-IsSubscribed: yes X-SW-Source: 2019-q4/txt/msg00123.txt.bz2 Hi, On Mon, 2019-10-28 at 15:06 -0400, Frank Ch. Eigler wrote: > Introduce the debuginfod/ subdirectory, containing the client for a > new debuginfo-over-http service, in shared-library and command-line > forms. Two functions in libdwfl make calls into the client library to > fetch elf/dwarf files by buildid, as a fallback. Instead of normal > dynamic linking (thus pulling in a variety of curl dependencies), > the libdwfl hooks use dlopen/dlsym. Server & tests coming in patch 2. And a bit more review. The debuginfod subdir in this case. diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog > new file mode 100644 > index 000000000000..1a31cf6f4e27 > --- /dev/null > +++ b/debuginfod/ChangeLog > @@ -0,0 +1,9 @@ > +2019-10-28 Aaron Merey > + > + * debuginfod-client.c: New file: debuginfod client library. > + * debuginfod.h: New file: header for same. > + * libdebuginfod.map: New file: govern its solib exports. > + * debuginfod-find.c: New file: command line frontend. > + * debuginfod-find.1, debuginfod_find_source.3, > + debuginfod_find_executable.3, debuginfod_find_debuginfo.3: > + New man pages. > diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am > new file mode 100644 > index 000000000000..3a4e94da2ad0 > --- /dev/null > +++ b/debuginfod/Makefile.am > @@ -0,0 +1,116 @@ > +## Makefile.am for libdebuginfod library subdirectory in elfutils. > +## > +## Process this file with automake to create Makefile.in > +## > +## Copyright (C) 2019 Red Hat, Inc. > +## This file is part of elfutils. > +## > +## This file is free software; you can redistribute it and/or modify > +## it under the terms of either > +## > +## * the GNU Lesser General Public License as published by the Free > +## Software Foundation; either version 3 of the License, or (at > +## your option) any later version > +## > +## or > +## > +## * 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 > +## > +## or both in parallel, as here. > +## > +## elfutils 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 copies of the GNU General Public License and > +## the GNU Lesser General Public License along with this program. If > +## not, see . > +## > +include $(top_srcdir)/config/eu.am > +AM_CPPFLAGS +=3D -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl= \ > + -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf > +VERSION =3D 1 > + > +# Disable eu- prefixing for artifacts (binaries & man pages) in this > +# directory, since they do not conflict with binutils tools. > +program_prefix=3D > +program_transform_name =3D s,x,x, > + > +if BUILD_STATIC > +libasm =3D ../libasm/libasm.a > +libdw =3D ../libdw/libdw.a -lz $(zip_LIBS) $(libelf) $(libebl) -ldl > +libelf =3D ../libelf/libelf.a -lz > +libdebuginfod =3D ./libdebuginfod.a > +else > +libasm =3D ../libasm/libasm.so > +libdw =3D ../libdw/libdw.so > +libelf =3D ../libelf/libelf.so > +libdebuginfod =3D ./libdebuginfod.so > +endif > +libebl =3D ../libebl/libebl.a > +libeu =3D ../lib/libeu.a I am not sure the BUILD_STATIC support is really tested and works. But it looks correct. It is used when building with profiling code ( --enable-gcov). > +AM_LDFLAGS =3D -Wl,-rpath-link,../libelf:../libdw:. > + > +bin_PROGRAMS =3D debuginfod-find > +dist_man3_MANS =3D debuginfod_find_debuginfo.3 debuginfod_find_source.3 = debuginfod_find_executable.3 > +dist_man1_MANS =3D debuginfod-find.1 Hurrah! Documentation! Thanks. But given that all other documentation is under doc/ could you move it there (guarded by DEBUGINFOD). It is just more consistent. If you leave it in this subdir I think you should also move the existing documentation files (and I think that is not worth it at the moment). > +debuginfod_find_SOURCES =3D debuginfod-find.c > +debuginfod_find_LDADD =3D $(libeu) $(libdebuginfod) > + > +noinst_LIBRARIES =3D libdebuginfod.a > +noinst_LIBRARIES +=3D libdebuginfod_pic.a > + > +libdebuginfod_a_SOURCES =3D debuginfod-client.c > +libdebuginfod_pic_a_SOURCES =3D debuginfod-client.c > +am_libdebuginfod_pic_a_OBJECTS =3D $(libdebuginfod_a_SOURCES:.c=3D.os) > + > +pkginclude_HEADERS =3D debuginfod.h > + > +libdebuginfod_so_LIBS =3D libdebuginfod_pic.a > +libdebuginfod_so_LDLIBS =3D $(libcurl_LIBS) > +libdebuginfod.so$(EXEEXT): $(srcdir)/libdebuginfod.map $(libdebuginfod_s= o_LIBS) > + $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \ > + -Wl,--soname,$@.$(VERSION) \ > + -Wl,--version-script,$<,--no-undefined \ > + -Wl,--whole-archive $(libdebuginfod_so_LIBS) -Wl,--no-whole-archive \ > + $(libdebuginfod_so_LDLIBS) > + @$(textrel_check) > + $(AM_V_at)ln -fs $@ $@.$(VERSION) Nice, properly versioned library. > +install: install-am libdebuginfod.so > + $(mkinstalldirs) $(DESTDIR)$(libdir) > + $(INSTALL_PROGRAM) libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$= (PACKAGE_VERSION).so > + ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebug= infod.so.$(VERSION) > + ln -fs libdebuginfod.so.$(VERSION) $(DESTDIR)$(libdir)/libdebuginfod.so > + > +uninstall: uninstall-am > + rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so > + rm -f $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION) > + rm -f $(DESTDIR)$(libdir)/libdebuginfod.so > + rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils Yeah, unfortunate dance. Looks correct though. > +EXTRA_DIST =3D libdebuginfod.map > +MOSTLYCLEANFILES =3D $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so.= $(VERSION) > +CLEANFILES +=3D $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so > + > +# automake std-options override: arrange to pass LD_LIBRARY_PATH > +installcheck-binPROGRAMS: $(bin_PROGRAMS) > + bad=3D0; pid=3D$$$$; list=3D"$(bin_PROGRAMS)"; for p in $$list; do \ > + case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \ > + *" $$p "* | *" $(srcdir)/$$p "*) continue;; \ > + esac; \ > + f=3D`echo "$$p" | \ > + sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ > + for opt in --help --version; do \ > + if LD_LIBRARY_PATH=3D$(DESTDIR)$(libdir) \ > + $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \ > + && test -n "`cat c$${pid}_.out`" \ > + && test -z "`cat c$${pid}_.err`"; then :; \ > + else echo "$$f does not support $$opt" 1>&2; bad=3D1; fi; \ > + done; \ > + done; rm -f c$${pid}_.???; exit $$bad I see we also do this in src/Makefile.am but, ehe, why? > diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-clien= t.c > new file mode 100644 > index 000000000000..2b91bb8bb1d2 > --- /dev/null > +++ b/debuginfod/debuginfod-client.c > @@ -0,0 +1,624 @@ > +/* Retrieve ELF / DWARF / source files from the debuginfod. > + Copyright (C) 2019 Red Hat, Inc. > + This file is part of elfutils. > + > + This file is free software; you can redistribute it and/or modify > + it under the terms of either > + > + * the GNU Lesser General Public License as published by the Free > + Software Foundation; either version 3 of the License, or (at > + your option) any later version > + > + or > + > + * 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 > + > + or both in parallel, as here. > + > + elfutils 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 copies of the GNU General Public License and > + the GNU Lesser General Public License along with this program. If > + not, see . */ > + > +#include "config.h" > +#include "debuginfod.h" > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include O, o... older (glibc) fts is problematic on 32bit systems when using large file offsets. See libdwfl/linux-kernel-modules.c. See the BAD_FTS check in configure.ac Older glibc had a broken fts that didn't work with Large File Systems. We want the version that can handler LFS, but include workaround if we get a bad one. Add define to CFLAGS (not AC_DEFINE it) since we need to check it before including config.h (which might define _FILE_OFFSET_BITS). Have you tried to build and run on i686 on Debian stable or CentOS7? > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +static const int max_build_id_bytes =3D 256; /* typical: 40 for gnu C to= olchain */ > + > + > +/* The cache_clean_interval_s file within the debuginfod cache specifies > + how frequently the cache should be cleaned. The file's st_mtime repre= sents > + the time of last cleaning. */ > +static const char *cache_clean_interval_filename =3D "cache_clean_interv= al_s"; > +static const time_t cache_clean_default_interval_s =3D 600; > + > +/* The cache_max_unused_age_s file within the debuginfod cache specifies= the > + the maximum time since last access that a file will remain in the cac= he. */ > +static const char *cache_max_unused_age_filename =3D "max_unused_age_s"; > +static const time_t cache_default_max_unused_age_s =3D 600; > + > +/* Location of the cache of files downloaded from debuginfods. > + The default parent directory is $HOME, or '/' if $HOME doesn't exist.= */ > +static const char *cache_default_name =3D ".debuginfod_client_cache"; > +static const char *cache_path_envvar =3D DEBUGINFOD_CACHE_PATH_ENV_VAR; > + > +/* URLs of debuginfods, separated by url_delim. > + This env var must be set for debuginfod-client to run. */ > +static const char *server_urls_envvar =3D DEBUGINFOD_URLS_ENV_VAR; > +static const char *url_delim =3D " "; > +static const char url_delim_char =3D ' '; > + > +/* Timeout for debuginfods, in seconds. > + This env var must be set for debuginfod-client to run. */ > +static const char *server_timeout_envvar =3D DEBUGINFOD_TIMEOUT_ENV_VAR; > +static int server_timeout =3D 5; > + > +/* Data associated with a particular CURL easy handle. Passed to > + the write callback. */ > +struct handle_data > +{ > + /* Cache file to be written to in case query is successful. */ > + int fd; > + > + /* URL queried by this handle. */ > + char url[PATH_MAX]; Are you sure that is the correct limit? Could it be made dynamic? > + /* This handle. */ > + CURL *handle; > + > + /* Pointer to handle that should write to fd. Initially points to NULL, > + then points to the first handle that begins writing the target file > + to the cache. Used to ensure that a file is not downloaded from > + multiple servers unnecessarily. */ > + CURL **target_handle; > +}; > + > +static size_t > +debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *d= ata) > +{ > + ssize_t count =3D size * nmemb; > + > + struct handle_data *d =3D (struct handle_data*)data; > + > + /* Indicate to other handles that they can abort their transfer. */ > + if (*d->target_handle =3D=3D NULL) > + *d->target_handle =3D d->handle; > + > + /* If this handle isn't the target handle, abort transfer. */ > + if (*d->target_handle !=3D d->handle) > + return -1; > + > + return (size_t) write(d->fd, (void*)ptr, count); > +} > + > +/* Create the cache and interval file if they do not already exist. > + Return 0 if cache and config file are initialized, otherwise return > + the appropriate error code. */ > +static int > +debuginfod_init_cache (char *cache_path, char *interval_path, char *maxa= ge_path) > +{ > + struct stat st; > + > + /* If the cache and config file already exist then we are done. */ > + if (stat(cache_path, &st) =3D=3D 0 && stat(interval_path, &st) =3D=3D = 0) > + return 0; > + > + /* Create the cache and config files as necessary. */ > + if (stat(cache_path, &st) !=3D 0 && mkdir(cache_path, 0777) < 0) > + return -errno; > + > + int fd =3D -1; > + > + /* init cleaning interval config file. */ > + fd =3D open(interval_path, O_CREAT | O_RDWR, 0666); > + if (fd < 0) > + return -errno; > + > + if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0) > + return -errno; > + > + /* init max age config file. */ > + if (stat(maxage_path, &st) !=3D 0 > + && (fd =3D open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0) > + return -errno; > + > + if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0) > + return -errno; > + > + return 0; > +} > + > + > +/* Delete any files that have been unmodied for a period > + longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */ > +static int > +debuginfod_clean_cache(char *cache_path, char *interval_path, char *max_= unused_path) > +{ > + struct stat st; > + FILE *interval_file; > + FILE *max_unused_file; > + > + if (stat(interval_path, &st) =3D=3D -1) > + { > + /* Create new interval file. */ > + interval_file =3D fopen(interval_path, "w"); > + > + if (interval_file =3D=3D NULL) > + return -errno; > + > + int rc =3D fprintf(interval_file, "%ld", cache_clean_default_inter= val_s); > + fclose(interval_file); > + > + if (rc < 0) > + return -errno; > + } > + > + /* Check timestamp of interval file to see whether cleaning is necessa= ry. */ > + time_t clean_interval; > + interval_file =3D fopen(interval_path, "r"); > + if (fscanf(interval_file, "%ld", &clean_interval) !=3D 1) > + clean_interval =3D cache_clean_default_interval_s; > + fclose(interval_file); > + > + if (time(NULL) - st.st_mtime < clean_interval) > + /* Interval has not passed, skip cleaning. */ > + return 0; > + > + /* Read max unused age value from config file. */ > + time_t max_unused_age; > + max_unused_file =3D fopen(max_unused_path, "r"); > + if (max_unused_file) > + { > + if (fscanf(max_unused_file, "%ld", &max_unused_age) !=3D 1) > + max_unused_age =3D cache_default_max_unused_age_s; > + fclose(max_unused_file); > + } > + else > + max_unused_age =3D cache_default_max_unused_age_s; > + > + char * const dirs[] =3D { cache_path, NULL, }; > + > + FTS *fts =3D fts_open(dirs, 0, NULL); > + if (fts =3D=3D NULL) > + return -errno; > + > + FTSENT *f; > + while ((f =3D fts_read(fts)) !=3D NULL) > + { > + switch (f->fts_info) > + { > + case FTS_F: > + /* delete file if max_unused_age has been met or exceeded. */ > + /* XXX consider extra effort to clean up old tmp files */ > + if (time(NULL) - f->fts_statp->st_atime >=3D max_unused_age) > + unlink (f->fts_path); > + break; > + > + case FTS_DP: > + /* Remove if empty. */ > + (void) rmdir (f->fts_path); > + break; > + > + default: > + ; > + } > + } > + fts_close(fts); > + > + /* Update timestamp representing when the cache was last cleaned. */ > + utime (interval_path, NULL); > + return 0; > +} > + > +/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file > + with the specified build-id, type (debuginfo, executable or source) > + and filename. filename may be NULL. If found, return a file > + descriptor for the target, otherwise return an error code. */ You don't describe PATH. I assume the caller is responsible for freeing it? Is it set on error? > +static int > +debuginfod_query_server (const unsigned char *build_id, > + int build_id_len, > + const char *type, > + const char *filename, > + char **path) > +{ > + char *urls_envvar; > + char *server_urls; > + char cache_path[PATH_MAX]; > + char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up g= cc -Wformat-truncation */ > + char interval_path[PATH_MAX*4]; > + char target_cache_dir[PATH_MAX*2]; > + char target_cache_path[PATH_MAX*3]; > + char target_cache_tmppath[PATH_MAX*4]; > + char suffix[PATH_MAX]; Ah, lots more uses of PATH_MAX. Now my worry is kind of the opposite. These are all stack allocated. Won't that blow up the stack? > + char build_id_bytes[max_build_id_bytes * 2 + 1]; > + > + /* Copy lowercase hex representation of build_id into buf. */ > + if ((build_id_len >=3D max_build_id_bytes) || > + (build_id_len =3D=3D 0 && > + sizeof(build_id_bytes) > max_build_id_bytes*2 + 1)) > + return -EINVAL; I wonder if you also want to check for a build_id that is simply too small. Since build_id are supposed to be globally unique it doesn't really make sense to have a build_id length that is too small. libdwfl for example needs at least 3 bytes: /* We don't handle very short or really large build-ids. We need at at least 3 and allow for up to 64 (normally ids are 20 long). */ #define MIN_BUILD_ID_BYTES 3 #define MAX_BUILD_ID_BYTES 64 > + if (build_id_len =3D=3D 0) /* expect clean hexadecimal */ > + strcpy (build_id_bytes, (const char *) build_id); > + else > + for (int i =3D 0; i < build_id_len; i++) > + sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]); I would sanity check the "clean hexadecimal" =3D=3D 0 case. > + unsigned q =3D 0; > + if (filename !=3D NULL) > + { > + if (filename[0] !=3D '/') // must start with / > + return -EINVAL; > + > + /* copy the filename to suffix, s,/,#,g */ > + for (q=3D0; q + { > + if (filename[q] =3D=3D '\0') break; > + if (filename[q] =3D=3D '/' || filename[q] =3D=3D '.') suffix[q= ] =3D '#'; > + else suffix[q] =3D filename[q]; > + } > + /* XXX: if we had a CURL* handle at this time, we could > + curl_easy_escape() to url-escape the filename in a > + collision-free, reversible manner. */ > + } > + suffix[q] =3D '\0'; > + > + /* set paths needed to perform the query > + > + example format > + cache_path: $HOME/.debuginfod_cache > + target_cache_dir: $HOME/.debuginfod_cache/0123abcd > + target_cache_path: $HOME/.debuginfod_cache/0123abcd/debuginfo > + target_cache_path: $HOME/.debuginfod_cache/0123abcd/source#PATH#TO#= SOURCE ? > + */ > + > + if (getenv(cache_path_envvar)) > + strcpy(cache_path, getenv(cache_path_envvar)); > + else > + { > + if (getenv("HOME")) > + sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name); > + else > + sprintf(cache_path, "/%s", cache_default_name); > + } > + > + /* avoid using snprintf here due to compiler warning. */ > + snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_pa= th, build_id_bytes); > + snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", targ= et_cache_dir, type, suffix); > + snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXX= X", target_cache_path); I don't understand the comment, you are actually using snprintf? > + /* XXX combine these */ > + snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, ca= che_clean_interval_filename); > + snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_= max_unused_age_filename); > + int rc =3D debuginfod_init_cache(cache_path, interval_path, maxage_pat= h); > + if (rc !=3D 0) > + goto out; > + rc =3D debuginfod_clean_cache(cache_path, interval_path, maxage_path); > + if (rc !=3D 0) > + goto out; > + > + /* If the target is already in the cache then we are done. */ > + int fd =3D open (target_cache_path, O_RDONLY); > + if (fd >=3D 0) > + { > + /* Success!!!! */ > + if (path !=3D NULL) > + *path =3D strdup(target_cache_path); > + return fd; > + } > + > + urls_envvar =3D getenv(server_urls_envvar); > + if (urls_envvar =3D=3D NULL || urls_envvar[0] =3D=3D '\0') > + { > + rc =3D -ENOSYS; > + goto out; > + } > + > + if (getenv(server_timeout_envvar)) > + server_timeout =3D atoi (getenv(server_timeout_envvar)); > + > + /* make a copy of the envvar so it can be safely modified. */ > + server_urls =3D strdup(urls_envvar); > + if (server_urls =3D=3D NULL) > + { > + rc =3D -ENOMEM; > + goto out; > + } > + /* thereafter, goto out0 on error*/ > + > + /* create target directory in cache if not found. */ > + struct stat st; > + if (stat(target_cache_dir, &st) =3D=3D -1 && mkdir(target_cache_dir, 0= 700) < 0) > + { > + rc =3D -errno; > + goto out0; > + } > + > + /* NB: write to a temporary file first, to avoid race condition of > + multiple clients checking the cache, while a partially-written or e= mpty > + file is in there, being written from libcurl. */ > + fd =3D mkstemp (target_cache_tmppath); > + if (fd < 0) > + { > + rc =3D -errno; > + goto out0; > + } > + > + /* Count number of URLs. */ > + int num_urls =3D 0; > + for (int i =3D 0; server_urls[i] !=3D '\0'; i++) > + if (server_urls[i] !=3D url_delim_char > + && (server_urls[i - 1] =3D=3D url_delim_char || i =3D=3D 0)) > + num_urls++; > + > + /* Tracks which handle should write to fd. Set to the first > + handle that is ready to write the target file to the cache. */ > + CURL *target_handle =3D NULL; > + struct handle_data *data =3D malloc(sizeof(struct handle_data) * num_u= rls); > + > + /* Initalize handle_data with default values. */ > + for (int i =3D 0; i < num_urls; i++) > + { > + data[i].handle =3D NULL; > + data[i].fd =3D -1; > + } > + > + CURLM *curlm =3D curl_multi_init(); > + if (curlm =3D=3D NULL) > + { > + rc =3D -ENETUNREACH; > + goto out0; > + } > + /* thereafter, goto out1 on error. */ > + > + char *strtok_saveptr; > + char *server_url =3D strtok_r(server_urls, url_delim, &strtok_saveptr); > + > + /* Initialize each handle. */ > + for (int i =3D 0; i < num_urls && server_url !=3D NULL; i++) > + { > + data[i].fd =3D fd; > + data[i].target_handle =3D &target_handle; > + data[i].handle =3D curl_easy_init(); > + > + if (data[i].handle =3D=3D NULL) > + { > + rc =3D -ENETUNREACH; > + goto out1; > + } > + > + /* Build handle url. Tolerate both http://foo:999 and > + http://foo:999/ forms */ > + char *slashbuildid; > + if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] =3D= =3D '/') > + slashbuildid =3D "buildid"; > + else > + slashbuildid =3D "/buildid"; > + > + if (filename) /* must start with / */ > + snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url, > + slashbuildid, build_id_bytes, type, filename); > + else > + snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url, > + slashbuildid, build_id_bytes, type); > + > + curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url); > + curl_easy_setopt(data[i].handle, > + CURLOPT_WRITEFUNCTION, > + debuginfod_write_callback); > + curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i= ]); > + curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_ti= meout); > + curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1); > + curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1); > + curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1); > + curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1); > + curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, ""); > + curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAG= E_STRING); > + > + curl_multi_add_handle(curlm, data[i].handle); > + server_url =3D strtok_r(NULL, url_delim, &strtok_saveptr); > + } > + > + /* Query servers in parallel. */ > + int still_running; > + do > + { > + CURLMcode curl_res; > + > + /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */ > + curl_multi_wait(curlm, NULL, 0, 1000, NULL); Why not actually use DEBUGINFOD_TIMEOUT? > + /* If the target file has been found, abort the other queries. */ > + if (target_handle !=3D NULL) > + for (int i =3D 0; i < num_urls; i++) > + if (data[i].handle !=3D target_handle) > + curl_multi_remove_handle(curlm, data[i].handle); > + > + curl_res =3D curl_multi_perform(curlm, &still_running); > + if (curl_res !=3D CURLM_OK) > + { > + switch (curl_res) > + { > + case CURLM_CALL_MULTI_PERFORM: continue; > + case CURLM_OUT_OF_MEMORY: rc =3D -ENOMEM; break; > + default: rc =3D -ENETUNREACH; break; > + } > + goto out1; > + } > + } while (still_running); > + > + /* Check whether a query was successful. If so, assign its handle > + to verified_handle. */ > + int num_msg; > + rc =3D -ENOENT; > + CURL *verified_handle =3D NULL; > + do > + { > + CURLMsg *msg; > + > + msg =3D curl_multi_info_read(curlm, &num_msg); > + if (msg !=3D NULL && msg->msg =3D=3D CURLMSG_DONE) > + { > + if (msg->data.result !=3D CURLE_OK) > + { > + /* Unsucessful query, determine error code. */ > + switch (msg->data.result) > + { > + case CURLE_COULDNT_RESOLVE_HOST: rc =3D -EHOSTUNREACH; b= reak; // no NXDOMAIN > + case CURLE_URL_MALFORMAT: rc =3D -EINVAL; break; > + case CURLE_COULDNT_CONNECT: rc =3D -ECONNREFUSED; break; > + case CURLE_REMOTE_ACCESS_DENIED: rc =3D -EACCES; break; > + case CURLE_WRITE_ERROR: rc =3D -EIO; break; > + case CURLE_OUT_OF_MEMORY: rc =3D -ENOMEM; break; > + case CURLE_TOO_MANY_REDIRECTS: rc =3D -EMLINK; break; > + case CURLE_SEND_ERROR: rc =3D -ECONNRESET; break; > + case CURLE_RECV_ERROR: rc =3D -ECONNRESET; break; > + case CURLE_OPERATION_TIMEDOUT: rc =3D -ETIME; break; > + default: rc =3D -ENOENT; break; > + } > + } > + else > + { > + /* Query completed without an error. Confirm that the > + response code is 200 and set verified_handle. */ > + long resp_code =3D 500; > + CURLcode curl_res; > + > + curl_res =3D curl_easy_getinfo(target_handle, > + CURLINFO_RESPONSE_CODE, > + &resp_code); > + > + if (curl_res =3D=3D CURLE_OK > + && resp_code =3D=3D 200 > + && msg->easy_handle !=3D NULL) > + { > + verified_handle =3D msg->easy_handle; > + break; > + } > + } > + } > + } while (num_msg > 0); > + > + if (verified_handle =3D=3D NULL) > + goto out1; > + > + /* we've got one!!!! */ > + time_t mtime; > + CURLcode curl_res =3D curl_easy_getinfo(verified_handle, CURLINFO_FILE= TIME, (void*) &mtime); > + if (curl_res !=3D CURLE_OK) > + mtime =3D time(NULL); /* fall back to current time */ > + > + struct timeval tvs[2]; > + tvs[0].tv_sec =3D tvs[1].tv_sec =3D mtime; > + tvs[0].tv_usec =3D tvs[1].tv_usec =3D 0; > + (void) futimes (fd, tvs); /* best effort */ > + > + /* rename tmp->real */ > + rc =3D rename (target_cache_tmppath, target_cache_path); > + if (rc < 0) > + { > + rc =3D -errno; > + goto out1; > + /* Perhaps we need not give up right away; could retry or somethin= g ... */ > + } > + > + /* Success!!!! */ > + for (int i =3D 0; i < num_urls; i++) > + curl_easy_cleanup(data[i].handle); > + > + curl_multi_cleanup (curlm); > + free (data); > + free (server_urls); > + /* don't close fd - we're returning it */ > + /* don't unlink the tmppath; it's already been renamed. */ > + if (path !=3D NULL) > + *path =3D strdup(target_cache_path); > + > + return fd; OK, looks like both PATHs where *path is set you return a zero or positive value. > +/* error exits */ > + out1: > + for (int i =3D 0; i < num_urls; i++) > + curl_easy_cleanup(data[i].handle); > + > + curl_multi_cleanup(curlm); > + unlink (target_cache_tmppath); > + (void) rmdir (target_cache_dir); /* nop if not empty */ > + free(data); > + close (fd); > + > + out0: > + free (server_urls); > + > + out: > + return rc; > +} Question about writing/creating and removal of target_cache. It seems they rely on an environment variable. Can a user trick this call into overwriting some existing files? Are you scrubbing all paths of things like ../ ? Just a bit concerned about weird paths, file names, URLs being set accidentally and the wrong files being over-written/removed. > +/* See debuginfod.h */ > +int > +debuginfod_find_debuginfo (const unsigned char *build_id, int build_id_l= en, > + char **path) > +{ > + return debuginfod_query_server(build_id, build_id_len, > + "debuginfo", NULL, path); > +} > + > + > +/* See debuginfod.h */ > +int > +debuginfod_find_executable(const unsigned char *build_id, int build_id_l= en, > + char **path) > +{ > + return debuginfod_query_server(build_id, build_id_len, > + "executable", NULL, path); > +} > + > +/* See debuginfod.h */ > +int debuginfod_find_source(const unsigned char *build_id, > + int build_id_len, > + const char *filename, > + char **path) > +{ > + return debuginfod_query_server(build_id, build_id_len, > + "source", filename, path); > +} > + > + > + > +/* NB: these are thread-unsafe. */ > +__attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(vo= id) > +{ > + curl_global_init(CURL_GLOBAL_DEFAULT); > +} How does this interact with a program that uses libcurl itself and also links with libdebuginfod? > +/* NB: this is very thread-unsafe: it breaks other threads that are stil= l in libcurl */ > +__attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(voi= d) > +{ > + /* ... so don't do this: */ > + /* curl_global_cleanup(); */ > +} yeah, so something leaks, o well. > diff --git a/debuginfod/debuginfod-find.1 b/debuginfod/debuginfod-find.1 > new file mode 100644 > index 000000000000..6d2251662a57 > --- /dev/null > +++ b/debuginfod/debuginfod-find.1 > @@ -0,0 +1,131 @@ > +'\"! tbl | nroff \-man > +'\" t macro stdmacro > + > +.de SAMPLE > +.br > +.RS 0 > +.nf > +.nh > +.. > +.de ESAMPLE > +.hy > +.fi > +.RE > +.. > + > +.TH DEBUGINFOD-FIND 1 > +.SH NAME > +debuginfod-find \- request debuginfo-related data > + > +.SH SYNOPSIS > +.B debuginfod-find debuginfo \fIBUILDID\fP > + > +.B debuginfod-find executable \fIBUILDID\fP > + > +.B debuginfod-find source \fIBUILDID\fP \fI/FILENAME\fP > + > +.SH DESCRIPTION > +\fBdebuginfod-find\fP queries one or more \fBdebuginfod\fP servers for > +debuginfo-related data. In case of a match, it saves the the > +requested file into a local cache, prints the file name to standard > +output, and exits with a success status of 0. In case of any error, > +it exits with a failure status and an error message to standard error. > + > +.\" Much of the following text is duplicated with debuginfod.8 > + > +The debuginfod system uses buildids to identify debuginfo-related data. > +These are stored as binary notes in ELF/DWARF files, and are > +represented as lowercase hexadecimal. For example, for a program > +/bin/ls, look at the ELF note GNU_BUILD_ID: > + > +.SAMPLE > +% readelf -n /bin/ls | grep -A4 build.id > +Note section [ 4] '.note.gnu.buildid' of 36 bytes at offset 0x340: > +Owner Data size Type > +GNU 20 GNU_BUILD_ID > +Build ID: 8713b9c3fb8a720137a4a08b325905c7aaf8429d > +.ESAMPLE > + > +Then the hexadecimal BUILDID is simply: > + > +.SAMPLE > +8713b9c3fb8a720137a4a08b325905c7aaf8429d > +.ESAMPLE > + > +.SS debuginfo \fIBUILDID\fP > + > +If the given buildid is known to a server, this request will result > +in a binary object that contains the customary \fB.*debug_*\fP > +sections. This may be a split debuginfo file as created by > +\fBstrip\fP, or it may be an original unstripped executable. > + > +.SS executable \fIBUILDID\fP > + > +If the given buildid is known to the server, this request will result > +in a binary object that contains the normal executable segments. This > +may be a executable stripped by \fBstrip\fP, or it may be an original > +unstripped executable. \fBET_DYN\fP shared libraries are considered > +to be a type of executable. > + > +.SS source \fIBUILDID\fP \fI/SOURCE/FILE\fP > + > +If the given buildid is known to the server, this request will result > +in a binary object that contains the source file mentioned. The path > +should be absolute. Relative path names commonly appear in the DWARF > +file's source directory, but these paths are relative to > +individual compilation unit AT_comp_dir paths, and yet an executable > +is made up of multiple CUs. Therefore, to disambiguate, debuginfod > +expects source queries to prefix relative path names with the CU > +compilation-directory. > + > +Note: you should not elide \fB../\fP or \fB/./\fP sorts of relative > +path components in the directory names, because if this is how those > +names appear in the DWARF files, that is what debuginfod needs to see > +too. > + > +For example: > +.TS > +l l. > +#include source BUILDID /usr/include/stdio.h > +/path/to/foo.c source BUILDID /path/to/foo.c > +\../bar/foo.c AT_comp_dir=3D/zoo source BUILDID /zoo/../bar/foo.c > +.TE Good that you give an example. This somewhat ties into my question above. So you don't scrub /../ normally. I am still somewhat worried about bogus paths to go outside of what is expected. > +.SH "SECURITY" > + > +debuginfod-find \fBdoes not\fP include any particular security > +features. It trusts that the binaries returned by the debuginfod(s) > +are accurate. Therefore, the list of servers should include only > +trustworthy ones. If accessed across HTTP rather than HTTPS, the > +network should be trustworthy. I assume libcurl handles tls certificates for https? Does that need any mention here? > +.SH "ENVIRONMENT VARIABLES" > + > +.TP 21 > +.B DEBUGINFOD_URLS > +This environment variable contains a list of URL prefixes for trusted > +debuginfod instances. Alternate URL prefixes are separated by space. > + > +.TP 21 > +.B DEBUGINFOD_TIMEOUT > +This environment variable governs the timeout for each debuginfod HTTP > +connection. A server that fails to respond within this many seconds > +is skipped. The default is 5. > + > +.TP 21 > +.B DEBUGINFOD_CACHE_PATH > +This environment variable governs the location of the cache where > +downloaded files are kept. It is cleaned periodically as this > +program is reexecuted. The default is $HOME/.debuginfod_client_cache. > +.\" XXX describe cache eviction policy > + > +.SH "FILES" > +.LP > +.PD .1v > +.TP 20 > +.B $HOME/.debuginfod_client_cache > +Default cache directory. > +.PD > + > +.SH "SEE ALSO" > +.I "debuginfod(8)" See note above (and below) about where to put this doc file. > diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c > new file mode 100644 > index 000000000000..78322fc6cd29 > --- /dev/null > +++ b/debuginfod/debuginfod-find.c > @@ -0,0 +1,108 @@ > +/* Command-line frontend for retrieving ELF / DWARF / source files > + from the debuginfod. > + Copyright (C) 2019 Red Hat, Inc. > + This file is part of elfutils. > + > + This file is free software; you can redistribute it and/or modify > + it under the terms of either > + > + * the GNU Lesser General Public License as published by the Free > + Software Foundation; either version 3 of the License, or (at > + your option) any later version > + > + or > + > + * 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 > + > + or both in parallel, as here. > + > + elfutils 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 copies of the GNU General Public License and > + the GNU Lesser General Public License along with this program. If > + not, see . */ For standalone tools, like those in src, we normally just use GPLv3+. > +#include "config.h" > +#include "printversion.h" > +#include "debuginfod.h" > +#include > +#include > +#include > +#include > + > + > +/* Name and version of program. */ > +ARGP_PROGRAM_VERSION_HOOK_DEF =3D print_version; > + > +/* Bug report address. */ > +ARGP_PROGRAM_BUG_ADDRESS_DEF =3D PACKAGE_BUGREPORT; > + > +/* Short description of program. */ > +static const char doc[] =3D N_("Request debuginfo-related content " > + "from debuginfods listed in $" DEBUGINFOD_U= RLS_ENV_VAR "."); > + > +/* Strings for arguments in help texts. */ > +static const char args_doc[] =3D N_("debuginfo BUILDID\n" > + "executable BUILDID\n" > + "source BUILDID /FILENAME"); > + > +/* Data structure to communicate with argp functions. */ > +static struct argp argp =3D > + { > + NULL, NULL, args_doc, doc, NULL, NULL, NULL > + }; > + > + > + > +int > +main(int argc, char** argv) > +{ > + int remaining; > + (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &rem= aining, NULL); > + > + if (argc < 2 || remaining+1 =3D=3D argc) /* no arguments or at least t= wo non-option words */ > + { > + argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); > + return 1; > + } > +=20=20 > + int rc; > + char *cache_name; > + > + /* Check whether FILETYPE is valid and call the appropriate > + debuginfod_find_* function. If FILETYPE is "source" > + then ensure a FILENAME was also supplied as an argument. */ > + if (strcmp(argv[remaining], "debuginfo") =3D=3D 0) > + rc =3D debuginfod_find_debuginfo((unsigned char *)argv[remaining+1],= 0, &cache_name); > + else if (strcmp(argv[remaining], "executable") =3D=3D 0) > + rc =3D debuginfod_find_executable((unsigned char *)argv[remaining+1]= , 0, &cache_name); > + else if (strcmp(argv[remaining], "source") =3D=3D 0) > + { > + if (remaining+2 =3D=3D argc || argv[3][0] !=3D '/') > + { > + fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILE= NAME must be given\n"); > + return 1; > + } > + rc =3D debuginfod_find_source((unsigned char *)argv[remaining+1], = 0, > + argv[remaining+2], &cache_name); > + } > + else > + { > + argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); > + return 1; > + } > + > + if (rc < 0) > + { > + fprintf(stderr, "Server query failed: %s\n", strerror(-rc)); > + return 1; > + } Is there any way we can get/print the actual URL tried here? That would really help the user trying to figure out what happened. > + printf("%s\n", cache_name); > + return 0; > +} > diff --git a/debuginfod/debuginfod.h b/debuginfod/debuginfod.h > new file mode 100644 > index 000000000000..c268ffebcdb6 > --- /dev/null > +++ b/debuginfod/debuginfod.h > @@ -0,0 +1,69 @@ > +/* External declarations for the libdebuginfod client library. > + Copyright (C) 2019 Red Hat, Inc. > + This file is part of elfutils. > + > + This file is free software; you can redistribute it and/or modify > + it under the terms of either > + > + * the GNU Lesser General Public License as published by the Free > + Software Foundation; either version 3 of the License, or (at > + your option) any later version > + > + or > + > + * 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 > + > + or both in parallel, as here. > + > + elfutils 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 copies of the GNU General Public License and > + the GNU Lesser General Public License along with this program. If > + not, see . */ > + > +#ifndef _LIBBGSERVER_CLIENT_H > +#define _LIBBGSERVER_CLIENT_H 1 Not that it really matters, but should that be _LIBDEBUGINFOD_CLIENT_H? > + > +/* Names of environment variables that control the client logic. */ > +#define DEBUGINFOD_URLS_ENV_VAR "DEBUGINFOD_URLS" > +#define DEBUGINFOD_CACHE_PATH_ENV_VAR "DEBUGINFOD_CACHE_PATH" > +#define DEBUGINFOD_TIMEOUT_ENV_VAR "DEBUGINFOD_TIMEOUT" > + > +#ifdef __cplusplus > +extern "C" { > +#endif You can also use __BEGIN_DECLS > + > +/* Query the urls contained in $DEBUGINFOD_URLS for a file with > + the specified type and build id. If build_id_len =3D=3D 0, the > + build_id is supplied as a lowercase hexadecimal string; otherwise > + it is a binary blob of given legnth. > + > + If successful, return a file descriptor to the target, otherwise > + return a posix error code. If successful, set *path to a > + strdup'd copy of the name of the same file in the cache. > + Caller must free() it later. */ > +=20=20 > +int debuginfod_find_debuginfo (const unsigned char *build_id, > + int build_id_len, > + char **path); > + > +int debuginfod_find_executable (const unsigned char *build_id, > + int build_id_len, > + char **path); > + > +int debuginfod_find_source (const unsigned char *build_id, > + int build_id_len, > + const char *filename, > + char **path); > + > +#ifdef __cplusplus > +} > +#endif and __END_DECLS here? > + > +#endif /* _LIBBGSERVER_CLIENT_H */ _LIBDEBUGINFOD_CLIENT_H here too? > diff --git a/debuginfod/debuginfod_find_debuginfo.3 b/debuginfod/debuginf= od_find_debuginfo.3 > new file mode 100644 > index 000000000000..18caec5576ba > --- /dev/null > +++ b/debuginfod/debuginfod_find_debuginfo.3 > @@ -0,0 +1,173 @@ > +'\"! tbl | nroff \-man > +'\" t macro stdmacro > + > +.de SAMPLE > +.br > +.RS 0 > +.nf > +.nh > +.. > +.de ESAMPLE > +.hy > +.fi > +.RE > +.. > + > +.TH DEBUGINFOD_FIND_DEBUGINFO 3 > +.SH NAME > +debuginfod_find_debuginfo \- request debuginfo from debuginfod > + > +.SH SYNOPSIS > +.nf > +.B #include > +.PP > +.BI "debuginfod_find_debuginfo(const unsigned char *" build_id ", int " = build_id_len ", char ** " path ");" > +.BI "debuginfod_find_executable(const unsigned char *" build_id ", int "= build_id_len ", char ** " path ");" > +.BI "debuginfod_find_source(const unsigned char *" build_id ", int " bui= ld_id_len ", const char *" filename ", char ** " path ");" > + > +.SH DESCRIPTION > +.BR debuginfod_find_debuginfo (), > +.BR debuginfod_find_executable (), > +and > +.BR debuginfod_find_source () > +query the debuginfod server URLs contained in > +.BR $DEBUGINFOD_URLS > +(see below) for the debuginfo, executable or source file with the > +given \fIbuild_id\fP. \fIbuild_id\fP should be a pointer to either > +a null-terminated, lowercase hex string or a binary blob. If > +\fIbuild_id\fP is given as a hex string, \fIbuild_id_len\fP should > +be 0. Otherwise \fIbuild_id_len\fP should be the number of bytes in > +the binary blob. > + > +.BR debuginfod_find_source () > +also requries a \fIfilename\fP in order to specify a particular > +source file. \fIfilename\fP should be an absolute path that includes > +the compilation directory of the CU associated with the source file. > +Relative path names commonly appear in the DWARF file's source directory, > +but these paths are relative to individual compilation unit AT_comp_dir > +paths, and yet an executable is made up of multiple CUs. Therefore, to > +disambiguate, debuginfod expects source queries to prefix relative path > +names with the CU compilation-directory. > + > +If \fIpath\fP is not NULL and the query is successful, \fIpath\fP is set > +to the path of the file in the cache. The caller must \fBfree\fP() this = value. Ah, good, path is mentioned here. Caller owns it. > +The URLs in \fB$DEBUGINFOD_URLS\fP are queried in parallel. As soon as a > +debuginfod server begins transfering the target file all of the connecti= ons > +to the other servers are closed. > + > +.SH "CACHE" > +If the query is successful, the \fBdebuginfod_find_*\fP() functions save > +the target file to a local cache. The location of the cache is controlled > +by the \fB$DEBUGINFOD_CACHE_PATH\fP environment variable (see below). > +Cleaning of the cache is controlled by the \fIcache_clean_interval_s\fP > +and \fImax_unused_age_s\fP files, which are found in the > +\fB$DEBUGINFOD_CACHE_PATH\fP directory. \fIcache_clean_interval_s\fP con= trols > +how frequently the cache is traversed for cleaning and \fImax_unused_age= _s\fP > +controls how long a file can go unused before its removed from the cache > +during cleaning. These files should contain only an ASCII integer > +representing the interval or max unused age in seconds. The files contai= n a > +default value of 600. > + > +.SH "SECURITY" > +.BR debuginfod_find_debuginfo (), > +.BR debuginfod_find_executable (), > +and > +.BR debuginfod_find_source () > +\fBdo not\fP include any particular security > +features. They trust that the binaries returned by the debuginfod(s) > +are accurate. Therefore, the list of servers should include only > +trustworthy ones. If accessed across HTTP rather than HTTPS, the > +network should be trustworthy. > + > +.SH "ENVIRONMENT VARIABLES" > + > +.TP 21 > +.B DEBUGINFOD_URLS > +This environment variable contains a list of URL prefixes for trusted > +debuginfod instances. Alternate URL prefixes are separated by space. > + > +.TP 21 > +.B DEBUGINFOD_TIMEOUT > +This environment variable governs the timeout for each debuginfod HTTP > +connection. A server that fails to respond within this many seconds > +is skipped. The default is 5. zero isn't allowed? > +.TP 21 > +.B DEBUGINFOD_CACHE_PATH > +This environment variable governs the location of the cache where > +downloaded files are kept. It is cleaned periodically as this > +program is reexecuted. The default is $HOME/.debuginfod_client_cache. > + > +.SH "RETURN VALUE" > +If the query is successful, these functions save the target file > +to the client cache and return a file descriptor to that file. > +Otherwise an error code is returned. > + > +.SH "ERRORS" > +The following list is not comprehensive. Error codes may also > +originate from calls to various C Library functions. > + > +.TP > +.BR EACCESS > +Denied access to resource located at the URL. > + > +.TP > +.BR ECONNREFUSED > +Unable to connect to remote host. > + > +.TP > +.BR ECONNRESET > +Unable to either send or recieve network data. > + > +.TP > +.BR EHOSTUNREACH > +Unable to resolve remote host. > + > +.TP > +.BR EINVAL > +One or more arguments are incorrectly formatted. \fIbuild_id\fP may > +be too long (greater than 256 characters), \fIfilename\fP may not > +be an absolute path or a debuginfod URL is malformed. > + > +.TP > +.BR EIO > +Unable to write data received from server to local file. > + > +.TP > +.BR EMLINK > +Too many HTTP redirects. > + > +.TP > +.BR ENETUNREACH > +Unable to initialize network connection. > + > +.TP > +.BR ENOENT > +Could not find the resource located at URL. Often this error code > +indicates that a debuginfod server was successfully contacted but > +the server could not find the target file. > + > +.TP > +.BR ENOMEM > +System is unable to allocate resources. > + > +.TP > +.BR ENOSYS > +\fB$DEBUGINFOD_URLS\fP is not defined. > + > +.TP > +.BR ETIME > +Query failed due to timeout. \fB$DEBUGINFOD_TIMEOUT\fP controls > +the timeout duration. See debuginfod(8) for more information. > + > +.SH "FILES" > +.LP > +.PD .1v > +.TP 20 > +.B $HOME/.debuginfod_client_cache > +Default cache directory. > +.PD > + > +.SH "SEE ALSO" > +.I "debuginfod(8)" > diff --git a/debuginfod/debuginfod_find_executable.3 b/debuginfod/debugin= fod_find_executable.3 > new file mode 100644 > index 000000000000..16279936e2ea > --- /dev/null > +++ b/debuginfod/debuginfod_find_executable.3 > @@ -0,0 +1 @@ > +.so man3/debuginfod_find_debuginfo.3 > diff --git a/debuginfod/debuginfod_find_source.3 b/debuginfod/debuginfod_= find_source.3 > new file mode 100644 > index 000000000000..16279936e2ea > --- /dev/null > +++ b/debuginfod/debuginfod_find_source.3 > @@ -0,0 +1 @@ > +.so man3/debuginfod_find_debuginfo.3 So these all look good, but like I said above, I think they should move into the doc/ subdir. > diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map > new file mode 100644 > index 000000000000..50d6fbaec639 > --- /dev/null > +++ b/debuginfod/libdebuginfod.map > @@ -0,0 +1,7 @@ > +ELFUTILS_0 { }; > +ELFUTILS_0.177 { > + global: > + debuginfod_find_debuginfo; > + debuginfod_find_executable; > + debuginfod_find_source; > +} ELFUTILS_0; Make that ELFUTILS_0.178 since that is the first version where they are added. Thanks, Mark