Hello,

Attached is a patch that extends the functionality of Coreutils install creating a basic package management capability. If it is to be considered useful or viable, I would like very much to receive reveiews, suggestions for improvements or bug reports. My purpose is to get the code integrated to Coreutils 'install'.

Thank you,
Joao Luis.

** RATIONALE **

When installing programs from upstream source code, 'install' is the traditional way makefiles install files to /usr/local or other locations. After that, it is difficult to get a full listing of what was installed where, and usually one must keep the source code so it is possible to 'make uninstall' in the future, in case the makefile provides this possibility.

This patch allows 'install' to make a list of all installed files and created directories, making it easy to track all files and uninstall the "package".

** USAGE **

With this patch, when 'install' is executed with the command line options

-l FILE

or

--list=FILE

it will append to FILE the canonicalized name of the installed file or directory.

For example

install -l file_list -D my_file /usr/local/my_dir/my_file

or

install --list=file_list -D my_file /usr/local/my_dir/my_file

or

export INSTALL_LIST=file_list
install -D my_file /usr/local/my_dir/my_file

will append to file_list (or create it if it doesn't exist) containing

/usr/local/my_dir
/usr/local/my_dir/my_file

After that, files and directories may be easily uninstalled with

tac file_list | while read f; do test -d "$f" && \rmdir "$f" || \rm "$f"; done; \rm file_list

** USE CASES **

Install may be called in new makefiles with the option -l to generate a list of installed files and direcory. For legacy makefiles, one can execute

INSTALL_LIST=somefile make install

or

export INSTALL_LIST=somefile
make install

or the like.

** DETAILS **

A global variable

static FILE *install_list = NULL;

was created to hold the stream for the file list. This stream is fopen()ed for append in main() and closed by the atexit() handler close_install_list().

A function

static void install_list_append (const char *fname)

was created to write to the file list. This function is called by:

make_ancestor(), to record all the created parent directories (case of option -D)

process_dir(), to record a created dir (case of option -d)

install_file_in_file(), to record installed files.


--- src/install.c.ORIG	2016-07-28 15:01:31.115348097 -0300
+++ src/install.c	2016-07-28 20:52:18.193822263 -0300
@@ -18,6 +18,7 @@
 
 #include <config.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <getopt.h>
 #include <sys/types.h>
 #include <signal.h>
@@ -28,6 +29,7 @@
 
 #include "system.h"
 #include "backupfile.h"
+#include "canonicalize.h"
 #include "error.h"
 #include "cp-hash.h"
 #include "copy.h"
@@ -109,6 +111,9 @@
 /* Program used to strip binaries, "strip" is default */
 static char const *strip_program = "strip";
 
+/* Stream to record installed files and directories */
+static FILE *install_list = NULL;
+
 /* For long options that have no equivalent short option, use a
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
@@ -124,6 +129,7 @@
   {GETOPT_SELINUX_CONTEXT_OPTION_DECL},
   {"directory", no_argument, NULL, 'd'},
   {"group", required_argument, NULL, 'g'},
+  {"list", required_argument, NULL, 'l'},
   {"mode", required_argument, NULL, 'm'},
   {"no-target-directory", no_argument, NULL, 'T'},
   {"owner", required_argument, NULL, 'o'},
@@ -409,6 +415,20 @@
   return is_a_dir;
 }
 
+/* Append fname to the install list */
+
+static void
+install_list_append (const char *fname)
+{
+  char *fname_canon;
+  if (install_list == NULL)
+    return;
+  fname_canon = canonicalize_file_name (fname);
+  if (fprintf (install_list, "%s\n", fname_canon) < 0)
+    error (EXIT_FAILURE, errno, _("cannot write to install list"));
+  free (fname_canon);
+}
+
 /* Report that directory DIR was made, if OPTIONS requests this.  */
 static void
 announce_mkdir (char const *dir, void *options)
@@ -432,7 +452,10 @@
 
   int r = mkdir (component, DEFAULT_MODE);
   if (r == 0)
-    announce_mkdir (dir, options);
+    {
+      install_list_append (component);
+      announce_mkdir (dir, options);
+    }
   return r;
 }
 
@@ -448,6 +471,10 @@
           ? EXIT_SUCCESS
           : EXIT_FAILURE);
 
+  /* After patch is reviewed and accepted, integrate with if below */
+  if (ret == EXIT_SUCCESS)
+    install_list_append (dir);
+
   /* FIXME: Due to the current structure of make_dir_parents()
      we don't have the facility to call defaultcon() before the
      final component of DIR is created.  So for now, create the
@@ -658,6 +685,9 @@
                         or all components of --target-directory,\n\
                         then copy SOURCE to DEST\n\
   -g, --group=GROUP   set group ownership, instead of process' current group\n\
+  -l, --list=FILE     append to FILE the installed file or directory names;\n\
+                        FILE may be passed also through the environment\n\
+                        variable INSTALL_LIST\n\
   -m, --mode=MODE     set permission mode (as in chmod), instead of rwxr-xr-x\n\
   -o, --owner=OWNER   set ownership (super-user only)\n\
 "), stdout);
@@ -715,6 +745,7 @@
     }
   if (! copy_file (from, to, x))
     return false;
+  install_list_append (to);
   if (strip_files)
     if (! strip (to))
       {
@@ -800,6 +831,12 @@
   return ret;
 }
 
+static void close_install_list (void)
+{
+  if (install_list && fclose (install_list) == EOF)
+    error (0, errno, _("cannot write to install list"));
+}
+
 int
 main (int argc, char **argv)
 {
@@ -817,6 +854,7 @@
   char **file;
   bool strip_program_specified = false;
   char const *scontext = NULL;
+  char *install_list_fname = NULL;
   /* set iff kernel has extra selinux system calls */
   selinux_enabled = (0 < is_selinux_enabled ());
 
@@ -827,6 +865,7 @@
   textdomain (PACKAGE);
 
   atexit (close_stdin);
+  atexit (close_install_list);
 
   cp_option_init (&x);
 
@@ -840,8 +879,8 @@
      we'll actually use backup_suffix_string.  */
   backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
 
-  while ((optc = getopt_long (argc, argv, "bcCsDdg:m:o:pt:TvS:Z", long_options,
-                              NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "bcCsDdg:l:m:o:pt:TvS:Z",
+			      long_options, NULL)) != -1)
     {
       switch (optc)
         {
@@ -878,6 +917,9 @@
         case 'g':
           group_name = optarg;
           break;
+	case 'l':
+	  install_list_fname = optarg;
+	  break;
         case 'm':
           specified_mode = optarg;
           break;
@@ -1012,6 +1054,13 @@
                quoteaf (file[n_files - 1]));
     }
 
+  if (install_list_fname || (install_list_fname = getenv ("INSTALL_LIST")))
+    {
+      install_list = fopen (install_list_fname, "a");
+      if (install_list == NULL)
+	error (EXIT_FAILURE, errno, _("failed to open %s"), install_list_fname);
+    }
+
   if (specified_mode)
     {
       struct mode_change *change = mode_compile (specified_mode);

Reply via email to