* tests/cp/cp-a-selinux: New file. Test for the bug reported in
[platform/upstream/coreutils.git] / src / install.c
index aabbafa..dc7ff01 100644 (file)
@@ -1,5 +1,5 @@
 /* install - copy files and set attributes
-   Copyright (C) 89, 90, 91, 1995-2006 Free Software Foundation, Inc.
+   Copyright (C) 89, 90, 91, 1995-2007 Free Software Foundation, Inc.
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@
 #include <signal.h>
 #include <pwd.h>
 #include <grp.h>
+#include <selinux/selinux.h>
 
 #include "system.h"
 #include "backupfile.h"
@@ -35,6 +36,7 @@
 #include "mkdir-p.h"
 #include "modechange.h"
 #include "quote.h"
+#include "quotearg.h"
 #include "savewd.h"
 #include "stat-time.h"
 #include "utimens.h"
@@ -49,6 +51,9 @@
 # include <sys/wait.h>
 #endif
 
+static int selinux_enabled = 0;
+static bool use_default_selinux_context = true;
+
 #if ! HAVE_ENDGRENT
 # define endgrent() ((void) 0)
 #endif
 /* Number of bytes of a file to copy at a time. */
 #define READ_SIZE (32 * 1024)
 
-/* Options passed to subsidiary functions.  */
-struct install_options
-{
-  /* Full name of file being installed.  */
-  char const *full_name;
-
-  /* Options for cp-related code.  */
-  struct cp_options cp;
-};
-
 static bool change_timestamps (struct stat const *from_sb, char const *to);
 static bool change_attributes (char const *name);
 static bool copy_file (const char *from, const char *to,
                       const struct cp_options *x);
 static bool install_file_in_file_parents (char const *from, char *to,
-                                         struct install_options *x);
+                                         struct cp_options *x);
 static bool install_file_in_dir (const char *from, const char *to_dir,
                                 const struct cp_options *x);
 static bool install_file_in_file (const char *from, const char *to,
@@ -89,7 +84,8 @@ static bool install_file_in_file (const char *from, const char *to,
 static void get_ids (void);
 static void strip (char const *name);
 static void announce_mkdir (char const *dir, void *options);
-static int make_ancestor (char const *dir, void *options);
+static int make_ancestor (char const *dir, char const *component,
+                         void *options);
 void usage (int status);
 
 /* The name this program was run with, for error messages. */
@@ -130,15 +126,28 @@ static bool strip_files;
 /* If true, install a directory instead of a regular file. */
 static bool dir_arg;
 
+/* For long options that have no equivalent short option, use a
+   non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
+enum
+{
+  PRESERVE_CONTEXT_OPTION = CHAR_MAX + 1
+};
+
 static struct option const long_options[] =
 {
   {"backup", optional_argument, NULL, 'b'},
+  {GETOPT_SELINUX_CONTEXT_OPTION_DECL},
   {"directory", no_argument, NULL, 'd'},
   {"group", required_argument, NULL, 'g'},
   {"mode", required_argument, NULL, 'm'},
   {"no-target-directory", no_argument, NULL, 'T'},
   {"owner", required_argument, NULL, 'o'},
   {"preserve-timestamps", no_argument, NULL, 'p'},
+  {"preserve-context", no_argument, NULL, PRESERVE_CONTEXT_OPTION},
+  /* Continue silent support for --preserve_context until Jan 2008. FIXME-obs
+     After that, FIXME-obs: warn in, say, late 2008, and disable altogether
+     a year or two later.  */
+  {"preserve_context", no_argument, NULL, PRESERVE_CONTEXT_OPTION},
   {"strip", no_argument, NULL, 's'},
   {"suffix", required_argument, NULL, 'S'},
   {"target-directory", required_argument, NULL, 't'},
@@ -165,6 +174,7 @@ cp_option_init (struct cp_options *x)
   x->preserve_mode = false;
   x->preserve_timestamps = false;
   x->require_preserve = false;
+  x->require_preserve_context = false;
   x->recursive = false;
   x->sparse_mode = SPARSE_AUTO;
   x->symbolic_link = false;
@@ -178,11 +188,47 @@ cp_option_init (struct cp_options *x)
   x->stdin_tty = false;
 
   x->update = false;
+  x->preserve_security_context = false;
   x->verbose = false;
   x->dest_info = NULL;
   x->src_info = NULL;
 }
 
+/* Modify file context to match the specified policy.
+   If an error occurs the file will remain with the default directory
+   context.  */
+static void
+setdefaultfilecon (char const *file)
+{
+  struct stat st;
+  security_context_t scontext = NULL;
+  if (selinux_enabled != 1)
+    {
+      /* Indicate no context found. */
+      return;
+    }
+  if (lstat (file, &st) != 0)
+    return;
+
+  /* If there's an error determining the context, or it has none,
+     return to allow default context */
+  if ((matchpathcon (file, st.st_mode, &scontext) != 0) ||
+      (strcmp (scontext, "<<none>>") == 0))
+    {
+      if (scontext != NULL)
+       freecon (scontext);
+      return;
+    }
+
+  if (lsetfilecon (file, scontext) < 0 && errno != ENOTSUP)
+    error (0, errno,
+          _("warning: %s: failed to change context to %s"),
+          quotearg_colon (file), scontext);
+
+  freecon (scontext);
+  return;
+}
+
 /* FILE is the last operand of this command.  Return true if FILE is a
    directory.  But report an error there is a problem accessing FILE,
    or if FILE does not exist but would have to refer to an existing
@@ -208,8 +254,6 @@ target_directory_operand (char const *file)
 static int
 process_dir (char *dir, struct savewd *wd, void *options)
 {
-  struct install_options *o = options;
-  o->full_name = dir;
   return (make_dir_parents (dir, wd,
                            make_ancestor, options,
                            dir_mode, announce_mkdir,
@@ -228,11 +272,14 @@ main (int argc, char **argv)
   char *backup_suffix_string;
   char *version_control_string = NULL;
   bool mkdir_and_install = false;
-  struct install_options x;
+  struct cp_options x;
   char const *target_directory = NULL;
   bool no_target_directory = false;
   int n_files;
   char **file;
+  security_context_t scontext = NULL;
+  /* set iff kernel has extra selinux system calls */
+  selinux_enabled = (0 < is_selinux_enabled ());
 
   initialize_main (&argc, &argv);
   program_name = argv[0];
@@ -242,7 +289,7 @@ main (int argc, char **argv)
 
   atexit (close_stdout);
 
-  cp_option_init (&x.cp);
+  cp_option_init (&x);
 
   owner_name = NULL;
   group_name = NULL;
@@ -254,7 +301,7 @@ main (int argc, char **argv)
      we'll actually use backup_suffix_string.  */
   backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
 
-  while ((optc = getopt_long (argc, argv, "bcsDdg:m:o:pt:TvS:", long_options,
+  while ((optc = getopt_long (argc, argv, "bcsDdg:m:o:pt:TvS:Z:", long_options,
                              NULL)) != -1)
     {
       switch (optc)
@@ -280,7 +327,7 @@ main (int argc, char **argv)
          mkdir_and_install = true;
          break;
        case 'v':
-         x.cp.verbose = true;
+         x.verbose = true;
          break;
        case 'g':
          group_name = optarg;
@@ -292,7 +339,7 @@ main (int argc, char **argv)
          owner_name = optarg;
          break;
        case 'p':
-         x.cp.preserve_timestamps = true;
+         x.preserve_timestamps = true;
          break;
        case 'S':
          make_backups = true;
@@ -316,6 +363,27 @@ main (int argc, char **argv)
        case 'T':
          no_target_directory = true;
          break;
+
+       case PRESERVE_CONTEXT_OPTION:
+         if ( ! selinux_enabled)
+           {
+             error (0, 0, _("Warning: ignoring --preserve-context; "
+                            "this kernel is not SELinux-enabled."));
+             break;
+           }
+         x.preserve_security_context = true;
+         use_default_selinux_context = false;
+         break;
+       case 'Z':
+         if ( ! selinux_enabled)
+           {
+             error (0, 0, _("Warning: ignoring --context (-Z); "
+                            "this kernel is not SELinux-enabled."));
+             break;
+           }
+         scontext = optarg;
+         use_default_selinux_context = false;
+         break;
        case_GETOPT_HELP_CHAR;
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
        default:
@@ -331,13 +399,23 @@ main (int argc, char **argv)
     error (EXIT_FAILURE, 0,
           _("target directory not allowed when installing a directory"));
 
+  if (x.preserve_security_context && scontext != NULL)
+    error (EXIT_FAILURE, 0,
+          _("cannot force target context to %s and preserve it"),
+          quote (scontext));
+
   if (backup_suffix_string)
     simple_backup_suffix = xstrdup (backup_suffix_string);
 
-  x.cp.backup_type = (make_backups
-                     ? xget_version (_("backup type"),
-                                     version_control_string)
-                     : no_backups);
+  x.backup_type = (make_backups
+                  ? xget_version (_("backup type"),
+                                  version_control_string)
+                  : no_backups);
+
+  if (scontext && setfscreatecon (scontext) < 0)
+    error (EXIT_FAILURE, errno,
+          _("failed to set default file creation context to %s"),
+          quote (scontext));
 
   n_files = argc - optind;
   file = argv + optind;
@@ -397,15 +475,15 @@ main (int argc, char **argv)
         {
           if (! (mkdir_and_install
                 ? install_file_in_file_parents (file[0], file[1], &x)
-                : install_file_in_file (file[0], file[1], &x.cp)))
+                : install_file_in_file (file[0], file[1], &x)))
            exit_status = EXIT_FAILURE;
        }
       else
        {
          int i;
-         dest_info_init (&x.cp);
+         dest_info_init (&x);
          for (i = 0; i < n_files; i++)
-           if (! install_file_in_dir (file[i], target_directory, &x.cp))
+           if (! install_file_in_dir (file[i], target_directory, &x))
              exit_status = EXIT_FAILURE;
        }
     }
@@ -418,7 +496,7 @@ main (int argc, char **argv)
 
 static bool
 install_file_in_file_parents (char const *from, char *to,
-                             struct install_options *x)
+                             struct cp_options *x)
 {
   bool save_working_directory =
     ! (IS_ABSOLUTE_FILE_NAME (from) && IS_ABSOLUTE_FILE_NAME (to));
@@ -449,7 +527,7 @@ install_file_in_file_parents (char const *from, char *to,
        }
     }
 
-  return (status == EXIT_SUCCESS && install_file_in_file (from, to, &x->cp));
+  return (status == EXIT_SUCCESS && install_file_in_file (from, to, x));
 }
 
 /* Copy file FROM onto file TO and give TO the appropriate
@@ -470,11 +548,10 @@ install_file_in_file (const char *from, const char *to,
     return false;
   if (strip_files)
     strip (to);
-  if (! change_attributes (to))
+  if (x->preserve_timestamps && (strip_files || ! S_ISREG (from_sb.st_mode))
+      && ! change_timestamps (&from_sb, to))
     return false;
-  if (x->preserve_timestamps && (strip_files || ! S_ISREG (from_sb.st_mode)))
-    return change_timestamps (&from_sb, to);
-  return true;
+  return change_attributes (to);
 }
 
 /* Copy file FROM into directory TO_DIR, keeping its same name,
@@ -515,6 +592,7 @@ copy_file (const char *from, const char *to, const struct cp_options *x)
 static bool
 change_attributes (char const *name)
 {
+  bool ok = false;
   /* chown must precede chmod because on some systems,
      chown clears the set[ug]id bits for non-superusers,
      resulting in incorrect permissions.
@@ -533,9 +611,12 @@ change_attributes (char const *name)
   else if (chmod (name, mode) != 0)
     error (0, errno, _("cannot change permissions of %s"), quote (name));
   else
-    return true;
+    ok = true;
 
-  return false;
+  if (use_default_selinux_context)
+    setdefaultfilecon (name);
+
+  return ok;
 }
 
 /* Set the timestamps of file TO to match those of file FROM.
@@ -578,11 +659,10 @@ strip (char const *name)
       error (EXIT_FAILURE, errno, _("cannot run strip"));
       break;
     default:                   /* Parent. */
