]> git.ipfire.org Git - thirdparty/elfutils.git/blobdiff - src/srcfiles.cxx
PR 30991: srcfiles tarball feature
[thirdparty/elfutils.git] / src / srcfiles.cxx
index 3c7afdc4d038d28b94f348d6e197bf4e753825f3..892737ccccfb943d1b6e0a66240979032cd021b5 100644 (file)
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
+
+/* In case we have a bad fts we include this before config.h because it
+   can't handle _FILE_OFFSET_BITS.
+   Everything we need here is fine if its declarations just come first.
+   Also, include sys/types.h before fts.  On some systems fts.h is not self
+   contained.  */
+#ifdef BAD_FTS
+#include <sys/types.h>
+#include <fts.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
 #include "printversion.h"
 #include <dwarf.h>
 #include <argp.h>
 #include <set>
 #include <string>
 #include <cassert>
-#include <config.h>
+#include <gelf.h>
+#include <memory>
+
+#ifdef ENABLE_LIBDEBUGINFOD
+#include "debuginfod.h"
+#endif
 
 #include <libdwfl.h>
 #include <fcntl.h>
 #include <iostream>
 #include <libdw.h>
+#include <sstream>
+#include <vector>
+
+/* Libraries for use by the --zip option */
+#ifdef HAVE_LIBARCHIVE
+#include <archive.h>
+#include <archive_entry.h>
+#endif
+
+/* If fts.h is included before config.h, its indirect inclusions may not
+   give us the right LFS aliases of these functions, so map them manually.  */
+#ifdef BAD_FTS
+#ifdef _FILE_OFFSET_BITS
+#define open open64
+#define fopen fopen64
+#endif
+#else
+  #include <sys/types.h>
+  #include <fts.h>
+#endif
 
 using namespace std;
 
@@ -38,6 +78,8 @@ ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
 /* Bug report address.  */
 ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
 
+constexpr size_t BUFFER_SIZE = 8192;
+
 /* Definitions of arguments for argp functions.  */
 static const struct argp_option options[] =
 {
@@ -46,12 +88,21 @@ static const struct argp_option options[] =
     N_ ("Separate items by a null instead of a newline."), 0 },
   { "verbose", 'v', NULL, 0,
     N_ ("Increase verbosity of logging messages."), 0 },
-  { "cu-only", 'c', NULL, 0, N_ ("Only list the CU names."), 0 },
+  { "cu-only", 'c', NULL, 0, N_("Only list the CU names."), 0 },
+  #ifdef HAVE_LIBARCHIVE
+  { "zip", 'z', NULL, 0, N_("Zip all the source files and send to stdout. "
+    "Cannot be used with the null option"), 0 },
+    #ifdef ENABLE_LIBDEBUGINFOD
+    { "no-backup", 'b', NULL, 0, N_("Disables local source file search when "
+      "debuginfod fails to fetch files. This option is only applicable"
+      "when fetching and zipping files."), 0 },
+    #endif
+  #endif
   { NULL, 0, NULL, 0, NULL, 0 }
 };
 
 /* Short description of program.  */
-static const char doc[] = N_("Lists the source files of a DWARF/ELF file. The default input is the file 'a.out'.");
+static const char doc[] = N_("Lists the source files of a DWARF/ELF file.  The default input is the file 'a.out'.");
 
 /* Strings for arguments in help texts.  */
 static const char args_doc[] = N_("INPUT");
@@ -67,18 +118,29 @@ static const struct argp argp =
   options, parse_opt, args_doc, doc, argp_children, NULL, NULL
 };
 
-/* Verbose message printing. */
+/* Verbose message printing.  */
 static bool verbose;
-/* Delimit the output with nulls. */
+/* Delimit the output with nulls.  */
 static bool null_arg;
-/* Only print compilation unit names. */
+/* Only print compilation unit names.  */
 static bool CU_only;
-
-/* Handle program arguments. */
+#ifdef HAVE_LIBARCHIVE
+  /* Zip all the source files and send to stdout. */
+  static bool zip;
+
+  #ifdef ENABLE_LIBDEBUGINFOD
+    /* Disables local source file search when debuginfod fails to fetch them.
+       This option is only applicable when fetching and zipping files.*/
+    static bool no_backup;
+  #endif
+#endif
+
+/* Handle program arguments.  Note null arg and zip
+    cannot be combined due to warnings raised when unzipping.  */
 static error_t
 parse_opt (int key, char *arg, struct argp_state *state)
 {
-  /* Suppress "unused parameter" warning. */
+  /* Suppress "unused parameter" warning.  */
   (void)arg;
   switch (key)
     {
@@ -98,17 +160,58 @@ parse_opt (int key, char *arg, struct argp_state *state)
       CU_only = true;
       break;
 
+    #ifdef HAVE_LIBARCHIVE
+      case 'z':
+      zip = true;
+      break;
+
+      #ifdef ENABLE_LIBDEBUGINFOD
+        case 'b':
+        no_backup = true;
+        break;
+      #endif
+    #endif
+
     default:
       return ARGP_ERR_UNKNOWN;
     }
   return 0;
 }
 
