summaryrefslogtreecommitdiff
path: root/src/install.c
diff options
context:
space:
mode:
authorKamil Dudka <kdudka@redhat.com>2009-02-17 13:16:54 +0100
committerJim Meyering <meyering@redhat.com>2009-02-17 14:12:44 +0100
commitdac5f12c6ed1225bfb8043d9ff7227d29439a955 (patch)
treed736baa51d46a7c6b53fe93aed03acb12c73f27e /src/install.c
parent26d3d090bd08ff36b1f66af8d4b7974c5d5b6a95 (diff)
downloadcoreutils-dac5f12c6ed1225bfb8043d9ff7227d29439a955.tar.xz
install: add --compare (-C) option to install file only when necessary
* src/install.c (have_same_content): New function to compare files content. (extra_mode): New function checking for non-permission bits in mode. (need_copy): New function to check if copy is necessary. (main): Handle new option --compare (-C). (copy_file): Skip file copying if not necessary. (usage): Show new option --compare (-C) in --help. * tests/install/install-C: Basic tests for install --compare (-C). * tests/install/install-C-root: Tests requiring root privileges. * tests/install/install-C-selinux: Tests requiring SELinux. * tests/Makefile.am: Add new tests for install --compare (-C). * doc/coreutils.texi: Document new install option --compare (-C). * NEWS: Mention the change.
Diffstat (limited to 'src/install.c')
-rw-r--r--src/install.c134
1 files changed, 133 insertions, 1 deletions
diff --git a/src/install.c b/src/install.c
index 9bf9eee0f..669fbea7a 100644
--- a/src/install.c
+++ b/src/install.c
@@ -31,6 +31,7 @@
#include "cp-hash.h"
#include "copy.h"
#include "filenamecat.h"
+#include "full-read.h"
#include "mkancesdirs.h"
#include "mkdir-p.h"
#include "modechange.h"
@@ -125,6 +126,9 @@ static mode_t dir_mode = DEFAULT_MODE;
or S_ISGID bits. */
static mode_t dir_mode_bits = CHMOD_MODE_BITS;
+/* Compare files before installing (-C) */
+static bool copy_only_if_needed;
+
/* If true, strip executable files after copying them. */
static bool strip_files;
@@ -145,6 +149,7 @@ enum
static struct option const long_options[] =
{
{"backup", optional_argument, NULL, 'b'},
+ {"compare", no_argument, NULL, 'C'},
{GETOPT_SELINUX_CONTEXT_OPTION_DECL},
{"directory", no_argument, NULL, 'd'},
{"group", required_argument, NULL, 'g'},
@@ -167,6 +172,107 @@ static struct option const long_options[] =
{NULL, 0, NULL, 0}
};
+/* Compare content of opened files using file descriptors A_FD and B_FD. Return
+ true if files are equal. */
+static bool
+have_same_content (int a_fd, int b_fd)
+{
+ enum { CMP_BLOCK_SIZE = 4096 };
+ static char a_buff[CMP_BLOCK_SIZE];
+ static char b_buff[CMP_BLOCK_SIZE];
+
+ size_t size;
+ while (0 < (size = full_read (a_fd, a_buff, sizeof a_buff))) {
+ if (size != full_read (b_fd, b_buff, sizeof b_buff))
+ return false;
+
+ if (memcmp (a_buff, b_buff, size) != 0)
+ return false;
+ }
+
+ return size == 0;
+}
+
+/* Return true for mode with non-permission bits. */
+static bool
+extra_mode (mode_t input)
+{
+ const mode_t mask = ~S_IRWXUGO & ~S_IFMT;
+ return input & mask;
+}
+
+/* Return true if copy of file SRC_NAME to file DEST_NAME is necessary. */
+static bool
+need_copy (const char *src_name, const char *dest_name,
+ const struct cp_options *x)
+{
+ struct stat src_sb, dest_sb;
+ int src_fd, dest_fd;
+ bool content_match;
+
+ if (extra_mode (mode))
+ return true;
+
+ /* compare files using stat */
+ if (lstat (src_name, &src_sb) != 0)
+ return true;
+
+ if (lstat (dest_name, &dest_sb) != 0)
+ return true;
+
+ if (!S_ISREG (src_sb.st_mode) || !S_ISREG (dest_sb.st_mode)
+ || extra_mode (src_sb.st_mode) || extra_mode (dest_sb.st_mode))
+ return true;
+
+ if (src_sb.st_size != dest_sb.st_size
+ || (dest_sb.st_mode & CHMOD_MODE_BITS) != mode
+ || dest_sb.st_uid != (owner_id == (uid_t) -1 ? getuid () : owner_id)
+ || dest_sb.st_gid != (group_id == (gid_t) -1 ? getgid () : group_id))
+ return true;
+
+ /* compare SELinux context if preserving */
+ if (selinux_enabled && x->preserve_security_context)
+ {
+ security_context_t file_scontext = NULL;
+ security_context_t to_scontext = NULL;
+ bool scontext_match;
+
+ if (getfilecon (src_name, &file_scontext) == -1)
+ return true;
+
+ if (getfilecon (dest_name, &to_scontext) == -1)
+ {
+ freecon (file_scontext);
+ return true;
+ }
+
+ scontext_match = STREQ (file_scontext, to_scontext);
+
+ freecon (file_scontext);
+ freecon (to_scontext);
+ if (!scontext_match)
+ return true;
+ }
+
+ /* compare files content */
+ src_fd = open (src_name, O_RDONLY);
+ if (src_fd < 0)
+ return true;
+
+ dest_fd = open (dest_name, O_RDONLY);
+ if (dest_fd < 0)
+ {
+ close (src_fd);
+ return true;
+ }
+
+ content_match = have_same_content (src_fd, dest_fd);
+
+ close (src_fd);
+ close (dest_fd);
+ return !content_match;
+}
+
static void
cp_option_init (struct cp_options *x)
{
@@ -361,7 +467,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:Z:", long_options,
+ while ((optc = getopt_long (argc, argv, "bcCsDdg:m:o:pt:TvS:Z:", long_options,
NULL)) != -1)
{
switch (optc)
@@ -373,6 +479,9 @@ main (int argc, char **argv)
break;
case 'c':
break;
+ case 'C':
+ copy_only_if_needed = true;
+ break;
case 's':
strip_files = true;
#ifdef SIGCHLD
@@ -529,6 +638,24 @@ main (int argc, char **argv)
error (0, 0, _("WARNING: ignoring --strip-program option as -s option was "
"not specified"));
+ if (copy_only_if_needed && x.preserve_timestamps)
+ {
+ error (0, 0, _("options --compare (-C) and --preserve-timestamps are "
+ "mutually exclusive"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (copy_only_if_needed && strip_files)
+ {
+ error (0, 0, _("options --compare (-C) and --strip are mutually "
+ "exclusive"));
+ usage (EXIT_FAILURE);
+ }
+
+ if (copy_only_if_needed && extra_mode (mode))
+ error (0, 0, _("the --compare (-C) option is ignored when you"
+ " specify a mode with non-permission bits"));
+
get_ids ();
if (dir_arg)
@@ -645,6 +772,9 @@ copy_file (const char *from, const char *to, const struct cp_options *x)
{
bool copy_into_self;
+ if (copy_only_if_needed && !need_copy (from, to, x))
+ return true;
+
/* Allow installing from non-regular files like /dev/null.
Charles Karney reported that some Sun version of install allows that
and that sendmail's installation process relies on the behavior.
@@ -835,6 +965,8 @@ Mandatory arguments to long options are mandatory for short options too.\n\
--backup[=CONTROL] make a backup of each existing destination file\n\
-b like --backup but does not accept an argument\n\
-c (ignored)\n\
+ -C, --compare compare each pair of source and destination files, and\n\
+ in some cases, do not modify the destination at all\n\
-d, --directory treat all arguments as directory names; create all\n\
components of the specified directories\n\
"), stdout);