-      /* Parent process. */
-      while (pid != wait (&status))    /* Wait for kid to finish. */
-       /* Do nothing. */ ;
-      if (status)
-       error (EXIT_FAILURE, 0, _("strip failed"));
+      if (waitpid (pid, &status, 0) < 0)
+       error (EXIT_FAILURE, errno, _("waiting for strip"));
+      else if (! WIFEXITED (status) || WEXITSTATUS (status))
+       error (EXIT_FAILURE, 0, _("strip process terminated abnormally"));
       break;
     }
 }
@@ -636,19 +716,20 @@ get_ids (void)
 static void
 announce_mkdir (char const *dir, void *options)
 {
-  struct install_options const *x = options;
-  if (x->cp.verbose)
+  struct cp_options const *x = options;
+  if (x->verbose)
     error (0, 0, _("creating directory %s"), quote (dir));
 }
 
-/* Make ancestor directory DIR, with options OPTIONS.  */
+/* Make ancestor directory DIR, whose last file name component is
+   COMPONENT, with options OPTIONS.  Assume the working directory is
+   COMPONENT's parent.  */
 static int
-make_ancestor (char const *dir, void *options)
+make_ancestor (char const *dir, char const *component, void *options)
 {
-  struct install_options const *x = options;
-  int r = mkdir (dir, DEFAULT_MODE);
+  int r = mkdir (component, DEFAULT_MODE);
   if (r == 0)
-    announce_mkdir (x->full_name, options);
+    announce_mkdir (dir, options);
   return r;
 }
 
@@ -694,11 +775,16 @@ Mandatory arguments to long options are mandatory for short options too.\n\
   -p, --preserve-timestamps   apply access/modification times of SOURCE files\n\
                         to corresponding destination files\n\
   -s, --strip         strip symbol tables\n\
-  -S, --suffix=SUFFIX override the usual backup suffix\n\
+  -S, --suffix=SUFFIX  override the usual backup suffix\n\
   -t, --target-directory=DIRECTORY  copy all SOURCE arguments into DIRECTORY\n\
   -T, --no-target-directory  treat DEST as a normal file\n\
   -v, --verbose       print the name of each directory as it is created\n\
 "), stdout);
+      fputs (_("\
+      --preserve-context  preserve SELinux security context\n\
+  -Z, --context=CONTEXT  set SELinux security context of files and directories\n\
+"), stdout);
+
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       fputs (_("\
@@ -714,7 +800,7 @@ the VERSION_CONTROL environment variable.  Here are the values:\n\
   existing, nil   numbered if numbered backups exist, simple otherwise\n\
   simple, never   always make simple backups\n\
 "), stdout);
-      printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+      emit_bug_reporting_address ();
     }
   exit (status);
 }