+/* Remove the "/./" , "../" and the preceding directory
+    that some paths include which raise errors during unzip.  */
+string canonicalize_path(string path)
+{
+    stringstream ss(path);
+    string token;
+    vector<string> tokens;
+    /* Extract each directory of the path and place into a vector.  */
+    while (getline(ss, token, '/')) {
+      /* Ignore any empty //, or /./ dirs.  */
+        if (token == "" || token == ".")
+            continue;
+      /* When /..  is encountered, remove the most recent directory from the vector.  */
+        else if (token == "..") {
+            if (!tokens.empty())
+                tokens.pop_back();
+        } else
+            tokens.push_back(token);
+    }
+    stringstream result;
+    if (tokens.empty())
+        return "/";
+    /* Reconstruct the path from the extracted directories.  */
+    for (const string &t : tokens) {
+        result << '/' << t;
+    }
+    return result.str();
+}
 
-/* Global list of collected source files.  Normally, it'll contain
-   the sources of just one named binary, but the '-K' option can cause
-   multiple dwfl modules to be loaded, thus listed.   */
-   set<string> debug_sourcefiles;
+/* Global list of collected source files and their respective module.
+   Normally, it'll contain the sources of just one named binary, but
+   the '-K' option can cause multiple dwfl modules to be loaded, thus
+   listed.  */
+set<pair<string, Dwfl_Module*>> debug_sourcefiles;
 
 static int
 collect_sourcefiles (Dwfl_Module *dwflmod,
@@ -118,14 +221,13 @@ collect_sourcefiles (Dwfl_Module *dwflmod,
                      void *arg __attribute__ ((unused)))
 {
   Dwarf *dbg;
-  Dwarf_Addr bias; /* ignored - for addressing purposes only  */
+  Dwarf_Addr bias; /* ignored - for addressing purposes only.  */
 
   dbg = dwfl_module_getdwarf (dwflmod, &bias);
 
   Dwarf_Off offset = 0;
   Dwarf_Off old_offset;
   size_t hsize;
-
   /* Traverse all CUs of this module.  */
   while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
     {
@@ -141,7 +243,7 @@ collect_sourcefiles (Dwfl_Module *dwflmod,
       if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
         continue;
 
-      /* extract DW_AT_comp_dir to resolve relative file names  */
+      /* extract DW_AT_comp_dir to resolve relative file names.  */
       const char *comp_dir = "";
       const char *const *dirs;
       size_t ndirs;
@@ -152,11 +254,19 @@ collect_sourcefiles (Dwfl_Module *dwflmod,
         comp_dir = "";
 
       if (verbose)
-        std::clog << "searching for sources for cu=" << cuname
+        clog << "searching for sources for cu=" << cuname
                   << " comp_dir=" << comp_dir << " #files=" << nfiles
                   << " #dirs=" << ndirs << endl;
 
-      for (size_t f = 1; f < nfiles; f++)
+      if (comp_dir[0] == '\0' && cuname[0] != '/')
+        {
+          /* This is a common symptom for dwz-compressed debug files,
+             where the altdebug file cannot be resolved.  */
+          if (verbose)
+            clog << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
+          continue;
+        }
+      for (size_t f = 1; f < nfiles; ++f)
         {
           const char *hat;
           if (CU_only)
@@ -172,7 +282,7 @@ collect_sourcefiles (Dwfl_Module *dwflmod,
             continue;
 
           if (string(hat).find("<built-in>")
-              != std::string::npos) /* gcc intrinsics, don't bother record  */
+              != string::npos) /* gcc intrinsics, don't bother recording */
             continue;
 
           string waldo;
@@ -180,13 +290,133 @@ collect_sourcefiles (Dwfl_Module *dwflmod,
             waldo = (string (hat));
           else if (comp_dir[0] != '\0') /* comp_dir relative */
             waldo = (string (comp_dir) + string ("/") + string (hat));
-          debug_sourcefiles.insert (waldo);
+          else
+           {
+             if (verbose)
+              clog << "skipping file=" << hat << " due to empty comp_dir" << endl;
+             continue;
+           }
+          waldo = canonicalize_path (waldo);
+          debug_sourcefiles.insert (make_pair(waldo, dwflmod));
         }
     }
-
   return DWARF_CB_OK;
 }
 
+#ifdef HAVE_LIBARCHIVE
+void zip_files()
+{
+  struct archive *a = archive_write_new();
+  struct stat st;
+  char buff[BUFFER_SIZE];
+  int len;
+  int fd;
+  #ifdef ENABLE_LIBDEBUGINFOD
+  /* Initialize a debuginfod client.  */
+  static unique_ptr <debuginfod_client, void (*)(debuginfod_client*)>
+    client (debuginfod_begin(), &debuginfod_end);
+  #endif
+
+  archive_write_set_format_zip(a);
+  archive_write_open_fd(a, STDOUT_FILENO);
+
+  int missing_files = 0;
+  for (const auto &pair : debug_sourcefiles)
+  {
+    fd = -1;
+    const std::string &file_path = pair.first;
+
+    /* Attempt to query debuginfod client to fetch source files.  */
+    #ifdef ENABLE_LIBDEBUGINFOD
+    Dwfl_Module* dwflmod = pair.second;
+    /* Obtain source file's build ID.  */
+    const unsigned char *bits;
+    GElf_Addr vaddr;
+    int bits_length = dwfl_module_build_id(dwflmod, &bits, &vaddr);
+    /* Ensure successful client and build ID acquisition.  */
+    if (client.get() != NULL && bits_length > 0)
+    {
+      fd = debuginfod_find_source(client.get(),
+                                    bits, bits_length,
+                                    file_path.c_str(), NULL);
+    }
+    else
+    {
+        if (client.get() == NULL)
+            cerr << "Error: Failed to initialize debuginfod client." << endl;
+        else
+            cerr << "Error: Invalid build ID length (" << bits_length << ")." << endl;
+    }
+    #endif
+
+    if (!no_backup)
+      /* Files could not be located using debuginfod, search locally */
+      if (fd < 0)
+        fd = open(file_path.c_str(), O_RDONLY);
+    if (fd < 0)
+    {
+      if (verbose)
+        cerr << file_path << endl;
+      missing_files++;
+      continue;
+    }
+
+    /* Create an entry for each file including file information to be placed in the zip.  */
+    if (fstat(fd, &st) == -1)
+    {
+      if (verbose)
+        cerr << file_path << endl;
+      missing_files++;
+      if (verbose)
+        cerr << "Error: Failed to get file status for " << file_path << ": " << strerror(errno) << endl;
+      continue;
+    }
+    struct archive_entry *entry = archive_entry_new();
+    /* Removing first "/"" to make the path "relative" before zipping, otherwise warnings are raised when unzipping.  */
+    string entry_name = file_path.substr(file_path.find_first_of('/') + 1);
+    archive_entry_set_pathname(entry, entry_name.c_str());
+    archive_entry_copy_stat(entry, &st);
+    if (archive_write_header(a, entry) != ARCHIVE_OK)
+    {
+      if (verbose)
+        cerr << file_path << endl;
+      missing_files++;
+      if (verbose)
+        cerr << "Error: failed to write header for " << file_path << ": " << archive_error_string(a) << endl;
+      continue;
+    }
+
+    /* Write the file to the zip.  */
+    len = read(fd, buff, sizeof(buff));
+    if (len == -1)
+    {
+      if (verbose)
+        cerr << file_path << endl;
+      missing_files++;
+      if (verbose)
+        cerr << "Error: Failed to open file: " << file_path << ": " << strerror(errno) <<endl;
+      continue;
+    }
+    while (len > 0)
+    {
+      if (archive_write_data(a, buff, len) < ARCHIVE_OK)
+      {
+        if (verbose)
+          cerr << "Error: Failed to read from the file: " << file_path << ": " << strerror(errno) << endl;
+        break;
+      }
+      len = read(fd, buff, sizeof(buff));
+    }
+    close(fd);
+    archive_entry_free(entry);
+  }
+  if (verbose && missing_files > 0 )
+    cerr << missing_files << " file(s) listed above could not be found.  " << endl;
+
+  archive_write_close(a);
+  archive_write_free(a);
+}
+#endif
 
 int
 main (int argc, char *argv[])
@@ -200,18 +430,27 @@ main (int argc, char *argv[])
   Dwfl *dwfl = NULL;
   (void) argp_parse (&argp, argc, argv, 0, &remaining, &dwfl);
   assert (dwfl != NULL);
-  /* Process all loaded modules - probably just one, except if -K or -p is used. */
+  /* Process all loaded modules - probably just one, except if -K or -p is used.  */
   (void) dwfl_getmodules (dwfl, &collect_sourcefiles, NULL, 0);
 
   if (!debug_sourcefiles.empty ())
-    for (const string &element : debug_sourcefiles)
+  {
+    #ifdef HAVE_LIBARCHIVE
+      if (zip)
+        zip_files();
+      else
+    #endif
       {
-        std::cout << element;
-        if (null_arg)
-          std::cout << '\0';
-        else
-          std::cout << '\n';
+        for (const auto &pair : debug_sourcefiles)
+          {
+            cout << pair.first;
+            if (null_arg)
+              cout << '\0';
+            else
+              cout << '\n';
+          }
       }
+  }
 
   dwfl_end (dwfl);
   return 0;