diff -urNp coreutils-8.0-orig/configure.ac coreutils-8.0/configure.ac --- coreutils-8.0-orig/configure.ac 2009-10-07 10:09:43.000000000 +0200 +++ coreutils-8.0/configure.ac 2009-10-07 10:10:11.000000000 +0200 @@ -122,6 +122,13 @@ AC_ARG_ENABLE(pam, dnl LIB_PAM="-ldl -lpam -lpam_misc" AC_SUBST(LIB_PAM)]) +dnl Give the chance to enable SELINUX +AC_ARG_ENABLE(selinux, dnl +[ --enable-selinux Enable use of the SELINUX libraries], +[AC_DEFINE(WITH_SELINUX, 1, [Define if you want to use SELINUX]) +LIB_SELINUX="-lselinux" +AC_SUBST(LIB_SELINUX)]) + AC_FUNC_FORK optional_bin_progs= diff -urNp coreutils-8.0-orig/configure.ac.orig coreutils-8.0/configure.ac.orig --- coreutils-8.0-orig/configure.ac.orig 2009-10-07 10:09:43.000000000 +0200 +++ coreutils-8.0/configure.ac.orig 2009-10-07 10:09:43.000000000 +0200 @@ -115,6 +115,13 @@ if test "$gl_gcc_warnings" = yes; then AC_DEFINE([GNULIB_PORTCHECK], [1], [enable some gnulib portability checks]) fi +dnl Give the chance to enable PAM +AC_ARG_ENABLE(pam, dnl +[ --enable-pam Enable use of the PAM libraries], +[AC_DEFINE(USE_PAM, 1, [Define if you want to use PAM]) +LIB_PAM="-ldl -lpam -lpam_misc" +AC_SUBST(LIB_PAM)]) + AC_FUNC_FORK optional_bin_progs= diff -urNp coreutils-8.0-orig/man/chcon.x coreutils-8.0/man/chcon.x --- coreutils-8.0-orig/man/chcon.x 2009-09-01 13:01:16.000000000 +0200 +++ coreutils-8.0/man/chcon.x 2009-10-07 10:10:11.000000000 +0200 @@ -1,4 +1,4 @@ [NAME] -chcon \- change file security context +chcon \- change file SELinux security context [DESCRIPTION] .\" Add any additional description here diff -urNp coreutils-8.0-orig/man/runcon.x coreutils-8.0/man/runcon.x --- coreutils-8.0-orig/man/runcon.x 2009-09-01 13:01:16.000000000 +0200 +++ coreutils-8.0/man/runcon.x 2009-10-07 10:10:11.000000000 +0200 @@ -1,5 +1,5 @@ [NAME] -runcon \- run command with specified security context +runcon \- run command with specified SELinux security context [DESCRIPTION] Run COMMAND with completely-specified CONTEXT, or with current or transitioned security context modified by one or more of LEVEL, diff -urNp coreutils-8.0-orig/src/copy.c coreutils-8.0/src/copy.c --- coreutils-8.0-orig/src/copy.c 2009-09-29 15:27:54.000000000 +0200 +++ coreutils-8.0/src/copy.c 2009-10-07 10:10:11.000000000 +0200 @@ -1943,6 +1943,8 @@ copy_internal (char const *src_name, cha { /* Here, we are crossing a file system boundary and cp's -x option is in effect: so don't copy the contents of this directory. */ + if (x->preserve_security_context) + restore_default_fscreatecon_or_die (); } else { diff -urNp coreutils-8.0-orig/src/copy.c.orig coreutils-8.0/src/copy.c.orig --- coreutils-8.0-orig/src/copy.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/copy.c.orig 2009-09-29 15:27:54.000000000 +0200 @@ -0,0 +1,2369 @@ +/* copy.c -- core functions for copying files and directories + Copyright (C) 89, 90, 91, 1995-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Extracted from cp.c and librarified by Jim Meyering. */ + +#include +#include +#include +#include +#include + +#if HAVE_HURD_H +# include +#endif +#if HAVE_PRIV_H +# include +#endif + +#include "system.h" +#include "acl.h" +#include "backupfile.h" +#include "buffer-lcm.h" +#include "copy.h" +#include "cp-hash.h" +#include "error.h" +#include "fcntl--.h" +#include "file-set.h" +#include "filemode.h" +#include "filenamecat.h" +#include "full-write.h" +#include "hash.h" +#include "hash-triple.h" +#include "ignore-value.h" +#include "quote.h" +#include "same.h" +#include "savedir.h" +#include "stat-time.h" +#include "utimecmp.h" +#include "utimens.h" +#include "write-any-file.h" +#include "areadlink.h" +#include "yesno.h" + +#if USE_XATTR +# include +# include +# include +# include "verror.h" +#endif + +#if HAVE_SYS_IOCTL_H +# include +#endif + +#ifndef HAVE_FCHOWN +# define HAVE_FCHOWN false +# define fchown(fd, uid, gid) (-1) +#endif + +#ifndef HAVE_LCHOWN +# define HAVE_LCHOWN false +# define lchown(name, uid, gid) chown (name, uid, gid) +#endif + +#ifndef HAVE_MKFIFO +static int +rpl_mkfifo (char const *file, mode_t mode) +{ + errno = ENOTSUP; + return -1; +} +# define mkfifo rpl_mkfifo +#endif + +#ifndef USE_ACL +# define USE_ACL 0 +#endif + +#define SAME_OWNER(A, B) ((A).st_uid == (B).st_uid) +#define SAME_GROUP(A, B) ((A).st_gid == (B).st_gid) +#define SAME_OWNER_AND_GROUP(A, B) (SAME_OWNER (A, B) && SAME_GROUP (A, B)) + +struct dir_list +{ + struct dir_list *parent; + ino_t ino; + dev_t dev; +}; + +/* Initial size of the cp.dest_info hash table. */ +#define DEST_INFO_INITIAL_CAPACITY 61 + +static bool copy_internal (char const *src_name, char const *dst_name, + bool new_dst, dev_t device, + struct dir_list *ancestors, + const struct cp_options *x, + bool command_line_arg, + bool *first_dir_created_per_command_line_arg, + bool *copy_into_self, + bool *rename_succeeded); +static bool owner_failure_ok (struct cp_options const *x); + +/* Pointers to the file names: they're used in the diagnostic that is issued + when we detect the user is trying to copy a directory into itself. */ +static char const *top_level_src_name; +static char const *top_level_dst_name; + +/* Set the timestamp of symlink, FILE, to TIMESPEC. + If this system lacks support for that, simply return 0. */ +static inline int +utimens_symlink (char const *file, struct timespec const *timespec) +{ + int err = 0; + +#if HAVE_UTIMENSAT + err = utimensat (AT_FDCWD, file, timespec, AT_SYMLINK_NOFOLLOW); + /* When configuring on a system with new headers and libraries, and + running on one with a kernel that is old enough to lack the syscall, + utimensat fails with ENOSYS. Ignore that. */ + if (err && errno == ENOSYS) + err = 0; +#else + (void) file; + (void) timespec; +#endif + + return err; +} + +/* Perform the O(1) btrfs clone operation, if possible. + Upon success, return 0. Otherwise, return -1 and set errno. */ +static inline int +clone_file (int dest_fd, int src_fd) +{ +#ifdef __linux__ +# undef BTRFS_IOCTL_MAGIC +# define BTRFS_IOCTL_MAGIC 0x94 +# undef BTRFS_IOC_CLONE +# define BTRFS_IOC_CLONE _IOW (BTRFS_IOCTL_MAGIC, 9, int) + return ioctl (dest_fd, BTRFS_IOC_CLONE, src_fd); +#else + (void) dest_fd; + (void) src_fd; + errno = ENOTSUP; + return -1; +#endif +} + +/* FIXME: describe */ +/* FIXME: rewrite this to use a hash table so we avoid the quadratic + performance hit that's probably noticeable only on trees deeper + than a few hundred levels. See use of active_dir_map in remove.c */ + +static bool +is_ancestor (const struct stat *sb, const struct dir_list *ancestors) +{ + while (ancestors != 0) + { + if (ancestors->ino == sb->st_ino && ancestors->dev == sb->st_dev) + return true; + ancestors = ancestors->parent; + } + return false; +} + +static bool +errno_unsupported (int err) +{ + return err == ENOTSUP || err == ENODATA; +} + +#if USE_XATTR +static void +copy_attr_error (struct error_context *ctx ATTRIBUTE_UNUSED, + char const *fmt, ...) +{ + int err = errno; + va_list ap; + + if (!errno_unsupported (errno)) + { + /* use verror module to print error message */ + va_start (ap, fmt); + verror (0, err, fmt, ap); + va_end (ap); + } +} + +static void +copy_attr_allerror (struct error_context *ctx ATTRIBUTE_UNUSED, + char const *fmt, ...) +{ + int err = errno; + va_list ap; + + /* use verror module to print error message */ + va_start (ap, fmt); + verror (0, err, fmt, ap); + va_end (ap); +} + +static char const * +copy_attr_quote (struct error_context *ctx ATTRIBUTE_UNUSED, char const *str) +{ + return quote (str); +} + +static void +copy_attr_free (struct error_context *ctx ATTRIBUTE_UNUSED, + char const *str ATTRIBUTE_UNUSED) +{ +} + +static bool +copy_attr_by_fd (char const *src_path, int src_fd, + char const *dst_path, int dst_fd, const struct cp_options *x) +{ + struct error_context ctx = + { + .error = x->require_preserve_xattr ? copy_attr_allerror : copy_attr_error, + .quote = copy_attr_quote, + .quote_free = copy_attr_free + }; + return 0 == attr_copy_fd (src_path, src_fd, dst_path, dst_fd, 0, + (x->reduce_diagnostics + && !x->require_preserve_xattr)? NULL : &ctx); +} + +static bool +copy_attr_by_name (char const *src_path, char const *dst_path, + const struct cp_options *x) +{ + struct error_context ctx = + { + .error = x->require_preserve_xattr ? copy_attr_allerror : copy_attr_error, + .quote = copy_attr_quote, + .quote_free = copy_attr_free + }; + return 0 == attr_copy_file (src_path, dst_path, 0, + (x-> reduce_diagnostics + && !x->require_preserve_xattr) ? NULL : &ctx); +} +#else /* USE_XATTR */ + +static bool +copy_attr_by_fd (char const *src_path ATTRIBUTE_UNUSED, + int src_fd ATTRIBUTE_UNUSED, + char const *dst_path ATTRIBUTE_UNUSED, + int dst_fd ATTRIBUTE_UNUSED, + const struct cp_options *x ATTRIBUTE_UNUSED) +{ + return true; +} + +static bool +copy_attr_by_name (char const *src_path ATTRIBUTE_UNUSED, + char const *dst_path ATTRIBUTE_UNUSED, + const struct cp_options *x ATTRIBUTE_UNUSED) +{ + return true; +} +#endif /* USE_XATTR */ + +/* Read the contents of the directory SRC_NAME_IN, and recursively + copy the contents to DST_NAME_IN. NEW_DST is true if + DST_NAME_IN is a directory that was created previously in the + recursion. SRC_SB and ANCESTORS describe SRC_NAME_IN. + Set *COPY_INTO_SELF if SRC_NAME_IN is a parent of + FIRST_DIR_CREATED_PER_COMMAND_LINE_ARG FIXME + (or the same as) DST_NAME_IN; otherwise, clear it. + Return true if successful. */ + +static bool +copy_dir (char const *src_name_in, char const *dst_name_in, bool new_dst, + const struct stat *src_sb, struct dir_list *ancestors, + const struct cp_options *x, + bool *first_dir_created_per_command_line_arg, + bool *copy_into_self) +{ + char *name_space; + char *namep; + struct cp_options non_command_line_options = *x; + bool ok = true; + + name_space = savedir (src_name_in); + if (name_space == NULL) + { + /* This diagnostic is a bit vague because savedir can fail in + several different ways. */ + error (0, errno, _("cannot access %s"), quote (src_name_in)); + return false; + } + + /* For cp's -H option, dereference command line arguments, but do not + dereference symlinks that are found via recursive traversal. */ + if (x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) + non_command_line_options.dereference = DEREF_NEVER; + + namep = name_space; + while (*namep != '\0') + { + bool local_copy_into_self; + char *src_name = file_name_concat (src_name_in, namep, NULL); + char *dst_name = file_name_concat (dst_name_in, namep, NULL); + + ok &= copy_internal (src_name, dst_name, new_dst, src_sb->st_dev, + ancestors, &non_command_line_options, false, + first_dir_created_per_command_line_arg, + &local_copy_into_self, NULL); + *copy_into_self |= local_copy_into_self; + + free (dst_name); + free (src_name); + + /* If we're copying into self, there's no point in continuing, + and in fact, that would even infloop, now that we record only + the first created directory per command line argument. */ + if (local_copy_into_self) + break; + + namep += strlen (namep) + 1; + } + free (name_space); + return ok; +} + +/* Set the owner and owning group of DEST_DESC to the st_uid and + st_gid fields of SRC_SB. If DEST_DESC is undefined (-1), set + the owner and owning group of DST_NAME instead; for + safety prefer lchown if the system supports it since no + symbolic links should be involved. DEST_DESC must + refer to the same file as DEST_NAME if defined. + Upon failure to set both UID and GID, try to set only the GID. + NEW_DST is true if the file was newly created; otherwise, + DST_SB is the status of the destination. + Return 1 if the initial syscall succeeds, 0 if it fails but it's OK + not to preserve ownership, -1 otherwise. */ + +static int +set_owner (const struct cp_options *x, char const *dst_name, int dest_desc, + struct stat const *src_sb, bool new_dst, + struct stat const *dst_sb) +{ + uid_t uid = src_sb->st_uid; + gid_t gid = src_sb->st_gid; + + /* Naively changing the ownership of an already-existing file before + changing its permissions would create a window of vulnerability if + the file's old permissions are too generous for the new owner and + group. Avoid the window by first changing to a restrictive + temporary mode if necessary. */ + + if (!new_dst && (x->preserve_mode || x->move_mode || x->set_mode)) + { + mode_t old_mode = dst_sb->st_mode; + mode_t new_mode = + (x->preserve_mode || x->move_mode ? src_sb->st_mode : x->mode); + mode_t restrictive_temp_mode = old_mode & new_mode & S_IRWXU; + + if ((USE_ACL + || (old_mode & CHMOD_MODE_BITS + & (~new_mode | S_ISUID | S_ISGID | S_ISVTX))) + && qset_acl (dst_name, dest_desc, restrictive_temp_mode) != 0) + { + if (! owner_failure_ok (x)) + error (0, errno, _("clearing permissions for %s"), quote (dst_name)); + return -x->require_preserve; + } + } + + if (HAVE_FCHOWN && dest_desc != -1) + { + if (fchown (dest_desc, uid, gid) == 0) + return 1; + if (errno == EPERM || errno == EINVAL) + { + /* We've failed to set *both*. Now, try to set just the group + ID, but ignore any failure here, and don't change errno. */ + int saved_errno = errno; + ignore_value (fchown (dest_desc, -1, gid)); + errno = saved_errno; + } + } + else + { + if (lchown (dst_name, uid, gid) == 0) + return 1; + if (errno == EPERM || errno == EINVAL) + { + /* We've failed to set *both*. Now, try to set just the group + ID, but ignore any failure here, and don't change errno. */ + int saved_errno = errno; + ignore_value (lchown (dst_name, -1, gid)); + errno = saved_errno; + } + } + + if (! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + quote (dst_name)); + if (x->require_preserve) + return -1; + } + + return 0; +} + +/* Set the st_author field of DEST_DESC to the st_author field of + SRC_SB. If DEST_DESC is undefined (-1), set the st_author field + of DST_NAME instead. DEST_DESC must refer to the same file as + DEST_NAME if defined. */ + +static void +set_author (const char *dst_name, int dest_desc, const struct stat *src_sb) +{ +#if HAVE_STRUCT_STAT_ST_AUTHOR + /* FIXME: Modify the following code so that it does not + follow symbolic links. */ + + /* Preserve the st_author field. */ + file_t file = (dest_desc < 0 + ? file_name_lookup (dst_name, 0, 0) + : getdport (dest_desc)); + if (file == MACH_PORT_NULL) + error (0, errno, _("failed to lookup file %s"), quote (dst_name)); + else + { + error_t err = file_chauthor (file, src_sb->st_author); + if (err) + error (0, err, _("failed to preserve authorship for %s"), + quote (dst_name)); + mach_port_deallocate (mach_task_self (), file); + } +#else + (void) dst_name; + (void) dest_desc; + (void) src_sb; +#endif +} + +/* Change the file mode bits of the file identified by DESC or NAME to MODE. + Use DESC if DESC is valid and fchmod is available, NAME otherwise. */ + +static int +fchmod_or_lchmod (int desc, char const *name, mode_t mode) +{ +#if HAVE_FCHMOD + if (0 <= desc) + return fchmod (desc, mode); +#endif + return lchmod (name, mode); +} + +/* Copy a regular file from SRC_NAME to DST_NAME. + If the source file contains holes, copies holes and blocks of zeros + in the source file as holes in the destination file. + (Holes are read as zeroes by the `read' system call.) + When creating the destination, use DST_MODE & ~OMITTED_PERMISSIONS + as the third argument in the call to open, adding + OMITTED_PERMISSIONS after copying as needed. + X provides many option settings. + Return true if successful. + *NEW_DST is as in copy_internal. + SRC_SB is the result of calling XSTAT (aka stat) on SRC_NAME. */ + +static bool +copy_reg (char const *src_name, char const *dst_name, + const struct cp_options *x, + mode_t dst_mode, mode_t omitted_permissions, bool *new_dst, + struct stat const *src_sb) +{ + char *buf; + char *buf_alloc = NULL; + char *name_alloc = NULL; + int dest_desc; + int dest_errno; + int source_desc; + mode_t src_mode = src_sb->st_mode; + struct stat sb; + struct stat src_open_sb; + bool return_val = true; + bool data_copy_required = true; + + source_desc = open (src_name, + (O_RDONLY | O_BINARY + | (x->dereference == DEREF_NEVER ? O_NOFOLLOW : 0))); + if (source_desc < 0) + { + error (0, errno, _("cannot open %s for reading"), quote (src_name)); + return false; + } + + if (fstat (source_desc, &src_open_sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quote (src_name)); + return_val = false; + goto close_src_desc; + } + + /* Compare the source dev/ino from the open file to the incoming, + saved ones obtained via a previous call to stat. */ + if (! SAME_INODE (*src_sb, src_open_sb)) + { + error (0, 0, + _("skipping file %s, as it was replaced while being copied"), + quote (src_name)); + return_val = false; + goto close_src_desc; + } + + /* The semantics of the following open calls are mandated + by the specs for both cp and mv. */ + if (! *new_dst) + { + dest_desc = open (dst_name, O_WRONLY | O_TRUNC | O_BINARY); + dest_errno = errno; + + /* When using cp --preserve=context to copy to an existing destination, + use the default context rather than that of the source. Why? + 1) the src context may prohibit writing, and + 2) because it's more consistent to use the same context + that is used when the destination file doesn't already exist. */ + if (x->preserve_security_context && 0 <= dest_desc) + { + security_context_t con = NULL; + if (getfscreatecon (&con) < 0) + { + if (!x->reduce_diagnostics || x->require_preserve_context) + error (0, errno, _("failed to get file system create context")); + if (x->require_preserve_context) + { + return_val = false; + goto close_src_and_dst_desc; + } + } + + if (con) + { + if (fsetfilecon (dest_desc, con) < 0) + { + if (!x->reduce_diagnostics || x->require_preserve_context) + error (0, errno, + _("failed to set the security context of %s to %s"), + quote_n (0, dst_name), quote_n (1, con)); + if (x->require_preserve_context) + { + return_val = false; + freecon (con); + goto close_src_and_dst_desc; + } + } + freecon (con); + } + } + + if (dest_desc < 0 && x->unlink_dest_after_failed_open) + { + if (unlink (dst_name) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + return_val = false; + goto close_src_desc; + } + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + + /* Tell caller that the destination file was unlinked. */ + *new_dst = true; + } + } + + if (*new_dst) + { + int open_flags = O_WRONLY | O_CREAT | O_BINARY; + dest_desc = open (dst_name, open_flags | O_EXCL, + dst_mode & ~omitted_permissions); + dest_errno = errno; + + /* When trying to copy through a dangling destination symlink, + the above open fails with EEXIST. If that happens, and + lstat'ing the DST_NAME shows that it is a symlink, then we + have a problem: trying to resolve this dangling symlink to + a directory/destination-entry pair is fundamentally racy, + so punt. If POSIXLY_CORRECT is set, simply call open again, + but without O_EXCL (potentially dangerous). If not, fail + with a diagnostic. These shenanigans are necessary only + when copying, i.e., not in move_mode. */ + if (dest_desc < 0 && dest_errno == EEXIST && ! x->move_mode) + { + struct stat dangling_link_sb; + if (lstat (dst_name, &dangling_link_sb) == 0 + && S_ISLNK (dangling_link_sb.st_mode)) + { + if (x->open_dangling_dest_symlink) + { + dest_desc = open (dst_name, open_flags, + dst_mode & ~omitted_permissions); + dest_errno = errno; + } + else + { + error (0, 0, _("not writing through dangling symlink %s"), + quote (dst_name)); + return_val = false; + goto close_src_desc; + } + } + } + } + else + omitted_permissions = 0; + + if (dest_desc < 0) + { + error (0, dest_errno, _("cannot create regular file %s"), + quote (dst_name)); + return_val = false; + goto close_src_desc; + } + + if (fstat (dest_desc, &sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + + if (x->reflink_mode) + { + bool clone_ok = clone_file (dest_desc, source_desc) == 0; + if (clone_ok || x->reflink_mode == REFLINK_ALWAYS) + { + if (!clone_ok) + { + error (0, errno, _("failed to clone %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + data_copy_required = false; + } + } + + if (data_copy_required) + { + typedef uintptr_t word; + off_t n_read_total = 0; + + /* Choose a suitable buffer size; it may be adjusted later. */ + size_t buf_alignment = lcm (getpagesize (), sizeof (word)); + size_t buf_alignment_slop = sizeof (word) + buf_alignment - 1; + size_t buf_size = io_blksize (sb); + + /* Deal with sparse files. */ + bool last_write_made_hole = false; + bool make_holes = false; + + if (S_ISREG (sb.st_mode)) + { + /* Even with --sparse=always, try to create holes only + if the destination is a regular file. */ + if (x->sparse_mode == SPARSE_ALWAYS) + make_holes = true; + +#if HAVE_STRUCT_STAT_ST_BLOCKS + /* Use a heuristic to determine whether SRC_NAME contains any sparse + blocks. If the file has fewer blocks than would normally be + needed for a file of its size, then at least one of the blocks in + the file is a hole. */ + if (x->sparse_mode == SPARSE_AUTO && S_ISREG (src_open_sb.st_mode) + && ST_NBLOCKS (src_open_sb) < src_open_sb.st_size / ST_NBLOCKSIZE) + make_holes = true; +#endif + } + + /* If not making a sparse file, try to use a more-efficient + buffer size. */ + if (! make_holes) + { + /* Compute the least common multiple of the input and output + buffer sizes, adjusting for outlandish values. */ + size_t blcm_max = MIN (SIZE_MAX, SSIZE_MAX) - buf_alignment_slop; + size_t blcm = buffer_lcm (io_blksize (src_open_sb), buf_size, + blcm_max); + + /* Do not bother with a buffer larger than the input file, plus one + byte to make sure the file has not grown while reading it. */ + if (S_ISREG (src_open_sb.st_mode) && src_open_sb.st_size < buf_size) + buf_size = src_open_sb.st_size + 1; + + /* However, stick with a block size that is a positive multiple of + blcm, overriding the above adjustments. Watch out for + overflow. */ + buf_size += blcm - 1; + buf_size -= buf_size % blcm; + if (buf_size == 0 || blcm_max < buf_size) + buf_size = blcm; + } + + /* Make a buffer with space for a sentinel at the end. */ + buf_alloc = xmalloc (buf_size + buf_alignment_slop); + buf = ptr_align (buf_alloc, buf_alignment); + + for (;;) + { + word *wp = NULL; + + ssize_t n_read = read (source_desc, buf, buf_size); + if (n_read < 0) + { +#ifdef EINTR + if (errno == EINTR) + continue; +#endif + error (0, errno, _("reading %s"), quote (src_name)); + return_val = false; + goto close_src_and_dst_desc; + } + if (n_read == 0) + break; + + n_read_total += n_read; + + if (make_holes) + { + char *cp; + + /* Sentinel to stop loop. */ + buf[n_read] = '\1'; +#ifdef lint + /* Usually, buf[n_read] is not the byte just before a "word" + (aka uintptr_t) boundary. In that case, the word-oriented + test below (*wp++ == 0) would read some uninitialized bytes + after the sentinel. To avoid false-positive reports about + this condition (e.g., from a tool like valgrind), set the + remaining bytes -- to any value. */ + memset (buf + n_read + 1, 0, sizeof (word) - 1); +#endif + + /* Find first nonzero *word*, or the word with the sentinel. */ + + wp = (word *) buf; + while (*wp++ == 0) + continue; + + /* Find the first nonzero *byte*, or the sentinel. */ + + cp = (char *) (wp - 1); + while (*cp++ == 0) + continue; + + if (cp <= buf + n_read) + /* Clear to indicate that a normal write is needed. */ + wp = NULL; + else + { + /* We found the sentinel, so the whole input block was zero. + Make a hole. */ + if (lseek (dest_desc, n_read, SEEK_CUR) < 0) + { + error (0, errno, _("cannot lseek %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + last_write_made_hole = true; + } + } + + if (!wp) + { + size_t n = n_read; + if (full_write (dest_desc, buf, n) != n) + { + error (0, errno, _("writing %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + last_write_made_hole = false; + + /* It is tempting to return early here upon a short read from a + regular file. That would save the final read syscall for each + file. Unfortunately that doesn't work for certain files in + /proc with linux kernels from at least 2.6.9 .. 2.6.29. */ + } + } + + /* If the file ends with a `hole', we need to do something to record + the length of the file. On modern systems, calling ftruncate does + the job. On systems without native ftruncate support, we have to + write a byte at the ending position. Otherwise the kernel would + truncate the file at the end of the last write operation. */ + + if (last_write_made_hole) + { + if (HAVE_FTRUNCATE + ? /* ftruncate sets the file size, + so there is no need for a write. */ + ftruncate (dest_desc, n_read_total) < 0 + : /* Seek backwards one character and write a null. */ + (lseek (dest_desc, (off_t) -1, SEEK_CUR) < 0L + || full_write (dest_desc, "", 1) != 1)) + { + error (0, errno, _("writing %s"), quote (dst_name)); + return_val = false; + goto close_src_and_dst_desc; + } + } + } + + if (x->preserve_timestamps) + { + struct timespec timespec[2]; + timespec[0] = get_stat_atime (src_sb); + timespec[1] = get_stat_mtime (src_sb); + + if (gl_futimens (dest_desc, dst_name, timespec) != 0) + { + error (0, errno, _("preserving times for %s"), quote (dst_name)); + if (x->require_preserve) + { + return_val = false; + goto close_src_and_dst_desc; + } + } + } + + /* To allow copying xattrs on read-only files, temporarily chmod u+rw. + This workaround is required as an inode permission check is done + by xattr_permission() in fs/xattr.c of the GNU/Linux kernel tree. */ + if (x->preserve_xattr) + { + bool access_changed = false; + + if (!(sb.st_mode & S_IWUSR) && geteuid() != 0) + access_changed = fchmod_or_lchmod (dest_desc, dst_name, 0600) == 0; + + if (!copy_attr_by_fd (src_name, source_desc, dst_name, dest_desc, x) + && x->require_preserve_xattr) + return_val = false; + + if (access_changed) + fchmod_or_lchmod (dest_desc, dst_name, dst_mode & ~omitted_permissions); + } + + if (x->preserve_ownership && ! SAME_OWNER_AND_GROUP (*src_sb, sb)) + { + switch (set_owner (x, dst_name, dest_desc, src_sb, *new_dst, &sb)) + { + case -1: + return_val = false; + goto close_src_and_dst_desc; + + case 0: + src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); + break; + } + } + + set_author (dst_name, dest_desc, src_sb); + + if (x->preserve_mode || x->move_mode) + { + if (copy_acl (src_name, source_desc, dst_name, dest_desc, src_mode) != 0 + && x->require_preserve) + return_val = false; + } + else if (x->set_mode) + { + if (set_acl (dst_name, dest_desc, x->mode) != 0) + return_val = false; + } + else if (omitted_permissions) + { + omitted_permissions &= ~ cached_umask (); + if (omitted_permissions + && fchmod_or_lchmod (dest_desc, dst_name, dst_mode) != 0) + { + error (0, errno, _("preserving permissions for %s"), + quote (dst_name)); + if (x->require_preserve) + return_val = false; + } + } + +close_src_and_dst_desc: + if (close (dest_desc) < 0) + { + error (0, errno, _("closing %s"), quote (dst_name)); + return_val = false; + } +close_src_desc: + if (close (source_desc) < 0) + { + error (0, errno, _("closing %s"), quote (src_name)); + return_val = false; + } + + free (buf_alloc); + free (name_alloc); + return return_val; +} + +/* Return true if it's ok that the source and destination + files are the `same' by some measure. The goal is to avoid + making the `copy' operation remove both copies of the file + in that case, while still allowing the user to e.g., move or + copy a regular file onto a symlink that points to it. + Try to minimize the cost of this function in the common case. + Set *RETURN_NOW if we've determined that the caller has no more + work to do and should return successfully, right away. + + Set *UNLINK_SRC if we've determined that the caller wants to do + `rename (a, b)' where `a' and `b' are distinct hard links to the same + file. In that case, the caller should try to unlink `a' and then return + successfully. Ideally, we wouldn't have to do that, and we'd be + able to rely on rename to remove the source file. However, POSIX + mistakenly requires that such a rename call do *nothing* and return + successfully. */ + +static bool +same_file_ok (char const *src_name, struct stat const *src_sb, + char const *dst_name, struct stat const *dst_sb, + const struct cp_options *x, bool *return_now, bool *unlink_src) +{ + const struct stat *src_sb_link; + const struct stat *dst_sb_link; + struct stat tmp_dst_sb; + struct stat tmp_src_sb; + + bool same_link; + bool same = SAME_INODE (*src_sb, *dst_sb); + + *return_now = false; + *unlink_src = false; + + /* FIXME: this should (at the very least) be moved into the following + if-block. More likely, it should be removed, because it inhibits + making backups. But removing it will result in a change in behavior + that will probably have to be documented -- and tests will have to + be updated. */ + if (same && x->hard_link) + { + *return_now = true; + return true; + } + + if (x->dereference == DEREF_NEVER) + { + same_link = same; + + /* If both the source and destination files are symlinks (and we'll + know this here IFF preserving symlinks), then it's ok -- as long + as they are distinct. */ + if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode)) + return ! same_name (src_name, dst_name); + + src_sb_link = src_sb; + dst_sb_link = dst_sb; + } + else + { + if (!same) + return true; + + if (lstat (dst_name, &tmp_dst_sb) != 0 + || lstat (src_name, &tmp_src_sb) != 0) + return true; + + src_sb_link = &tmp_src_sb; + dst_sb_link = &tmp_dst_sb; + + same_link = SAME_INODE (*src_sb_link, *dst_sb_link); + + /* If both are symlinks, then it's ok, but only if the destination + will be unlinked before being opened. This is like the test + above, but with the addition of the unlink_dest_before_opening + conjunct because otherwise, with two symlinks to the same target, + we'd end up truncating the source file. */ + if (S_ISLNK (src_sb_link->st_mode) && S_ISLNK (dst_sb_link->st_mode) + && x->unlink_dest_before_opening) + return true; + } + + /* The backup code ensures there's a copy, so it's usually ok to + remove any destination file. One exception is when both + source and destination are the same directory entry. In that + case, moving the destination file aside (in making the backup) + would also rename the source file and result in an error. */ + if (x->backup_type != no_backups) + { + if (!same_link) + { + /* In copy mode when dereferencing symlinks, if the source is a + symlink and the dest is not, then backing up the destination + (moving it aside) would make it a dangling symlink, and the + subsequent attempt to open it in copy_reg would fail with + a misleading diagnostic. Avoid that by returning zero in + that case so the caller can make cp (or mv when it has to + resort to reading the source file) fail now. */ + + /* FIXME-note: even with the following kludge, we can still provoke + the offending diagnostic. It's just a little harder to do :-) + $ rm -f a b c; touch c; ln -s c b; ln -s b a; cp -b a b + cp: cannot open `a' for reading: No such file or directory + That's misleading, since a subsequent `ls' shows that `a' + is still there. + One solution would be to open the source file *before* moving + aside the destination, but that'd involve a big rewrite. */ + if ( ! x->move_mode + && x->dereference != DEREF_NEVER + && S_ISLNK (src_sb_link->st_mode) + && ! S_ISLNK (dst_sb_link->st_mode)) + return false; + + return true; + } + + return ! same_name (src_name, dst_name); + } + +#if 0 + /* FIXME: use or remove */ + + /* If we're making a backup, we'll detect the problem case in + copy_reg because SRC_NAME will no longer exist. Allowing + the test to be deferred lets cp do some useful things. + But when creating hardlinks and SRC_NAME is a symlink + but DST_NAME is not we must test anyway. */ + if (x->hard_link + || !S_ISLNK (src_sb_link->st_mode) + || S_ISLNK (dst_sb_link->st_mode)) + return true; + + if (x->dereference != DEREF_NEVER) + return true; +#endif + + /* They may refer to the same file if we're in move mode and the + target is a symlink. That is ok, since we remove any existing + destination file before opening it -- via `rename' if they're on + the same file system, via `unlink (DST_NAME)' otherwise. + It's also ok if they're distinct hard links to the same file. */ + if (x->move_mode || x->unlink_dest_before_opening) + { + if (S_ISLNK (dst_sb_link->st_mode)) + return true; + + if (same_link + && 1 < dst_sb_link->st_nlink + && ! same_name (src_name, dst_name)) + { + if (x->move_mode) + { + *unlink_src = true; + *return_now = true; + } + return true; + } + } + + /* If neither is a symlink, then it's ok as long as they aren't + hard links to the same file. */ + if (!S_ISLNK (src_sb_link->st_mode) && !S_ISLNK (dst_sb_link->st_mode)) + { + if (!SAME_INODE (*src_sb_link, *dst_sb_link)) + return true; + + /* If they are the same file, it's ok if we're making hard links. */ + if (x->hard_link) + { + *return_now = true; + return true; + } + } + + /* It's ok to remove a destination symlink. But that works only when we + unlink before opening the destination and when the source and destination + files are on the same partition. */ + if (x->unlink_dest_before_opening + && S_ISLNK (dst_sb_link->st_mode)) + return dst_sb_link->st_dev == src_sb_link->st_dev; + + if (x->dereference == DEREF_NEVER) + { + if ( ! S_ISLNK (src_sb_link->st_mode)) + tmp_src_sb = *src_sb_link; + else if (stat (src_name, &tmp_src_sb) != 0) + return true; + + if ( ! S_ISLNK (dst_sb_link->st_mode)) + tmp_dst_sb = *dst_sb_link; + else if (stat (dst_name, &tmp_dst_sb) != 0) + return true; + + if ( ! SAME_INODE (tmp_src_sb, tmp_dst_sb)) + return true; + + /* FIXME: shouldn't this be testing whether we're making symlinks? */ + if (x->hard_link) + { + *return_now = true; + return true; + } + } + + return false; +} + +/* Return true if FILE, with mode MODE, is writable in the sense of 'mv'. + Always consider a symbolic link to be writable. */ +static bool +writable_destination (char const *file, mode_t mode) +{ + return (S_ISLNK (mode) + || can_write_any_file () + || euidaccess (file, W_OK) == 0); +} + +static void +overwrite_prompt (char const *dst_name, struct stat const *dst_sb) +{ + if (! writable_destination (dst_name, dst_sb->st_mode)) + { + char perms[12]; /* "-rwxrwxrwx " ls-style modes. */ + strmode (dst_sb->st_mode, perms); + perms[10] = '\0'; + fprintf (stderr, + _("%s: try to overwrite %s, overriding mode %04lo (%s)? "), + program_name, quote (dst_name), + (unsigned long int) (dst_sb->st_mode & CHMOD_MODE_BITS), + &perms[1]); + } + else + { + fprintf (stderr, _("%s: overwrite %s? "), + program_name, quote (dst_name)); + } +} + +/* Initialize the hash table implementing a set of F_triple entries + corresponding to destination files. */ +extern void +dest_info_init (struct cp_options *x) +{ + x->dest_info + = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash, + triple_compare, + triple_free); +} + +/* Initialize the hash table implementing a set of F_triple entries + corresponding to source files listed on the command line. */ +extern void +src_info_init (struct cp_options *x) +{ + + /* Note that we use triple_hash_no_name here. + Contrast with the use of triple_hash above. + That is necessary because a source file may be specified + in many different ways. We want to warn about this + cp a a d/ + as well as this: + cp a ./a d/ + */ + x->src_info + = hash_initialize (DEST_INFO_INITIAL_CAPACITY, + NULL, + triple_hash_no_name, + triple_compare, + triple_free); +} + +/* When effecting a move (e.g., for mv(1)), and given the name DST_NAME + of the destination and a corresponding stat buffer, DST_SB, return + true if the logical `move' operation should _not_ proceed. + Otherwise, return false. + Depending on options specified in X, this code may issue an + interactive prompt asking whether it's ok to overwrite DST_NAME. */ +static bool +abandon_move (const struct cp_options *x, + char const *dst_name, + struct stat const *dst_sb) +{ + assert (x->move_mode); + return (x->interactive == I_ALWAYS_NO + || ((x->interactive == I_ASK_USER + || (x->interactive == I_UNSPECIFIED + && x->stdin_tty + && ! writable_destination (dst_name, dst_sb->st_mode))) + && (overwrite_prompt (dst_name, dst_sb), 1) + && ! yesno ())); +} + +/* Print --verbose output on standard output, e.g. `new' -> `old'. + If BACKUP_DST_NAME is non-NULL, then also indicate that it is + the name of a backup file. */ +static void +emit_verbose (char const *src, char const *dst, char const *backup_dst_name) +{ + printf ("%s -> %s", quote_n (0, src), quote_n (1, dst)); + if (backup_dst_name) + printf (_(" (backup: %s)"), quote (backup_dst_name)); + putchar ('\n'); +} + +/* A wrapper around "setfscreatecon (NULL)" that exits upon failure. */ +static void +restore_default_fscreatecon_or_die (void) +{ + if (setfscreatecon (NULL) != 0) + error (EXIT_FAILURE, errno, + _("failed to restore the default file creation context")); +} + +/* Copy the file SRC_NAME to the file DST_NAME. The files may be of + any type. NEW_DST should be true if the file DST_NAME cannot + exist because its parent directory was just created; NEW_DST should + be false if DST_NAME might already exist. DEVICE is the device + number of the parent directory, or 0 if the parent of this file is + not known. ANCESTORS points to a linked, null terminated list of + devices and inodes of parent directories of SRC_NAME. COMMAND_LINE_ARG + is true iff SRC_NAME was specified on the command line. + FIRST_DIR_CREATED_PER_COMMAND_LINE_ARG is both input and output. + Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the + same as) DST_NAME; otherwise, clear it. + Return true if successful. */ +static bool +copy_internal (char const *src_name, char const *dst_name, + bool new_dst, + dev_t device, + struct dir_list *ancestors, + const struct cp_options *x, + bool command_line_arg, + bool *first_dir_created_per_command_line_arg, + bool *copy_into_self, + bool *rename_succeeded) +{ + struct stat src_sb; + struct stat dst_sb; + mode_t src_mode; + mode_t dst_mode IF_LINT (= 0); + mode_t dst_mode_bits; + mode_t omitted_permissions; + bool restore_dst_mode = false; + char *earlier_file = NULL; + char *dst_backup = NULL; + bool backup_succeeded = false; + bool delayed_ok; + bool copied_as_regular = false; + bool dest_is_symlink = false; + bool have_dst_lstat = false; + + if (x->move_mode && rename_succeeded) + *rename_succeeded = false; + + *copy_into_self = false; + + if (XSTAT (x, src_name, &src_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (src_name)); + return false; + } + + src_mode = src_sb.st_mode; + + if (S_ISDIR (src_mode) && !x->recursive) + { + error (0, 0, _("omitting directory %s"), quote (src_name)); + return false; + } + + /* Detect the case in which the same source file appears more than + once on the command line and no backup option has been selected. + If so, simply warn and don't copy it the second time. + This check is enabled only if x->src_info is non-NULL. */ + if (command_line_arg) + { + if ( ! S_ISDIR (src_sb.st_mode) + && x->backup_type == no_backups + && seen_file (x->src_info, src_name, &src_sb)) + { + error (0, 0, _("warning: source file %s specified more than once"), + quote (src_name)); + return true; + } + + record_file (x->src_info, src_name, &src_sb); + } + + if (!new_dst) + { + /* Regular files can be created by writing through symbolic + links, but other files cannot. So use stat on the + destination when copying a regular file, and lstat otherwise. + However, if we intend to unlink or remove the destination + first, use lstat, since a copy won't actually be made to the + destination in that case. */ + bool use_stat = + ((S_ISREG (src_mode) + || (x->copy_as_regular + && ! (S_ISDIR (src_mode) || S_ISLNK (src_mode)))) + && ! (x->move_mode || x->symbolic_link || x->hard_link + || x->backup_type != no_backups + || x->unlink_dest_before_opening)); + if ((use_stat + ? stat (dst_name, &dst_sb) + : lstat (dst_name, &dst_sb)) + != 0) + { + if (errno != ENOENT) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + return false; + } + else + { + new_dst = true; + } + } + else + { /* Here, we know that dst_name exists, at least to the point + that it is stat'able or lstat'able. */ + bool return_now; + bool unlink_src; + + have_dst_lstat = !use_stat; + if (! same_file_ok (src_name, &src_sb, dst_name, &dst_sb, + x, &return_now, &unlink_src)) + { + error (0, 0, _("%s and %s are the same file"), + quote_n (0, src_name), quote_n (1, dst_name)); + return false; + } + + if (!S_ISDIR (src_mode) && x->update) + { + /* When preserving time stamps (but not moving within a file + system), don't worry if the destination time stamp is + less than the source merely because of time stamp + truncation. */ + int options = ((x->preserve_timestamps + && ! (x->move_mode + && dst_sb.st_dev == src_sb.st_dev)) + ? UTIMECMP_TRUNCATE_SOURCE + : 0); + + if (0 <= utimecmp (dst_name, &dst_sb, &src_sb, options)) + { + /* We're using --update and the destination is not older + than the source, so do not copy or move. Pretend the + rename succeeded, so the caller (if it's mv) doesn't + end up removing the source file. */ + if (rename_succeeded) + *rename_succeeded = true; + return true; + } + } + + /* When there is an existing destination file, we may end up + returning early, and hence not copying/moving the file. + This may be due to an interactive `negative' reply to the + prompt about the existing file. It may also be due to the + use of the --reply=no option. + + cp and mv treat -i and -f differently. */ + if (x->move_mode) + { + if (abandon_move (x, dst_name, &dst_sb) + || (unlink_src && unlink (src_name) == 0)) + { + /* Pretend the rename succeeded, so the caller (mv) + doesn't end up removing the source file. */ + if (rename_succeeded) + *rename_succeeded = true; + if (unlink_src && x->verbose) + printf (_("removed %s\n"), quote (src_name)); + return true; + } + if (unlink_src) + { + error (0, errno, _("cannot remove %s"), quote (src_name)); + return false; + } + } + else + { + if (! S_ISDIR (src_mode) + && (x->interactive == I_ALWAYS_NO + || (x->interactive == I_ASK_USER + && (overwrite_prompt (dst_name, &dst_sb), 1) + && ! yesno ()))) + return true; + } + + if (return_now) + return true; + + if (!S_ISDIR (dst_sb.st_mode)) + { + if (S_ISDIR (src_mode)) + { + if (x->move_mode && x->backup_type != no_backups) + { + /* Moving a directory onto an existing + non-directory is ok only with --backup. */ + } + else + { + error (0, 0, + _("cannot overwrite non-directory %s with directory %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + return false; + } + } + + /* Don't let the user destroy their data, even if they try hard: + This mv command must fail (likewise for cp): + rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c + Otherwise, the contents of b/f would be lost. + In the case of `cp', b/f would be lost if the user simulated + a move using cp and rm. + Note that it works fine if you use --backup=numbered. */ + if (command_line_arg + && x->backup_type != numbered_backups + && seen_file (x->dest_info, dst_name, &dst_sb)) + { + error (0, 0, + _("will not overwrite just-created %s with %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + return false; + } + } + + if (!S_ISDIR (src_mode)) + { + if (S_ISDIR (dst_sb.st_mode)) + { + if (x->move_mode && x->backup_type != no_backups) + { + /* Moving a non-directory onto an existing + directory is ok only with --backup. */ + } + else + { + error (0, 0, + _("cannot overwrite directory %s with non-directory"), + quote (dst_name)); + return false; + } + } + } + + if (x->move_mode) + { + /* Don't allow user to move a directory onto a non-directory. */ + if (S_ISDIR (src_sb.st_mode) && !S_ISDIR (dst_sb.st_mode) + && x->backup_type == no_backups) + { + error (0, 0, + _("cannot move directory onto non-directory: %s -> %s"), + quote_n (0, src_name), quote_n (0, dst_name)); + return false; + } + } + + if (x->backup_type != no_backups + /* Don't try to back up a destination if the last + component of src_name is "." or "..". */ + && ! dot_or_dotdot (last_component (src_name)) + /* Create a backup of each destination directory in move mode, + but not in copy mode. FIXME: it might make sense to add an + option to suppress backup creation also for move mode. + That would let one use mv to merge new content into an + existing hierarchy. */ + && (x->move_mode || ! S_ISDIR (dst_sb.st_mode))) + { + char *tmp_backup = find_backup_file_name (dst_name, + x->backup_type); + + /* Detect (and fail) when creating the backup file would + destroy the source file. Before, running the commands + cd /tmp; rm -f a a~; : > a; echo A > a~; cp --b=simple a~ a + would leave two zero-length files: a and a~. */ + /* FIXME: but simply change e.g., the final a~ to `./a~' + and the source will still be destroyed. */ + if (STREQ (tmp_backup, src_name)) + { + const char *fmt; + fmt = (x->move_mode + ? _("backing up %s would destroy source; %s not moved") + : _("backing up %s would destroy source; %s not copied")); + error (0, 0, fmt, + quote_n (0, dst_name), + quote_n (1, src_name)); + free (tmp_backup); + return false; + } + + /* FIXME: use fts: + Using alloca for a file name that may be arbitrarily + long is not recommended. In fact, even forming such a name + should be discouraged. Eventually, this code will be rewritten + to use fts, so using alloca here will be less of a problem. */ + ASSIGN_STRDUPA (dst_backup, tmp_backup); + free (tmp_backup); + if (rename (dst_name, dst_backup) != 0) + { + if (errno != ENOENT) + { + error (0, errno, _("cannot backup %s"), quote (dst_name)); + return false; + } + else + { + dst_backup = NULL; + } + } + else + { + backup_succeeded = true; + } + new_dst = true; + } + else if (! S_ISDIR (dst_sb.st_mode) + /* Never unlink dst_name when in move mode. */ + && ! x->move_mode + && (x->unlink_dest_before_opening + || (x->preserve_links && 1 < dst_sb.st_nlink) + || (x->dereference == DEREF_NEVER + && ! S_ISREG (src_sb.st_mode)) + )) + { + if (unlink (dst_name) != 0 && errno != ENOENT) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + return false; + } + new_dst = true; + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + } + } + } + + /* Ensure we don't try to copy through a symlink that was + created by a prior call to this function. */ + if (command_line_arg + && x->dest_info + && ! x->move_mode + && x->backup_type == no_backups) + { + bool lstat_ok = true; + struct stat tmp_buf; + struct stat *dst_lstat_sb; + + /* If we called lstat above, good: use that data. + Otherwise, call lstat here, in case dst_name is a symlink. */ + if (have_dst_lstat) + dst_lstat_sb = &dst_sb; + else + { + if (lstat (dst_name, &tmp_buf) == 0) + dst_lstat_sb = &tmp_buf; + else + lstat_ok = false; + } + + /* Never copy through a symlink we've just created. */ + if (lstat_ok + && S_ISLNK (dst_lstat_sb->st_mode) + && seen_file (x->dest_info, dst_name, dst_lstat_sb)) + { + error (0, 0, + _("will not copy %s through just-created symlink %s"), + quote_n (0, src_name), quote_n (1, dst_name)); + return false; + } + } + + /* If the source is a directory, we don't always create the destination + directory. So --verbose should not announce anything until we're + sure we'll create a directory. */ + if (x->verbose && !S_ISDIR (src_mode)) + emit_verbose (src_name, dst_name, backup_succeeded ? dst_backup : NULL); + + /* Associate the destination file name with the source device and inode + so that if we encounter a matching dev/ino pair in the source tree + we can arrange to create a hard link between the corresponding names + in the destination tree. + + When using the --link (-l) option, there is no need to take special + measures, because (barring race conditions) files that are hard-linked + in the source tree will also be hard-linked in the destination tree. + + Sometimes, when preserving links, we have to record dev/ino even + though st_nlink == 1: + - when in move_mode, since we may be moving a group of N hard-linked + files (via two or more command line arguments) to a different + partition; the links may be distributed among the command line + arguments (possibly hierarchies) so that the link count of + the final, once-linked source file is reduced to 1 when it is + considered below. But in this case (for mv) we don't need to + incur the expense of recording the dev/ino => name mapping; all we + really need is a lookup, to see if the dev/ino pair has already + been copied. + - when using -H and processing a command line argument; + that command line argument could be a symlink pointing to another + command line argument. With `cp -H --preserve=link', we hard-link + those two destination files. + - likewise for -L except that it applies to all files, not just + command line arguments. + + Also, with --recursive, record dev/ino of each command-line directory. + We'll use that info to detect this problem: cp -R dir dir. */ + + if (x->move_mode && src_sb.st_nlink == 1) + { + earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev); + } + else if (x->preserve_links + && !x->hard_link + && (1 < src_sb.st_nlink + || (command_line_arg + && x->dereference == DEREF_COMMAND_LINE_ARGUMENTS) + || x->dereference == DEREF_ALWAYS)) + { + earlier_file = remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev); + } + else if (x->recursive && S_ISDIR (src_mode)) + { + if (command_line_arg) + earlier_file = remember_copied (dst_name, src_sb.st_ino, src_sb.st_dev); + else + earlier_file = src_to_dest_lookup (src_sb.st_ino, src_sb.st_dev); + } + + /* Did we copy this inode somewhere else (in this command line argument) + and therefore this is a second hard link to the inode? */ + + if (earlier_file) + { + /* Avoid damaging the destination file system by refusing to preserve + hard-linked directories (which are found at least in Netapp snapshot + directories). */ + if (S_ISDIR (src_mode)) + { + /* If src_name and earlier_file refer to the same directory entry, + then warn about copying a directory into itself. */ + if (same_name (src_name, earlier_file)) + { + error (0, 0, _("cannot copy a directory, %s, into itself, %s"), + quote_n (0, top_level_src_name), + quote_n (1, top_level_dst_name)); + *copy_into_self = true; + goto un_backup; + } + else if (x->dereference == DEREF_ALWAYS) + { + /* This happens when e.g., encountering a directory for the + second or subsequent time via symlinks when cp is invoked + with -R and -L. E.g., + rm -rf a b c d; mkdir a b c d; ln -s ../c a; ln -s ../c b; + cp -RL a b d + */ + } + else + { + error (0, 0, _("will not create hard link %s to directory %s"), + quote_n (0, dst_name), quote_n (1, earlier_file)); + goto un_backup; + } + } + else + { + /* We want to guarantee that symlinks are not followed. */ + bool link_failed = (linkat (AT_FDCWD, earlier_file, AT_FDCWD, + dst_name, 0) != 0); + + /* If the link failed because of an existing destination, + remove that file and then call link again. */ + if (link_failed && errno == EEXIST) + { + if (unlink (dst_name) != 0) + { + error (0, errno, _("cannot remove %s"), quote (dst_name)); + goto un_backup; + } + if (x->verbose) + printf (_("removed %s\n"), quote (dst_name)); + link_failed = (linkat (AT_FDCWD, earlier_file, AT_FDCWD, + dst_name, 0) != 0); + } + + if (link_failed) + { + error (0, errno, _("cannot create hard link %s to %s"), + quote_n (0, dst_name), quote_n (1, earlier_file)); + goto un_backup; + } + + return true; + } + } + + if (x->move_mode) + { + if (rename (src_name, dst_name) == 0) + { + if (x->verbose && S_ISDIR (src_mode)) + emit_verbose (src_name, dst_name, + backup_succeeded ? dst_backup : NULL); + + if (rename_succeeded) + *rename_succeeded = true; + + if (command_line_arg) + { + /* Record destination dev/ino/name, so that if we are asked + to overwrite that file again, we can detect it and fail. */ + /* It's fine to use the _source_ stat buffer (src_sb) to get the + _destination_ dev/ino, since the rename above can't have + changed those, and `mv' always uses lstat. + We could limit it further by operating + only on non-directories. */ + record_file (x->dest_info, dst_name, &src_sb); + } + + return true; + } + + /* FIXME: someday, consider what to do when moving a directory into + itself but when source and destination are on different devices. */ + + /* This happens when attempting to rename a directory to a + subdirectory of itself. */ + if (errno == EINVAL) + { + /* FIXME: this is a little fragile in that it relies on rename(2) + failing with a specific errno value. Expect problems on + non-POSIX systems. */ + error (0, 0, _("cannot move %s to a subdirectory of itself, %s"), + quote_n (0, top_level_src_name), + quote_n (1, top_level_dst_name)); + + /* Note that there is no need to call forget_created here, + (compare with the other calls in this file) since the + destination directory didn't exist before. */ + + *copy_into_self = true; + /* FIXME-cleanup: Don't return true here; adjust mv.c accordingly. + The only caller that uses this code (mv.c) ends up setting its + exit status to nonzero when copy_into_self is nonzero. */ + return true; + } + + /* WARNING: there probably exist systems for which an inter-device + rename fails with a value of errno not handled here. + If/as those are reported, add them to the condition below. + If this happens to you, please do the following and send the output + to the bug-reporting address (e.g., in the output of cp --help): + touch k; perl -e 'rename "k","/tmp/k" or print "$!(",$!+0,")\n"' + where your current directory is on one partion and /tmp is the other. + Also, please try to find the E* errno macro name corresponding to + the diagnostic and parenthesized integer, and include that in your + e-mail. One way to do that is to run a command like this + find /usr/include/. -type f \ + | xargs grep 'define.*\.*\<18\>' /dev/null + where you'd replace `18' with the integer in parentheses that + was output from the perl one-liner above. + If necessary, of course, change `/tmp' to some other directory. */ + if (errno != EXDEV) + { + /* There are many ways this can happen due to a race condition. + When something happens between the initial XSTAT and the + subsequent rename, we can get many different types of errors. + For example, if the destination is initially a non-directory + or non-existent, but it is created as a directory, the rename + fails. If two `mv' commands try to rename the same file at + about the same time, one will succeed and the other will fail. + If the permissions on the directory containing the source or + destination file are made too restrictive, the rename will + fail. Etc. */ + error (0, errno, + _("cannot move %s to %s"), + quote_n (0, src_name), quote_n (1, dst_name)); + forget_created (src_sb.st_ino, src_sb.st_dev); + return false; + } + + /* The rename attempt has failed. Remove any existing destination + file so that a cross-device `mv' acts as if it were really using + the rename syscall. */ + if (unlink (dst_name) != 0 && errno != ENOENT) + { + error (0, errno, + _("inter-device move failed: %s to %s; unable to remove target"), + quote_n (0, src_name), quote_n (1, dst_name)); + forget_created (src_sb.st_ino, src_sb.st_dev); + return false; + } + + new_dst = true; + } + + /* If the ownership might change, or if it is a directory (whose + special mode bits may change after the directory is created), + omit some permissions at first, so unauthorized users cannot nip + in before the file is ready. */ + dst_mode_bits = (x->set_mode ? x->mode : src_mode) & CHMOD_MODE_BITS; + omitted_permissions = + (dst_mode_bits + & (x->preserve_ownership ? S_IRWXG | S_IRWXO + : S_ISDIR (src_mode) ? S_IWGRP | S_IWOTH + : 0)); + + delayed_ok = true; + + if (x->preserve_security_context) + { + security_context_t con; + + if (0 <= lgetfilecon (src_name, &con)) + { + if (setfscreatecon (con) < 0) + { + if (!x->reduce_diagnostics || x->require_preserve_context) + error (0, errno, + _("failed to set default file creation context to %s"), + quote (con)); + if (x->require_preserve_context) + { + freecon (con); + return false; + } + } + freecon (con); + } + else + { + if (!errno_unsupported (errno) || x->require_preserve_context) + { + if (!x->reduce_diagnostics || x->require_preserve_context) + error (0, errno, + _("failed to get security context of %s"), + quote (src_name)); + if (x->require_preserve_context) + return false; + } + } + } + + if (S_ISDIR (src_mode)) + { + struct dir_list *dir; + + /* If this directory has been copied before during the + recursion, there is a symbolic link to an ancestor + directory of the symbolic link. It is impossible to + continue to copy this, unless we've got an infinite disk. */ + + if (is_ancestor (&src_sb, ancestors)) + { + error (0, 0, _("cannot copy cyclic symbolic link %s"), + quote (src_name)); + goto un_backup; + } + + /* Insert the current directory in the list of parents. */ + + dir = alloca (sizeof *dir); + dir->parent = ancestors; + dir->ino = src_sb.st_ino; + dir->dev = src_sb.st_dev; + + if (new_dst || !S_ISDIR (dst_sb.st_mode)) + { + /* POSIX says mkdir's behavior is implementation-defined when + (src_mode & ~S_IRWXUGO) != 0. However, common practice is + to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir + decide what to do with S_ISUID | S_ISGID | S_ISVTX. */ + if (mkdir (dst_name, dst_mode_bits & ~omitted_permissions) != 0) + { + error (0, errno, _("cannot create directory %s"), + quote (dst_name)); + goto un_backup; + } + + /* We need search and write permissions to the new directory + for writing the directory's contents. Check if these + permissions are there. */ + + if (lstat (dst_name, &dst_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + goto un_backup; + } + else if ((dst_sb.st_mode & S_IRWXU) != S_IRWXU) + { + /* Make the new directory searchable and writable. */ + + dst_mode = dst_sb.st_mode; + restore_dst_mode = true; + + if (lchmod (dst_name, dst_mode | S_IRWXU) != 0) + { + error (0, errno, _("setting permissions for %s"), + quote (dst_name)); + goto un_backup; + } + } + + /* Record the created directory's inode and device numbers into + the search structure, so that we can avoid copying it again. + Do this only for the first directory that is created for each + source command line argument. */ + if (!*first_dir_created_per_command_line_arg) + { + remember_copied (dst_name, dst_sb.st_ino, dst_sb.st_dev); + *first_dir_created_per_command_line_arg = true; + } + + if (x->verbose) + emit_verbose (src_name, dst_name, NULL); + } + + /* Decide whether to copy the contents of the directory. */ + if (x->one_file_system && device != 0 && device != src_sb.st_dev) + { + /* Here, we are crossing a file system boundary and cp's -x option + is in effect: so don't copy the contents of this directory. */ + } + else + { + /* Copy the contents of the directory. Don't just return if + this fails -- otherwise, the failure to read a single file + in a source directory would cause the containing destination + directory not to have owner/perms set properly. */ + delayed_ok = copy_dir (src_name, dst_name, new_dst, &src_sb, dir, x, + first_dir_created_per_command_line_arg, + copy_into_self); + } + } + else if (x->symbolic_link) + { + dest_is_symlink = true; + if (*src_name != '/') + { + /* Check that DST_NAME denotes a file in the current directory. */ + struct stat dot_sb; + struct stat dst_parent_sb; + char *dst_parent; + bool in_current_dir; + + dst_parent = dir_name (dst_name); + + in_current_dir = (STREQ (".", dst_parent) + /* If either stat call fails, it's ok not to report + the failure and say dst_name is in the current + directory. Other things will fail later. */ + || stat (".", &dot_sb) != 0 + || stat (dst_parent, &dst_parent_sb) != 0 + || SAME_INODE (dot_sb, dst_parent_sb)); + free (dst_parent); + + if (! in_current_dir) + { + error (0, 0, + _("%s: can make relative symbolic links only in current directory"), + quote (dst_name)); + goto un_backup; + } + } + if (symlink (src_name, dst_name) != 0) + { + error (0, errno, _("cannot create symbolic link %s to %s"), + quote_n (0, dst_name), quote_n (1, src_name)); + goto un_backup; + } + } + + /* cp, invoked with `--link --no-dereference', should not follow the + link; we guarantee this with gnulib's linkat module (on systems + where link(2) follows the link, gnulib creates a symlink with + identical contents, which is good enough for our purposes). */ + else if (x->hard_link + && (!S_ISLNK (src_mode) + || x->dereference != DEREF_NEVER)) + { + if (linkat (AT_FDCWD, src_name, AT_FDCWD, dst_name, 0)) + { + error (0, errno, _("cannot create link %s"), quote (dst_name)); + goto un_backup; + } + } + else if (S_ISREG (src_mode) + || (x->copy_as_regular && !S_ISLNK (src_mode))) + { + copied_as_regular = true; + /* POSIX says the permission bits of the source file must be + used as the 3rd argument in the open call. Historical + practice passed all the source mode bits to 'open', but the extra + bits were ignored, so it should be the same either way. */ + if (! copy_reg (src_name, dst_name, x, src_mode & S_IRWXUGO, + omitted_permissions, &new_dst, &src_sb)) + goto un_backup; + } + else if (S_ISFIFO (src_mode)) + { + /* Use mknod, rather than mkfifo, because the former preserves + the special mode bits of a fifo on Solaris 10, while mkfifo + does not. But fall back on mkfifo, because on some BSD systems, + mknod always fails when asked to create a FIFO. */ + if (mknod (dst_name, src_mode & ~omitted_permissions, 0) != 0) + if (mkfifo (dst_name, src_mode & ~S_IFIFO & ~omitted_permissions) != 0) + { + error (0, errno, _("cannot create fifo %s"), quote (dst_name)); + goto un_backup; + } + } + else if (S_ISBLK (src_mode) || S_ISCHR (src_mode) || S_ISSOCK (src_mode)) + { + if (mknod (dst_name, src_mode & ~omitted_permissions, src_sb.st_rdev) + != 0) + { + error (0, errno, _("cannot create special file %s"), + quote (dst_name)); + goto un_backup; + } + } + else if (S_ISLNK (src_mode)) + { + char *src_link_val = areadlink_with_size (src_name, src_sb.st_size); + dest_is_symlink = true; + if (src_link_val == NULL) + { + error (0, errno, _("cannot read symbolic link %s"), quote (src_name)); + goto un_backup; + } + + if (symlink (src_link_val, dst_name) == 0) + free (src_link_val); + else + { + int saved_errno = errno; + bool same_link = false; + if (x->update && !new_dst && S_ISLNK (dst_sb.st_mode) + && dst_sb.st_size == strlen (src_link_val)) + { + /* See if the destination is already the desired symlink. + FIXME: This behavior isn't documented, and seems wrong + in some cases, e.g., if the destination symlink has the + wrong ownership, permissions, or time stamps. */ + char *dest_link_val = + areadlink_with_size (dst_name, dst_sb.st_size); + if (dest_link_val && STREQ (dest_link_val, src_link_val)) + same_link = true; + free (dest_link_val); + } + free (src_link_val); + + if (! same_link) + { + error (0, saved_errno, _("cannot create symbolic link %s"), + quote (dst_name)); + goto un_backup; + } + } + + if (x->preserve_security_context) + restore_default_fscreatecon_or_die (); + + if (x->preserve_ownership) + { + /* Preserve the owner and group of the just-`copied' + symbolic link, if possible. */ + if (HAVE_LCHOWN + && lchown (dst_name, src_sb.st_uid, src_sb.st_gid) != 0 + && ! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + dst_name); + goto un_backup; + } + else + { + /* Can't preserve ownership of symlinks. + FIXME: maybe give a warning or even error for symlinks + in directories with the sticky bit set -- there, not + preserving owner/group is a potential security problem. */ + } + } + } + else + { + error (0, 0, _("%s has unknown file type"), quote (src_name)); + goto un_backup; + } + + if (command_line_arg && x->dest_info) + { + /* Now that the destination file is very likely to exist, + add its info to the set. */ + struct stat sb; + if (lstat (dst_name, &sb) == 0) + record_file (x->dest_info, dst_name, &sb); + } + + /* If we've just created a hard-link due to cp's --link option, + we're done. */ + if (x->hard_link && ! S_ISDIR (src_mode)) + return delayed_ok; + + if (copied_as_regular) + return delayed_ok; + + /* POSIX says that `cp -p' must restore the following: + - permission bits + - setuid, setgid bits + - owner and group + If it fails to restore any of those, we may give a warning but + the destination must not be removed. + FIXME: implement the above. */ + + /* Adjust the times (and if possible, ownership) for the copy. + chown turns off set[ug]id bits for non-root, + so do the chmod last. */ + + if (x->preserve_timestamps) + { + struct timespec timespec[2]; + timespec[0] = get_stat_atime (&src_sb); + timespec[1] = get_stat_mtime (&src_sb); + + if ((dest_is_symlink + ? utimens_symlink (dst_name, timespec) + : utimens (dst_name, timespec)) + != 0) + { + error (0, errno, _("preserving times for %s"), quote (dst_name)); + if (x->require_preserve) + return false; + } + } + + /* The operations beyond this point may dereference a symlink. */ + if (dest_is_symlink) + return delayed_ok; + + /* Avoid calling chown if we know it's not necessary. */ + if (x->preserve_ownership + && (new_dst || !SAME_OWNER_AND_GROUP (src_sb, dst_sb))) + { + switch (set_owner (x, dst_name, -1, &src_sb, new_dst, &dst_sb)) + { + case -1: + return false; + + case 0: + src_mode &= ~ (S_ISUID | S_ISGID | S_ISVTX); + break; + } + } + + set_author (dst_name, -1, &src_sb); + + if (x->preserve_xattr && ! copy_attr_by_name (src_name, dst_name, x) + && x->require_preserve_xattr) + return false; + + if (x->preserve_mode || x->move_mode) + { + if (copy_acl (src_name, -1, dst_name, -1, src_mode) != 0 + && x->require_preserve) + return false; + } + else if (x->set_mode) + { + if (set_acl (dst_name, -1, x->mode) != 0) + return false; + } + else + { + if (omitted_permissions) + { + omitted_permissions &= ~ cached_umask (); + + if (omitted_permissions && !restore_dst_mode) + { + /* Permissions were deliberately omitted when the file + was created due to security concerns. See whether + they need to be re-added now. It'd be faster to omit + the lstat, but deducing the current destination mode + is tricky in the presence of implementation-defined + rules for special mode bits. */ + if (new_dst && lstat (dst_name, &dst_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (dst_name)); + return false; + } + dst_mode = dst_sb.st_mode; + if (omitted_permissions & ~dst_mode) + restore_dst_mode = true; + } + } + + if (restore_dst_mode) + { + if (lchmod (dst_name, dst_mode | omitted_permissions) != 0) + { + error (0, errno, _("preserving permissions for %s"), + quote (dst_name)); + if (x->require_preserve) + return false; + } + } + } + + return delayed_ok; + +un_backup: + + if (x->preserve_security_context) + restore_default_fscreatecon_or_die (); + + /* We have failed to create the destination file. + If we've just added a dev/ino entry via the remember_copied + call above (i.e., unless we've just failed to create a hard link), + remove the entry associating the source dev/ino with the + destination file name, so we don't try to `preserve' a link + to a file we didn't create. */ + if (earlier_file == NULL) + forget_created (src_sb.st_ino, src_sb.st_dev); + + if (dst_backup) + { + if (rename (dst_backup, dst_name) != 0) + error (0, errno, _("cannot un-backup %s"), quote (dst_name)); + else + { + if (x->verbose) + printf (_("%s -> %s (unbackup)\n"), + quote_n (0, dst_backup), quote_n (1, dst_name)); + } + } + return false; +} + +static bool +valid_options (const struct cp_options *co) +{ + assert (co != NULL); + assert (VALID_BACKUP_TYPE (co->backup_type)); + assert (VALID_SPARSE_MODE (co->sparse_mode)); + assert (VALID_REFLINK_MODE (co->reflink_mode)); + assert (!(co->hard_link && co->symbolic_link)); + assert (! + (co->reflink_mode == REFLINK_ALWAYS + && co->sparse_mode != SPARSE_AUTO)); + return true; +} + +/* Copy the file SRC_NAME to the file DST_NAME. The files may be of + any type. NONEXISTENT_DST should be true if the file DST_NAME + is known not to exist (e.g., because its parent directory was just + created); NONEXISTENT_DST should be false if DST_NAME might already + exist. OPTIONS is ... FIXME-describe + Set *COPY_INTO_SELF if SRC_NAME is a parent of (or the + same as) DST_NAME; otherwise, set clear it. + Return true if successful. */ + +extern bool +copy (char const *src_name, char const *dst_name, + bool nonexistent_dst, const struct cp_options *options, + bool *copy_into_self, bool *rename_succeeded) +{ + assert (valid_options (options)); + + /* Record the file names: they're used in case of error, when copying + a directory into itself. I don't like to make these tools do *any* + extra work in the common case when that work is solely to handle + exceptional cases, but in this case, I don't see a way to derive the + top level source and destination directory names where they're used. + An alternative is to use COPY_INTO_SELF and print the diagnostic + from every caller -- but I don't want to do that. */ + top_level_src_name = src_name; + top_level_dst_name = dst_name; + + bool first_dir_created_per_command_line_arg = false; + return copy_internal (src_name, dst_name, nonexistent_dst, 0, NULL, + options, true, + &first_dir_created_per_command_line_arg, + copy_into_self, rename_succeeded); +} + +/* Set *X to the default options for a value of type struct cp_options. */ + +extern void +cp_options_default (struct cp_options *x) +{ + memset (x, 0, sizeof *x); +#ifdef PRIV_FILE_CHOWN + { + priv_set_t *pset = priv_allocset (); + if (!pset) + xalloc_die (); + if (getppriv (PRIV_EFFECTIVE, pset) == 0) + { + x->chown_privileges = priv_ismember (pset, PRIV_FILE_CHOWN); + x->owner_privileges = priv_ismember (pset, PRIV_FILE_OWNER); + } + priv_freeset (pset); + } +#else + x->chown_privileges = x->owner_privileges = (geteuid () == 0); +#endif +} + +/* Return true if it's OK for chown to fail, where errno is + the error number that chown failed with and X is the copying + option set. */ + +extern bool +chown_failure_ok (struct cp_options const *x) +{ + /* If non-root uses -p, it's ok if we can't preserve ownership. + But root probably wants to know, e.g. if NFS disallows it, + or if the target system doesn't support file ownership. */ + + return ((errno == EPERM || errno == EINVAL) && !x->chown_privileges); +} + +/* Similarly, return true if it's OK for chmod and similar operations + to fail, where errno is the error number that chmod failed with and + X is the copying option set. */ + +static bool +owner_failure_ok (struct cp_options const *x) +{ + return ((errno == EPERM || errno == EINVAL) && !x->owner_privileges); +} + +/* Return the user's umask, caching the result. */ + +extern mode_t +cached_umask (void) +{ + static mode_t mask = (mode_t) -1; + if (mask == (mode_t) -1) + { + mask = umask (0); + umask (mask); + } + return mask; +} diff -urNp coreutils-8.0-orig/src/copy.h coreutils-8.0/src/copy.h --- coreutils-8.0-orig/src/copy.h 2009-09-21 14:29:33.000000000 +0200 +++ coreutils-8.0/src/copy.h 2009-10-07 10:10:11.000000000 +0200 @@ -158,6 +158,9 @@ struct cp_options bool preserve_mode; bool preserve_timestamps; + /* If true, attempt to set specified security context */ + bool set_security_context; + /* Enabled for mv, and for cp by the --preserve=links option. If true, attempt to preserve in the destination files any logical hard links between the source files. If used with cp's diff -urNp coreutils-8.0-orig/src/copy.h.orig coreutils-8.0/src/copy.h.orig --- coreutils-8.0-orig/src/copy.h.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/copy.h.orig 2009-09-21 14:29:33.000000000 +0200 @@ -0,0 +1,283 @@ +/* core functions for copying files and directories + Copyright (C) 89, 90, 91, 1995-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Extracted from cp.c and librarified by Jim Meyering. */ + +#ifndef COPY_H +# define COPY_H + +# include +# include "hash.h" + +/* Control creation of sparse files (files with holes). */ +enum Sparse_type +{ + SPARSE_UNUSED, + + /* Never create holes in DEST. */ + SPARSE_NEVER, + + /* This is the default. Use a crude (and sometimes inaccurate) + heuristic to determine if SOURCE has holes. If so, try to create + holes in DEST. */ + SPARSE_AUTO, + + /* For every sufficiently long sequence of bytes in SOURCE, try to + create a corresponding hole in DEST. There is a performance penalty + here because CP has to search for holes in SRC. But if the holes are + big enough, that penalty can be offset by the decrease in the amount + of data written to disk. */ + SPARSE_ALWAYS +}; + +/* Control creation of COW files. */ +enum Reflink_type +{ + /* Default to a standard copy. */ + REFLINK_NEVER, + + /* Try a COW copy and fall back to a standard copy. */ + REFLINK_AUTO, + + /* Require a COW copy and fail if not available. */ + REFLINK_ALWAYS +}; + +/* This type is used to help mv (via copy.c) distinguish these cases. */ +enum Interactive +{ + I_ALWAYS_YES = 1, + I_ALWAYS_NO, + I_ASK_USER, + I_UNSPECIFIED +}; + +/* How to handle symbolic links. */ +enum Dereference_symlink +{ + DEREF_UNDEFINED = 1, + + /* Copy the symbolic link itself. -P */ + DEREF_NEVER, + + /* If the symbolic is a command line argument, then copy + its referent. Otherwise, copy the symbolic link itself. -H */ + DEREF_COMMAND_LINE_ARGUMENTS, + + /* Copy the referent of the symbolic link. -L */ + DEREF_ALWAYS +}; + +# define VALID_SPARSE_MODE(Mode) \ + ((Mode) == SPARSE_NEVER \ + || (Mode) == SPARSE_AUTO \ + || (Mode) == SPARSE_ALWAYS) + +# define VALID_REFLINK_MODE(Mode) \ + ((Mode) == REFLINK_NEVER \ + || (Mode) == REFLINK_AUTO \ + || (Mode) == REFLINK_ALWAYS) + +/* These options control how files are copied by at least the + following programs: mv (when rename doesn't work), cp, install. + So, if you add a new member, be sure to initialize it in + mv.c, cp.c, and install.c. */ +struct cp_options +{ + enum backup_type backup_type; + + /* How to handle symlinks in the source. */ + enum Dereference_symlink dereference; + + /* This value is used to determine whether to prompt before removing + each existing destination file. It works differently depending on + whether move_mode is set. See code/comments in copy.c. */ + enum Interactive interactive; + + /* Control creation of sparse files. */ + enum Sparse_type sparse_mode; + + /* Set the mode of the destination file to exactly this value + if SET_MODE is nonzero. */ + mode_t mode; + + /* If true, copy all files except (directories and, if not dereferencing + them, symbolic links,) as if they were regular files. */ + bool copy_as_regular; + + /* If true, remove each existing destination nondirectory before + trying to open it. */ + bool unlink_dest_before_opening; + + /* If true, first try to open each existing destination nondirectory, + then, if the open fails, unlink and try again. + This option must be set for `cp -f', in case the destination file + exists when the open is attempted. It is irrelevant to `mv' since + any destination is sure to be removed before the open. */ + bool unlink_dest_after_failed_open; + + /* If true, create hard links instead of copying files. + Create destination directories as usual. */ + bool hard_link; + + /* If true, rather than copying, first attempt to use rename. + If that fails, then resort to copying. */ + bool move_mode; + + /* Whether this process has appropriate privileges to chown a file + whose owner is not the effective user ID. */ + bool chown_privileges; + + /* Whether this process has appropriate privileges to do the + following operations on a file even when it is owned by some + other user: set the file's atime, mtime, mode, or ACL; remove or + rename an entry in the file even though it is a sticky directory, + or to mount on the file. */ + bool owner_privileges; + + /* If true, when copying recursively, skip any subdirectories that are + on different file systems from the one we started on. */ + bool one_file_system; + + /* If true, attempt to give the copies the original files' permissions, + owner, group, and timestamps. */ + bool preserve_ownership; + bool preserve_mode; + bool preserve_timestamps; + + /* Enabled for mv, and for cp by the --preserve=links option. + If true, attempt to preserve in the destination files any + logical hard links between the source files. If used with cp's + --no-dereference option, and copying two hard-linked files, + the two corresponding destination files will also be hard linked. + + If used with cp's --dereference (-L) option, then, as that option implies, + hard links are *not* preserved. However, when copying a file F and + a symlink S to F, the resulting S and F in the destination directory + will be hard links to the same file (a copy of F). */ + bool preserve_links; + + /* If true and any of the above (for preserve) file attributes cannot + be applied to a destination file, treat it as a failure and return + nonzero immediately. E.g. for cp -p this must be true, for mv it + must be false. */ + bool require_preserve; + + /* If true, attempt to preserve the SELinux security context, too. + Set this only if the kernel is SELinux enabled. */ + bool preserve_security_context; + + /* Useful only when preserve_security_context is true. + If true, a failed attempt to preserve a file's security context + propagates failure "out" to the caller. If false, a failure to + preserve a file's security context does not change the invoking + application's exit status. Give diagnostics for failed syscalls + regardless of this setting. For example, with "cp --preserve=context" + this flag is "true", while with "cp -a", it is false. That means + "cp -a" attempts to preserve any security context, but does not + fail if it is unable to do so. */ + bool require_preserve_context; + + /* If true, attempt to preserve extended attributes using libattr. + Ignored if coreutils are compiled without xattr support. */ + bool preserve_xattr; + + /* Useful only when preserve_xattr is true. + If true, a failed attempt to preserve file's extended attributes + propagates failure "out" to the caller. If false, a failure to + preserve file's extended attributes does not change the invoking + application's exit status. Give diagnostics for failed syscalls + regardless of this setting. For example, with "cp --preserve=xattr" + this flag is "true", while with "cp --preserve=all", it is false. */ + bool require_preserve_xattr; + + /* Used as difference boolean between cp -a and cp -dR --preserve=all. + If true, non-mandatory failure diagnostics are not displayed. This + should prevent poluting cp -a output. + */ + bool reduce_diagnostics; + + /* If true, copy directories recursively and copy special files + as themselves rather than copying their contents. */ + bool recursive; + + /* If true, set file mode to value of MODE. Otherwise, + set it based on current umask modified by UMASK_KILL. */ + bool set_mode; + + /* If true, create symbolic links instead of copying files. + Create destination directories as usual. */ + bool symbolic_link; + + /* If true, do not copy a nondirectory that has an existing destination + with the same or newer modification time. */ + bool update; + + /* If true, display the names of the files before copying them. */ + bool verbose; + + /* If true, stdin is a tty. */ + bool stdin_tty; + + /* If true, open a dangling destination symlink when not in move_mode. + Otherwise, copy_reg gives a diagnostic (it refuses to write through + such a symlink) and returns false. */ + bool open_dangling_dest_symlink; + + /* Control creation of COW files. */ + enum Reflink_type reflink_mode; + + /* This is a set of destination name/inode/dev triples. Each such triple + represents a file we have created corresponding to a source file name + that was specified on the command line. Use it to avoid clobbering + source files in commands like this: + rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c + For now, it protects only regular files when copying (i.e. not renaming). + When renaming, it protects all non-directories. + Use dest_info_init to initialize it, or set it to NULL to disable + this feature. */ + Hash_table *dest_info; + + /* FIXME */ + Hash_table *src_info; +}; + +# define XSTAT(X, Src_name, Src_sb) \ + ((X)->dereference == DEREF_NEVER \ + ? lstat (Src_name, Src_sb) \ + : stat (Src_name, Src_sb)) + +/* Arrange to make rename calls go through the wrapper function + on systems with a rename function that fails for a source file name + specified with a trailing slash. */ +# if RENAME_TRAILING_SLASH_BUG +int rpl_rename (const char *, const char *); +# undef rename +# define rename rpl_rename +# endif + +bool copy (char const *src_name, char const *dst_name, + bool nonexistent_dst, const struct cp_options *options, + bool *copy_into_self, bool *rename_succeeded); + +void dest_info_init (struct cp_options *); +void src_info_init (struct cp_options *); + +void cp_options_default (struct cp_options *); +bool chown_failure_ok (struct cp_options const *); +mode_t cached_umask (void); + +#endif diff -urNp coreutils-8.0-orig/src/cp.c coreutils-8.0/src/cp.c --- coreutils-8.0-orig/src/cp.c 2009-09-29 15:27:54.000000000 +0200 +++ coreutils-8.0/src/cp.c 2009-10-07 10:10:11.000000000 +0200 @@ -139,6 +139,7 @@ static struct option const long_opts[] = {"target-directory", required_argument, NULL, 't'}, {"update", no_argument, NULL, 'u'}, {"verbose", no_argument, NULL, 'v'}, + {"context", required_argument, NULL, 'Z'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -197,6 +198,9 @@ Mandatory arguments to long options are all\n\ "), stdout); fputs (_("\ + -c same as --preserve=context\n\ +"), stdout); + fputs (_("\ --no-preserve=ATTR_LIST don't preserve the specified attributes\n\ --parents use full source file name under DIRECTORY\n\ "), stdout); @@ -223,6 +227,7 @@ Mandatory arguments to long options are destination file is missing\n\ -v, --verbose explain what is being done\n\ -x, --one-file-system stay on this file system\n\ + -Z, --context=CONTEXT set security context of copy to CONTEXT\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); @@ -777,6 +782,7 @@ cp_option_init (struct cp_options *x) x->preserve_timestamps = false; x->preserve_security_context = false; x->require_preserve_context = false; + x->set_security_context = false; x->preserve_xattr = false; x->reduce_diagnostics = false; x->require_preserve_xattr = false; @@ -923,7 +929,7 @@ main (int argc, char **argv) we'll actually use backup_suffix_string. */ backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); - while ((c = getopt_long (argc, argv, "abdfHilLnprst:uvxPRS:T", + while ((c = getopt_long (argc, argv, "abcdfHilLnprst:uvxPRS:TZ:", long_opts, NULL)) != -1) { @@ -966,6 +972,16 @@ main (int argc, char **argv) copy_contents = true; break; + case 'c': + if ( x.set_security_context ) { + (void) fprintf(stderr, "%s: cannot force target context and preserve it\n", argv[0]); + exit( 1 ); + } + else if (selinux_enabled) { + x.preserve_security_context = true; + x.require_preserve_context = true; + } + break; case 'd': x.preserve_links = true; x.dereference = DEREF_NEVER; @@ -1075,6 +1091,27 @@ main (int argc, char **argv) x.one_file_system = true; break; + + case 'Z': + /* politely decline if we're not on a selinux-enabled kernel. */ + if( !selinux_enabled ) { + fprintf( stderr, "Warning: ignoring --context (-Z). " + "It requires a SELinux enabled kernel.\n" ); + break; + } + if ( x.preserve_security_context ) { + (void) fprintf(stderr, "%s: cannot force target context to '%s' and preserve it\n", argv[0], optarg); + exit( 1 ); + } + x.set_security_context = true; + /* if there's a security_context given set new path + components to that context, too */ + if ( setfscreatecon(optarg) < 0 ) { + (void) fprintf(stderr, _("cannot set default security context %s\n"), optarg); + exit( 1 ); + } + break; + case 'S': make_backups = true; backup_suffix_string = optarg; diff -urNp coreutils-8.0-orig/src/cp.c.orig coreutils-8.0/src/cp.c.orig --- coreutils-8.0-orig/src/cp.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/cp.c.orig 2009-09-29 15:27:54.000000000 +0200 @@ -0,0 +1,1160 @@ +/* cp.c -- file copying (main routines) + Copyright (C) 89, 90, 91, 1995-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . + + Written by Torbjorn Granlund, David MacKenzie, and Jim Meyering. */ + +#include +#include +#include +#include +#include + +#include "system.h" +#include "argmatch.h" +#include "backupfile.h" +#include "copy.h" +#include "cp-hash.h" +#include "error.h" +#include "filenamecat.h" +#include "ignore-value.h" +#include "quote.h" +#include "stat-time.h" +#include "utimens.h" +#include "acl.h" + +#if ! HAVE_LCHOWN +# define lchown(name, uid, gid) chown (name, uid, gid) +#endif + +#define ASSIGN_BASENAME_STRDUPA(Dest, File_name) \ + do \ + { \ + char *tmp_abns_; \ + ASSIGN_STRDUPA (tmp_abns_, (File_name)); \ + Dest = last_component (tmp_abns_); \ + strip_trailing_slashes (Dest); \ + } \ + while (0) + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "cp" + +#define AUTHORS \ + proper_name_utf8 ("Torbjorn Granlund", "Torbj\303\266rn Granlund"), \ + proper_name ("David MacKenzie"), \ + proper_name ("Jim Meyering") + +/* Used by do_copy, make_dir_parents_private, and re_protect + to keep a list of leading directories whose protections + need to be fixed after copying. */ +struct dir_attr +{ + struct stat st; + bool restore_mode; + size_t slash_offset; + struct dir_attr *next; +}; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + COPY_CONTENTS_OPTION = CHAR_MAX + 1, + NO_PRESERVE_ATTRIBUTES_OPTION, + PARENTS_OPTION, + PRESERVE_ATTRIBUTES_OPTION, + REFLINK_OPTION, + SPARSE_OPTION, + STRIP_TRAILING_SLASHES_OPTION, + UNLINK_DEST_BEFORE_OPENING +}; + +/* True if the kernel is SELinux enabled. */ +static bool selinux_enabled; + +/* If true, the command "cp x/e_file e_dir" uses "e_dir/x/e_file" + as its destination instead of the usual "e_dir/e_file." */ +static bool parents_option = false; + +/* Remove any trailing slashes from each SOURCE argument. */ +static bool remove_trailing_slashes; + +static char const *const sparse_type_string[] = +{ + "never", "auto", "always", NULL +}; +static enum Sparse_type const sparse_type[] = +{ + SPARSE_NEVER, SPARSE_AUTO, SPARSE_ALWAYS +}; +ARGMATCH_VERIFY (sparse_type_string, sparse_type); + +static char const *const reflink_type_string[] = +{ + "auto", "always", NULL +}; +static enum Reflink_type const reflink_type[] = +{ + REFLINK_AUTO, REFLINK_ALWAYS +}; +ARGMATCH_VERIFY (reflink_type_string, reflink_type); + +static struct option const long_opts[] = +{ + {"archive", no_argument, NULL, 'a'}, + {"backup", optional_argument, NULL, 'b'}, + {"copy-contents", no_argument, NULL, COPY_CONTENTS_OPTION}, + {"dereference", no_argument, NULL, 'L'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"link", no_argument, NULL, 'l'}, + {"no-clobber", no_argument, NULL, 'n'}, + {"no-dereference", no_argument, NULL, 'P'}, + {"no-preserve", required_argument, NULL, NO_PRESERVE_ATTRIBUTES_OPTION}, + {"no-target-directory", no_argument, NULL, 'T'}, + {"one-file-system", no_argument, NULL, 'x'}, + {"parents", no_argument, NULL, PARENTS_OPTION}, + {"path", no_argument, NULL, PARENTS_OPTION}, /* Deprecated. */ + {"preserve", optional_argument, NULL, PRESERVE_ATTRIBUTES_OPTION}, + {"recursive", no_argument, NULL, 'R'}, + {"remove-destination", no_argument, NULL, UNLINK_DEST_BEFORE_OPENING}, + {"sparse", required_argument, NULL, SPARSE_OPTION}, + {"reflink", optional_argument, NULL, REFLINK_OPTION}, + {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, + {"suffix", required_argument, NULL, 'S'}, + {"symbolic-link", no_argument, NULL, 's'}, + {"target-directory", required_argument, NULL, 't'}, + {"update", no_argument, NULL, 'u'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] SOURCE DEST\n\ + or: %s [OPTION]... SOURCE... DIRECTORY\n\ + or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --archive same as -dR --preserve=all\n\ + --backup[=CONTROL] make a backup of each existing destination file\n\ + -b like --backup but does not accept an argument\n\ + --copy-contents copy contents of special files when recursive\n\ + -d same as --no-dereference --preserve=links\n\ +"), stdout); + fputs (_("\ + -f, --force if an existing destination file cannot be\n\ + opened, remove it and try again (redundant if\n\ + the -n option is used)\n\ + -i, --interactive prompt before overwrite (overrides a previous -n\n\ + option)\n\ + -H follow command-line symbolic links in SOURCE\n\ +"), stdout); + fputs (_("\ + -l, --link link files instead of copying\n\ + -L, --dereference always follow symbolic links in SOURCE\n\ +"), stdout); + fputs (_("\ + -n, --no-clobber do not overwrite an existing file (overrides\n\ + a previous -i option)\n\ + -P, --no-dereference never follow symbolic links in SOURCE\n\ +"), stdout); + fputs (_("\ + -p same as --preserve=mode,ownership,timestamps\n\ + --preserve[=ATTR_LIST] preserve the specified attributes (default:\n\ + mode,ownership,timestamps), if possible\n\ + additional attributes: context, links, xattr,\n\ + all\n\ +"), stdout); + fputs (_("\ + --no-preserve=ATTR_LIST don't preserve the specified attributes\n\ + --parents use full source file name under DIRECTORY\n\ +"), stdout); + fputs (_("\ + -R, -r, --recursive copy directories recursively\n\ + --reflink[=WHEN] control clone/CoW copies. See below.\n\ + --remove-destination remove each existing destination file before\n\ + attempting to open it (contrast with --force)\n\ +"), stdout); + fputs (_("\ + --sparse=WHEN control creation of sparse files. See below.\n\ + --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ + argument\n\ +"), stdout); + fputs (_("\ + -s, --symbolic-link make symbolic links instead of copying\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\ +"), stdout); + fputs (_("\ + -u, --update copy only when the SOURCE file is newer\n\ + than the destination file or when the\n\ + destination file is missing\n\ + -v, --verbose explain what is being done\n\ + -x, --one-file-system stay on this file system\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +By default, sparse SOURCE files are detected by a crude heuristic and the\n\ +corresponding DEST file is made sparse as well. That is the behavior\n\ +selected by --sparse=auto. Specify --sparse=always to create a sparse DEST\n\ +file whenever the SOURCE file contains a long enough sequence of zero bytes.\n\ +Use --sparse=never to inhibit creation of sparse files.\n\ +\n\ +When --reflink[=always] is specified, perform a lightweight copy, where the\n\ +data blocks are copied only when modified. If this is not possible the copy\n\ +fails, or if --reflink=auto is specified, fall back to a standard copy.\n\ +"), stdout); + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + fputs (_("\ +\n\ +As a special case, cp makes a backup of SOURCE when the force and backup\n\ +options are given and SOURCE and DEST are the same name for an existing,\n\ +regular file.\n\ +"), stdout); + emit_ancillary_info (); + } + exit (status); +} + +/* Ensure that the parent directories of CONST_DST_NAME have the + correct protections, for the --parents option. This is done + after all copying has been completed, to allow permissions + that don't include user write/execute. + + SRC_OFFSET is the index in CONST_DST_NAME of the beginning of the + source directory name. + + ATTR_LIST is a null-terminated linked list of structures that + indicates the end of the filename of each intermediate directory + in CONST_DST_NAME that may need to have its attributes changed. + The command `cp --parents --preserve a/b/c d/e_dir' changes the + attributes of the directories d/e_dir/a and d/e_dir/a/b to match + the corresponding source directories regardless of whether they + existed before the `cp' command was given. + + Return true if the parent of CONST_DST_NAME and any intermediate + directories specified by ATTR_LIST have the proper permissions + when done. */ + +static bool +re_protect (char const *const_dst_name, size_t src_offset, + struct dir_attr *attr_list, const struct cp_options *x) +{ + struct dir_attr *p; + char *dst_name; /* A copy of CONST_DST_NAME we can change. */ + char *src_name; /* The source name in `dst_name'. */ + + ASSIGN_STRDUPA (dst_name, const_dst_name); + src_name = dst_name + src_offset; + + for (p = attr_list; p; p = p->next) + { + dst_name[p->slash_offset] = '\0'; + + /* Adjust the times (and if possible, ownership) for the copy. + chown turns off set[ug]id bits for non-root, + so do the chmod last. */ + + if (x->preserve_timestamps) + { + struct timespec timespec[2]; + + timespec[0] = get_stat_atime (&p->st); + timespec[1] = get_stat_mtime (&p->st); + + if (utimens (dst_name, timespec)) + { + error (0, errno, _("failed to preserve times for %s"), + quote (dst_name)); + return false; + } + } + + if (x->preserve_ownership) + { + if (lchown (dst_name, p->st.st_uid, p->st.st_gid) != 0) + { + if (! chown_failure_ok (x)) + { + error (0, errno, _("failed to preserve ownership for %s"), + quote (dst_name)); + return false; + } + /* Failing to preserve ownership is OK. Still, try to preserve + the group, but ignore the possible error. */ + ignore_value (lchown (dst_name, -1, p->st.st_gid)); + } + } + + if (x->preserve_mode) + { + if (copy_acl (src_name, -1, dst_name, -1, p->st.st_mode) != 0) + return false; + } + else if (p->restore_mode) + { + if (lchmod (dst_name, p->st.st_mode) != 0) + { + error (0, errno, _("failed to preserve permissions for %s"), + quote (dst_name)); + return false; + } + } + + dst_name[p->slash_offset] = '/'; + } + return true; +} + +/* Ensure that the parent directory of CONST_DIR exists, for + the --parents option. + + SRC_OFFSET is the index in CONST_DIR (which is a destination + directory) of the beginning of the source directory name. + Create any leading directories that don't already exist. + If VERBOSE_FMT_STRING is nonzero, use it as a printf format + string for printing a message after successfully making a directory. + The format should take two string arguments: the names of the + source and destination directories. + Creates a linked list of attributes of intermediate directories, + *ATTR_LIST, for re_protect to use after calling copy. + Sets *NEW_DST if this function creates parent of CONST_DIR. + + Return true if parent of CONST_DIR exists as a directory with the proper + permissions when done. */ + +/* FIXME: Synch this function with the one in ../lib/mkdir-p.c. */ + +static bool +make_dir_parents_private (char const *const_dir, size_t src_offset, + char const *verbose_fmt_string, + struct dir_attr **attr_list, bool *new_dst, + const struct cp_options *x) +{ + struct stat stats; + char *dir; /* A copy of CONST_DIR we can change. */ + char *src; /* Source name in DIR. */ + char *dst_dir; /* Leading directory of DIR. */ + size_t dirlen; /* Length of DIR. */ + + ASSIGN_STRDUPA (dir, const_dir); + + src = dir + src_offset; + + dirlen = dir_len (dir); + dst_dir = alloca (dirlen + 1); + memcpy (dst_dir, dir, dirlen); + dst_dir[dirlen] = '\0'; + + *attr_list = NULL; + + if (stat (dst_dir, &stats) != 0) + { + /* A parent of CONST_DIR does not exist. + Make all missing intermediate directories. */ + char *slash; + + slash = src; + while (*slash == '/') + slash++; + while ((slash = strchr (slash, '/'))) + { + struct dir_attr *new IF_LINT (= NULL); + bool missing_dir; + + *slash = '\0'; + missing_dir = (stat (dir, &stats) != 0); + + if (missing_dir | x->preserve_ownership | x->preserve_mode + | x->preserve_timestamps) + { + /* Add this directory to the list of directories whose + modes might need fixing later. */ + struct stat src_st; + int src_errno = (stat (src, &src_st) != 0 + ? errno + : S_ISDIR (src_st.st_mode) + ? 0 + : ENOTDIR); + if (src_errno) + { + error (0, src_errno, _("failed to get attributes of %s"), + quote (src)); + return false; + } + + new = xmalloc (sizeof *new); + new->st = src_st; + new->slash_offset = slash - dir; + new->restore_mode = false; + new->next = *attr_list; + *attr_list = new; + } + + if (missing_dir) + { + mode_t src_mode; + mode_t omitted_permissions; + mode_t mkdir_mode; + + /* This component does not exist. We must set + *new_dst and new->st.st_mode inside this loop because, + for example, in the command `cp --parents ../a/../b/c e_dir', + make_dir_parents_private creates only e_dir/../a if + ./b already exists. */ + *new_dst = true; + src_mode = new->st.st_mode; + + /* If the ownership or special mode bits might change, + omit some permissions at first, so unauthorized users + cannot nip in before the file is ready. */ + omitted_permissions = (src_mode + & (x->preserve_ownership + ? S_IRWXG | S_IRWXO + : x->preserve_mode + ? S_IWGRP | S_IWOTH + : 0)); + + /* POSIX says mkdir's behavior is implementation-defined when + (src_mode & ~S_IRWXUGO) != 0. However, common practice is + to ask mkdir to copy all the CHMOD_MODE_BITS, letting mkdir + decide what to do with S_ISUID | S_ISGID | S_ISVTX. */ + mkdir_mode = src_mode & CHMOD_MODE_BITS & ~omitted_permissions; + if (mkdir (dir, mkdir_mode) != 0) + { + error (0, errno, _("cannot make directory %s"), + quote (dir)); + return false; + } + else + { + if (verbose_fmt_string != NULL) + printf (verbose_fmt_string, src, dir); + } + + /* We need search and write permissions to the new directory + for writing the directory's contents. Check if these + permissions are there. */ + + if (lstat (dir, &stats)) + { + error (0, errno, _("failed to get attributes of %s"), + quote (dir)); + return false; + } + + + if (! x->preserve_mode) + { + if (omitted_permissions & ~stats.st_mode) + omitted_permissions &= ~ cached_umask (); + if (omitted_permissions & ~stats.st_mode + || (stats.st_mode & S_IRWXU) != S_IRWXU) + { + new->st.st_mode = stats.st_mode | omitted_permissions; + new->restore_mode = true; + } + } + + if ((stats.st_mode & S_IRWXU) != S_IRWXU) + { + /* Make the new directory searchable and writable. + The original permissions will be restored later. */ + + if (lchmod (dir, stats.st_mode | S_IRWXU) != 0) + { + error (0, errno, _("setting permissions for %s"), + quote (dir)); + return false; + } + } + } + else if (!S_ISDIR (stats.st_mode)) + { + error (0, 0, _("%s exists but is not a directory"), + quote (dir)); + return false; + } + else + *new_dst = false; + *slash++ = '/'; + + /* Avoid unnecessary calls to `stat' when given + file names containing multiple adjacent slashes. */ + while (*slash == '/') + slash++; + } + } + + /* We get here if the parent of DIR already exists. */ + + else if (!S_ISDIR (stats.st_mode)) + { + error (0, 0, _("%s exists but is not a directory"), quote (dst_dir)); + return false; + } + else + { + *new_dst = false; + } + return true; +} + +/* FILE is the last operand of this command. + Return true if FILE is a directory. + But report an error and exit if there is a problem accessing FILE, + or if FILE does not exist but would have to refer to an existing + directory if it referred to anything at all. + + If the file exists, store the file's status into *ST. + Otherwise, set *NEW_DST. */ + +static bool +target_directory_operand (char const *file, struct stat *st, bool *new_dst) +{ + int err = (stat (file, st) == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st->st_mode); + if (err) + { + if (err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + *new_dst = true; + } + return is_a_dir; +} + +/* Scan the arguments, and copy each by calling copy. + Return true if successful. */ + +static bool +do_copy (int n_files, char **file, const char *target_directory, + bool no_target_directory, struct cp_options *x) +{ + struct stat sb; + bool new_dst = false; + bool ok = true; + + if (n_files <= !target_directory) + { + if (n_files <= 0) + error (0, 0, _("missing file operand")); + else + error (0, 0, _("missing destination file operand after %s"), + quote (file[0])); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("cannot combine --target-directory (-t) " + "and --no-target-directory (-T)")); + if (2 < n_files) + { + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (!target_directory) + { + if (2 <= n_files + && target_directory_operand (file[n_files - 1], &sb, &new_dst)) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (target_directory) + { + /* cp file1...filen edir + Copy the files `file1' through `filen' + to the existing directory `edir'. */ + int i; + + /* Initialize these hash tables only if we'll need them. + The problems they're used to detect can arise only if + there are two or more files to copy. */ + if (2 <= n_files) + { + dest_info_init (x); + src_info_init (x); + } + + for (i = 0; i < n_files; i++) + { + char *dst_name; + bool parent_exists = true; /* True if dir_name (dst_name) exists. */ + struct dir_attr *attr_list; + char *arg_in_concat = NULL; + char *arg = file[i]; + + /* Trailing slashes are meaningful (i.e., maybe worth preserving) + only in the source file names. */ + if (remove_trailing_slashes) + strip_trailing_slashes (arg); + + if (parents_option) + { + char *arg_no_trailing_slash; + + /* Use `arg' without trailing slashes in constructing destination + file names. Otherwise, we can end up trying to create a + directory via `mkdir ("dst/foo/"...', which is not portable. + It fails, due to the trailing slash, on at least + NetBSD 1.[34] systems. */ + ASSIGN_STRDUPA (arg_no_trailing_slash, arg); + strip_trailing_slashes (arg_no_trailing_slash); + + /* Append all of `arg' (minus any trailing slash) to `dest'. */ + dst_name = file_name_concat (target_directory, + arg_no_trailing_slash, + &arg_in_concat); + + /* For --parents, we have to make sure that the directory + dir_name (dst_name) exists. We may have to create a few + leading directories. */ + parent_exists = + (make_dir_parents_private + (dst_name, arg_in_concat - dst_name, + (x->verbose ? "%s -> %s\n" : NULL), + &attr_list, &new_dst, x)); + } + else + { + char *arg_base; + /* Append the last component of `arg' to `target_directory'. */ + + ASSIGN_BASENAME_STRDUPA (arg_base, arg); + /* For `cp -R source/.. dest', don't copy into `dest/..'. */ + dst_name = (STREQ (arg_base, "..") + ? xstrdup (target_directory) + : file_name_concat (target_directory, arg_base, + NULL)); + } + + if (!parent_exists) + { + /* make_dir_parents_private failed, so don't even + attempt the copy. */ + ok = false; + } + else + { + bool copy_into_self; + ok &= copy (arg, dst_name, new_dst, x, ©_into_self, NULL); + + if (parents_option) + ok &= re_protect (dst_name, arg_in_concat - dst_name, + attr_list, x); + } + + if (parents_option) + { + while (attr_list) + { + struct dir_attr *p = attr_list; + attr_list = attr_list->next; + free (p); + } + } + + free (dst_name); + } + } + else /* !target_directory */ + { + char const *new_dest; + char const *source = file[0]; + char const *dest = file[1]; + bool unused; + + if (parents_option) + { + error (0, 0, + _("with --parents, the destination must be a directory")); + usage (EXIT_FAILURE); + } + + /* When the force and backup options have been specified and + the source and destination are the same name for an existing + regular file, convert the user's command, e.g., + `cp --force --backup foo foo' to `cp --force foo fooSUFFIX' + where SUFFIX is determined by any version control options used. */ + + if (x->unlink_dest_after_failed_open + && x->backup_type != no_backups + && STREQ (source, dest) + && !new_dst && S_ISREG (sb.st_mode)) + { + static struct cp_options x_tmp; + + new_dest = find_backup_file_name (dest, x->backup_type); + /* Set x->backup_type to `no_backups' so that the normal backup + mechanism is not used when performing the actual copy. + backup_type must be set to `no_backups' only *after* the above + call to find_backup_file_name -- that function uses + backup_type to determine the suffix it applies. */ + x_tmp = *x; + x_tmp.backup_type = no_backups; + x = &x_tmp; + } + else + { + new_dest = dest; + } + + ok = copy (source, new_dest, 0, x, &unused, NULL); + } + + return ok; +} + +static void +cp_option_init (struct cp_options *x) +{ + cp_options_default (x); + x->copy_as_regular = true; + x->dereference = DEREF_UNDEFINED; + x->unlink_dest_before_opening = false; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = false; + x->one_file_system = false; + x->reflink_mode = REFLINK_NEVER; + + x->preserve_ownership = false; + x->preserve_links = false; + x->preserve_mode = false; + x->preserve_timestamps = false; + x->preserve_security_context = false; + x->require_preserve_context = false; + x->preserve_xattr = false; + x->reduce_diagnostics = false; + x->require_preserve_xattr = false; + + x->require_preserve = false; + x->recursive = false; + x->sparse_mode = SPARSE_AUTO; + x->symbolic_link = false; + x->set_mode = false; + x->mode = 0; + + /* Not used. */ + x->stdin_tty = false; + + x->update = false; + x->verbose = false; + + /* By default, refuse to open a dangling destination symlink, because + in general one cannot do that safely, give the current semantics of + open's O_EXCL flag, (which POSIX doesn't even allow cp to use, btw). + But POSIX requires it. */ + x->open_dangling_dest_symlink = getenv ("POSIXLY_CORRECT") != NULL; + + x->dest_info = NULL; + x->src_info = NULL; +} + +/* Given a string, ARG, containing a comma-separated list of arguments + to the --preserve option, set the appropriate fields of X to ON_OFF. */ +static void +decode_preserve_arg (char const *arg, struct cp_options *x, bool on_off) +{ + enum File_attribute + { + PRESERVE_MODE, + PRESERVE_TIMESTAMPS, + PRESERVE_OWNERSHIP, + PRESERVE_LINK, + PRESERVE_CONTEXT, + PRESERVE_XATTR, + PRESERVE_ALL + }; + static enum File_attribute const preserve_vals[] = + { + PRESERVE_MODE, PRESERVE_TIMESTAMPS, + PRESERVE_OWNERSHIP, PRESERVE_LINK, PRESERVE_CONTEXT, PRESERVE_XATTR, + PRESERVE_ALL + }; + /* Valid arguments to the `--preserve' option. */ + static char const* const preserve_args[] = + { + "mode", "timestamps", + "ownership", "links", "context", "xattr", "all", NULL + }; + ARGMATCH_VERIFY (preserve_args, preserve_vals); + + char *arg_writable = xstrdup (arg); + char *s = arg_writable; + do + { + /* find next comma */ + char *comma = strchr (s, ','); + enum File_attribute val; + + /* If we found a comma, put a NUL in its place and advance. */ + if (comma) + *comma++ = 0; + + /* process S. */ + val = XARGMATCH ("--preserve", s, preserve_args, preserve_vals); + switch (val) + { + case PRESERVE_MODE: + x->preserve_mode = on_off; + break; + + case PRESERVE_TIMESTAMPS: + x->preserve_timestamps = on_off; + break; + + case PRESERVE_OWNERSHIP: + x->preserve_ownership = on_off; + break; + + case PRESERVE_LINK: + x->preserve_links = on_off; + break; + + case PRESERVE_CONTEXT: + x->preserve_security_context = on_off; + x->require_preserve_context = on_off; + break; + + case PRESERVE_XATTR: + x->preserve_xattr = on_off; + x->require_preserve_xattr = on_off; + break; + + case PRESERVE_ALL: + x->preserve_mode = on_off; + x->preserve_timestamps = on_off; + x->preserve_ownership = on_off; + x->preserve_links = on_off; + if (selinux_enabled) + x->preserve_security_context = on_off; + x->preserve_xattr = on_off; + break; + + default: + abort (); + } + s = comma; + } + while (s); + + free (arg_writable); +} + +int +main (int argc, char **argv) +{ + int c; + bool ok; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + struct cp_options x; + bool copy_contents = false; + char *target_directory = NULL; + bool no_target_directory = false; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdin); + + selinux_enabled = (0 < is_selinux_enabled ()); + cp_option_init (&x); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + while ((c = getopt_long (argc, argv, "abdfHilLnprst:uvxPRS:T", + long_opts, NULL)) + != -1) + { + switch (c) + { + case SPARSE_OPTION: + x.sparse_mode = XARGMATCH ("--sparse", optarg, + sparse_type_string, sparse_type); + break; + + case REFLINK_OPTION: + if (optarg == NULL) + x.reflink_mode = REFLINK_ALWAYS; + else + x.reflink_mode = XARGMATCH ("--reflink", optarg, + reflink_type_string, reflink_type); + break; + + case 'a': /* Like -dR --preserve=all with reduced failure diagnostics. */ + x.dereference = DEREF_NEVER; + x.preserve_links = true; + x.preserve_ownership = true; + x.preserve_mode = true; + x.preserve_timestamps = true; + x.require_preserve = true; + if (selinux_enabled) + x.preserve_security_context = true; + x.preserve_xattr = true; + x.reduce_diagnostics = true; + x.recursive = true; + break; + + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + + case COPY_CONTENTS_OPTION: + copy_contents = true; + break; + + case 'd': + x.preserve_links = true; + x.dereference = DEREF_NEVER; + break; + + case 'f': + x.unlink_dest_after_failed_open = true; + break; + + case 'H': + x.dereference = DEREF_COMMAND_LINE_ARGUMENTS; + break; + + case 'i': + x.interactive = I_ASK_USER; + break; + + case 'l': + x.hard_link = true; + break; + + case 'L': + x.dereference = DEREF_ALWAYS; + break; + + case 'n': + x.interactive = I_ALWAYS_NO; + break; + + case 'P': + x.dereference = DEREF_NEVER; + break; + + case NO_PRESERVE_ATTRIBUTES_OPTION: + decode_preserve_arg (optarg, &x, false); + break; + + case PRESERVE_ATTRIBUTES_OPTION: + if (optarg == NULL) + { + /* Fall through to the case for `p' below. */ + } + else + { + decode_preserve_arg (optarg, &x, true); + x.require_preserve = true; + break; + } + + case 'p': + x.preserve_ownership = true; + x.preserve_mode = true; + x.preserve_timestamps = true; + x.require_preserve = true; + break; + + case PARENTS_OPTION: + parents_option = true; + break; + + case 'r': + case 'R': + x.recursive = true; + break; + + case UNLINK_DEST_BEFORE_OPENING: + x.unlink_dest_before_opening = true; + break; + + case STRIP_TRAILING_SLASHES_OPTION: + remove_trailing_slashes = true; + break; + + case 's': + x.symbolic_link = true; + break; + + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, + _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + + case 'T': + no_target_directory = true; + break; + + case 'u': + x.update = true; + break; + + case 'v': + x.verbose = true; + break; + + case 'x': + x.one_file_system = true; + break; + + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (x.hard_link && x.symbolic_link) + { + error (0, 0, _("cannot make both hard and symbolic links")); + usage (EXIT_FAILURE); + } + + if (make_backups && x.interactive == I_ALWAYS_NO) + { + error (0, 0, + _("options --backup and --no-clobber are mutually exclusive")); + usage (EXIT_FAILURE); + } + + if (x.reflink_mode == REFLINK_ALWAYS && x.sparse_mode != SPARSE_AUTO) + { + error (0, 0, _("--reflink can be used only with --sparse=auto")); + usage (EXIT_FAILURE); + } + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + x.backup_type = (make_backups + ? xget_version (_("backup type"), + version_control_string) + : no_backups); + + if (x.dereference == DEREF_UNDEFINED) + { + if (x.recursive) + /* This is compatible with FreeBSD. */ + x.dereference = DEREF_NEVER; + else + x.dereference = DEREF_ALWAYS; + } + + if (x.recursive) + x.copy_as_regular = copy_contents; + + /* If --force (-f) was specified and we're in link-creation mode, + first remove any existing destination file. */ + if (x.unlink_dest_after_failed_open && (x.hard_link || x.symbolic_link)) + x.unlink_dest_before_opening = true; + + if (x.preserve_security_context) + { + if (!selinux_enabled) + error (EXIT_FAILURE, 0, + _("cannot preserve security context " + "without an SELinux-enabled kernel")); + } + +#if !USE_XATTR + if (x.require_preserve_xattr) + error (EXIT_FAILURE, 0, _("cannot preserve extended attributes, cp is " + "built without xattr support")); +#endif + + /* Allocate space for remembering copied and created files. */ + + hash_init (); + + ok = do_copy (argc - optind, argv + optind, + target_directory, no_target_directory, &x); + + forget_all (); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff -urNp coreutils-8.0-orig/src/chcon.c coreutils-8.0/src/chcon.c --- coreutils-8.0-orig/src/chcon.c 2009-10-06 10:55:34.000000000 +0200 +++ coreutils-8.0/src/chcon.c 2009-10-07 10:10:11.000000000 +0200 @@ -348,7 +348,7 @@ Usage: %s [OPTION]... CONTEXT FILE...\n\ "), program_name, program_name, program_name); fputs (_("\ -Change the security context of each FILE to CONTEXT.\n\ +Change the SELinux security context of each FILE to CONTEXT.\n\ With --reference, change the security context of each FILE to that of RFILE.\n\ \n\ -h, --no-dereference affect symbolic links instead of any referenced file\n\ @@ -523,6 +523,10 @@ main (int argc, char **argv) error (EXIT_FAILURE, 0, _("%s may be used only on a SELinux kernel"), program_name); + if (is_selinux_enabled () != 1) + error (EXIT_FAILURE, 0, + _("%s may be used only on a SELinux kernel"), program_name); + if (reference_file) { if (getfilecon (reference_file, &ref_context) < 0) diff -urNp coreutils-8.0-orig/src/chcon.c.orig coreutils-8.0/src/chcon.c.orig --- coreutils-8.0-orig/src/chcon.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/chcon.c.orig 2009-10-06 10:55:34.000000000 +0200 @@ -0,0 +1,572 @@ +/* chcon -- change security context of files + Copyright (C) 2005-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include + +#include "system.h" +#include "dev-ino.h" +#include "error.h" +#include "ignore-value.h" +#include "quote.h" +#include "quotearg.h" +#include "root-dev-ino.h" +#include "selinux-at.h" +#include "xfts.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "chcon" + +#define AUTHORS \ + proper_name ("Russell Coker"), \ + proper_name ("Jim Meyering") + +/* If nonzero, and the systems has support for it, change the context + of symbolic links rather than any files they point to. */ +static bool affect_symlink_referent; + +/* If true, change the modes of directories recursively. */ +static bool recurse; + +/* Level of verbosity. */ +static bool verbose; + +/* Pointer to the device and inode numbers of `/', when --recursive. + Otherwise NULL. */ +static struct dev_ino *root_dev_ino; + +/* The name of the context file is being given. */ +static char const *specified_context; + +/* Specific components of the context */ +static char const *specified_user; +static char const *specified_role; +static char const *specified_range; +static char const *specified_type; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + DEREFERENCE_OPTION = CHAR_MAX + 1, + NO_PRESERVE_ROOT, + PRESERVE_ROOT, + REFERENCE_FILE_OPTION +}; + +static struct option const long_options[] = +{ + {"recursive", no_argument, NULL, 'R'}, + {"dereference", no_argument, NULL, DEREFERENCE_OPTION}, + {"no-dereference", no_argument, NULL, 'h'}, + {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT}, + {"preserve-root", no_argument, NULL, PRESERVE_ROOT}, + {"reference", required_argument, NULL, REFERENCE_FILE_OPTION}, + {"user", required_argument, NULL, 'u'}, + {"role", required_argument, NULL, 'r'}, + {"type", required_argument, NULL, 't'}, + {"range", required_argument, NULL, 'l'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Given a security context, CONTEXT, derive a context_t (*RET), + setting any portions selected via the global variables, specified_user, + specified_role, etc. */ +static int +compute_context_from_mask (security_context_t context, context_t *ret) +{ + bool ok = true; + context_t new_context = context_new (context); + if (!new_context) + { + error (0, errno, _("failed to create security context: %s"), + quotearg_colon (context)); + return 1; + } + +#define SET_COMPONENT(C, comp) \ + do \ + { \ + if (specified_ ## comp \ + && context_ ## comp ## _set ((C), specified_ ## comp)) \ + { \ + error (0, errno, \ + _("failed to set %s security context component to %s"), \ + #comp, quote (specified_ ## comp)); \ + ok = false; \ + } \ + } \ + while (0) + + SET_COMPONENT (new_context, user); + SET_COMPONENT (new_context, range); + SET_COMPONENT (new_context, role); + SET_COMPONENT (new_context, type); + + if (!ok) + { + int saved_errno = errno; + context_free (new_context); + errno = saved_errno; + return 1; + } + + *ret = new_context; + return 0; +} + +/* Change the context of FILE, using specified components. + If it is a directory and -R is given, recurse. + Return 0 if successful, 1 if errors occurred. */ + +static int +change_file_context (int fd, char const *file) +{ + security_context_t file_context = NULL; + context_t context; + security_context_t context_string; + int errors = 0; + + if (specified_context == NULL) + { + int status = (affect_symlink_referent + ? getfileconat (fd, file, &file_context) + : lgetfileconat (fd, file, &file_context)); + + if (status < 0 && errno != ENODATA) + { + error (0, errno, _("failed to get security context of %s"), + quote (file)); + return 1; + } + + /* If the file doesn't have a context, and we're not setting all of + the context components, there isn't really an obvious default. + Thus, we just give up. */ + if (file_context == NULL) + { + error (0, 0, _("can't apply partial context to unlabeled file %s"), + quote (file)); + return 1; + } + + if (compute_context_from_mask (file_context, &context)) + return 1; + } + else + { + /* FIXME: this should be done exactly once, in main. */ + context = context_new (specified_context); + if (!context) + abort (); + } + + context_string = context_str (context); + + if (file_context == NULL || ! STREQ (context_string, file_context)) + { + int fail = (affect_symlink_referent + ? setfileconat (fd, file, context_string) + : lsetfileconat (fd, file, context_string)); + + if (fail) + { + errors = 1; + error (0, errno, _("failed to change context of %s to %s"), + quote_n (0, file), quote_n (1, context_string)); + } + } + + context_free (context); + freecon (file_context); + + return errors; +} + +/* Change the context of FILE. + Return true if successful. This function is called + once for every file system object that fts encounters. */ + +static bool +process_file (FTS *fts, FTSENT *ent) +{ + char const *file_full_name = ent->fts_path; + char const *file = ent->fts_accpath; + const struct stat *file_stats = ent->fts_statp; + bool ok = true; + + switch (ent->fts_info) + { + case FTS_D: + if (recurse) + { + if (ROOT_DEV_INO_CHECK (root_dev_ino, ent->fts_statp)) + { + /* This happens e.g., with "chcon -R --preserve-root ... /" + and with "chcon -RH --preserve-root ... symlink-to-root". */ + ROOT_DEV_INO_WARN (file_full_name); + /* Tell fts not to traverse into this hierarchy. */ + fts_set (fts, ent, FTS_SKIP); + /* Ensure that we do not process "/" on the second visit. */ + ignore_ptr (fts_read (fts)); + return false; + } + return true; + } + break; + + case FTS_DP: + if (! recurse) + return true; + break; + + case FTS_NS: + /* For a top-level file or directory, this FTS_NS (stat failed) + indicator is determined at the time of the initial fts_open call. + With programs like chmod, chown, and chgrp, that modify + permissions, it is possible that the file in question is + accessible when control reaches this point. So, if this is + the first time we've seen the FTS_NS for this file, tell + fts_read to stat it "again". */ + if (ent->fts_level == 0 && ent->fts_number == 0) + { + ent->fts_number = 1; + fts_set (fts, ent, FTS_AGAIN); + return true; + } + error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name)); + ok = false; + break; + + case FTS_ERR: + error (0, ent->fts_errno, _("%s"), quote (file_full_name)); + ok = false; + break; + + case FTS_DNR: + error (0, ent->fts_errno, _("cannot read directory %s"), + quote (file_full_name)); + ok = false; + break; + + default: + break; + } + + if (ent->fts_info == FTS_DP + && ok && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats)) + { + ROOT_DEV_INO_WARN (file_full_name); + ok = false; + } + + if (ok) + { + if (verbose) + printf (_("changing security context of %s\n"), + quote (file_full_name)); + + if (change_file_context (fts->fts_cwd_fd, file) != 0) + ok = false; + } + + if ( ! recurse) + fts_set (fts, ent, FTS_SKIP); + + return ok; +} + +/* Recursively operate on the specified FILES (the last entry + of which is NULL). BIT_FLAGS controls how fts works. + Return true if successful. */ + +static bool +process_files (char **files, int bit_flags) +{ + bool ok = true; + + FTS *fts = xfts_open (files, bit_flags, NULL); + + while (1) + { + FTSENT *ent; + + ent = fts_read (fts); + if (ent == NULL) + { + if (errno != 0) + { + /* FIXME: try to give a better message */ + error (0, errno, _("fts_read failed")); + ok = false; + } + break; + } + + ok &= process_file (fts, ent); + } + + if (fts_close (fts) != 0) + { + error (0, errno, _("fts_close failed")); + ok = false; + } + + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... CONTEXT FILE...\n\ + or: %s [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...\n\ + or: %s [OPTION]... --reference=RFILE FILE...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Change the security context of each FILE to CONTEXT.\n\ +With --reference, change the security context of each FILE to that of RFILE.\n\ +\n\ + -h, --no-dereference affect symbolic links instead of any referenced file\n\ +"), stdout); + fputs (_("\ + --reference=RFILE use RFILE's security context rather than specifying\n\ + a CONTEXT value\n\ + -R, --recursive operate on files and directories recursively\n\ + -v, --verbose output a diagnostic for every file processed\n\ +"), stdout); + fputs (_("\ + -u, --user=USER set user USER in the target security context\n\ + -r, --role=ROLE set role ROLE in the target security context\n\ + -t, --type=TYPE set type TYPE in the target security context\n\ + -l, --range=RANGE set range RANGE in the target security context\n\ +\n\ +"), stdout); + fputs (_("\ +The following options modify how a hierarchy is traversed when the -R\n\ +option is also specified. If more than one is specified, only the final\n\ +one takes effect.\n\ +\n\ + -H if a command line argument is a symbolic link\n\ + to a directory, traverse it\n\ + -L traverse every symbolic link to a directory\n\ + encountered\n\ + -P do not traverse any symbolic links (default)\n\ +\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + emit_ancillary_info (); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + security_context_t ref_context = NULL; + + /* Bit flags that control how fts works. */ + int bit_flags = FTS_PHYSICAL; + + /* 1 if --dereference, 0 if --no-dereference, -1 if neither has been + specified. */ + int dereference = -1; + + bool ok; + bool preserve_root = false; + bool component_specified = false; + char *reference_file = NULL; + int optc; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((optc = getopt_long (argc, argv, "HLPRhvu:r:t:l:", long_options, NULL)) + != -1) + { + switch (optc) + { + case 'H': /* Traverse command-line symlinks-to-directories. */ + bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL; + break; + + case 'L': /* Traverse all symlinks-to-directories. */ + bit_flags = FTS_LOGICAL; + break; + + case 'P': /* Traverse no symlinks-to-directories. */ + bit_flags = FTS_PHYSICAL; + break; + + case 'h': /* --no-dereference: affect symlinks */ + dereference = 0; + break; + + case DEREFERENCE_OPTION: /* --dereference: affect the referent + of each symlink */ + dereference = 1; + break; + + case NO_PRESERVE_ROOT: + preserve_root = false; + break; + + case PRESERVE_ROOT: + preserve_root = true; + break; + + case REFERENCE_FILE_OPTION: + reference_file = optarg; + break; + + case 'R': + recurse = true; + break; + + case 'f': + /* ignore */ + break; + + case 'v': + verbose = true; + break; + + case 'u': + specified_user = optarg; + component_specified = true; + break; + + case 'r': + specified_role = optarg; + component_specified = true; + break; + + case 't': + specified_type = optarg; + component_specified = true; + break; + + case 'l': + specified_range = optarg; + component_specified = true; + break; + + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + if (recurse) + { + if (bit_flags == FTS_PHYSICAL) + { + if (dereference == 1) + error (EXIT_FAILURE, 0, + _("-R --dereference requires either -H or -L")); + affect_symlink_referent = false; + } + else + { + if (dereference == 0) + error (EXIT_FAILURE, 0, _("-R -h requires -P")); + affect_symlink_referent = true; + } + } + else + { + bit_flags = FTS_PHYSICAL; + affect_symlink_referent = (dereference != 0); + } + + if (argc - optind < (reference_file || component_specified ? 1 : 2)) + { + if (argc <= optind) + error (0, 0, _("missing operand")); + else + error (0, 0, _("missing operand after %s"), quote (argv[argc - 1])); + usage (EXIT_FAILURE); + } + + if (is_selinux_enabled () != 1) + error (EXIT_FAILURE, 0, + _("%s may be used only on a SELinux kernel"), program_name); + + if (reference_file) + { + if (getfilecon (reference_file, &ref_context) < 0) + error (EXIT_FAILURE, errno, _("failed to get security context of %s"), + quote (reference_file)); + + specified_context = ref_context; + } + else if (component_specified) + { + /* FIXME: it's already null, so this is a no-op. */ + specified_context = NULL; + } + else + { + context_t context; + specified_context = argv[optind++]; + context = context_new (specified_context); + if (!context) + error (EXIT_FAILURE, 0, _("invalid context: %s"), + quotearg_colon (specified_context)); + context_free (context); + } + + if (reference_file && component_specified) + { + error (0, 0, _("conflicting security context specifiers given")); + usage (1); + } + + if (recurse && preserve_root) + { + static struct dev_ino dev_ino_buf; + root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } + else + { + root_dev_ino = NULL; + } + + ok = process_files (argv + optind, bit_flags | FTS_NOSTAT); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff -urNp coreutils-8.0-orig/src/id.c coreutils-8.0/src/id.c --- coreutils-8.0-orig/src/id.c 2009-09-29 15:27:54.000000000 +0200 +++ coreutils-8.0/src/id.c 2009-10-07 10:10:11.000000000 +0200 @@ -107,7 +107,7 @@ int main (int argc, char **argv) { int optc; - int selinux_enabled = (is_selinux_enabled () > 0); + bool selinux_enabled = (is_selinux_enabled () > 0); /* If true, output the list of all group IDs. -G */ bool just_group_list = false; diff -urNp coreutils-8.0-orig/src/install.c coreutils-8.0/src/install.c --- coreutils-8.0-orig/src/install.c 2009-09-29 15:27:54.000000000 +0200 +++ coreutils-8.0/src/install.c 2009-10-07 10:10:11.000000000 +0200 @@ -284,6 +284,7 @@ cp_option_init (struct cp_options *x) x->reduce_diagnostics=false; x->require_preserve = false; x->require_preserve_context = false; + x->set_security_context = false; x->require_preserve_xattr = false; x->recursive = false; x->sparse_mode = SPARSE_AUTO; @@ -461,7 +462,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, "bcCsDdg:m:o:pt:TvS:Z:", long_options, + while ((optc = getopt_long (argc, argv, "bcCsDdg:m:o:pPt:TvS:Z:", long_options, NULL)) != -1) { switch (optc) @@ -535,6 +536,7 @@ main (int argc, char **argv) error (0, 0, _("WARNING: --preserve_context is deprecated; " "use --preserve-context instead")); /* fall through */ + case 'P': case PRESERVE_CONTEXT_OPTION: if ( ! selinux_enabled) { @@ -542,6 +544,10 @@ main (int argc, char **argv) "this kernel is not SELinux-enabled")); break; } + if ( x.set_security_context ) { + (void) fprintf(stderr, "%s: cannot force target context and preserve it\n", argv[0]); + exit( 1 ); + } x.preserve_security_context = true; use_default_selinux_context = false; break; @@ -553,6 +559,7 @@ main (int argc, char **argv) break; } scontext = optarg; + x.set_security_context = true; use_default_selinux_context = false; break; case_GETOPT_HELP_CHAR; @@ -986,8 +993,8 @@ Mandatory arguments to long options are -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\ + -P, --preserve-context (SELinux) preserve security context\n\ + -Z, --context=CONTEXT (SELinux) set security context of files and directories\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); diff -urNp coreutils-8.0-orig/src/install.c.orig coreutils-8.0/src/install.c.orig --- coreutils-8.0-orig/src/install.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/install.c.orig 2009-09-29 15:27:54.000000000 +0200 @@ -0,0 +1,1011 @@ +/* install - copy files and set attributes + Copyright (C) 89, 90, 91, 1995-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by David MacKenzie */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "system.h" +#include "backupfile.h" +#include "error.h" +#include "cp-hash.h" +#include "copy.h" +#include "filenamecat.h" +#include "full-read.h" +#include "mkancesdirs.h" +#include "mkdir-p.h" +#include "modechange.h" +#include "prog-fprintf.h" +#include "quote.h" +#include "quotearg.h" +#include "savewd.h" +#include "stat-time.h" +#include "utimens.h" +#include "xstrtol.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "install" + +#define AUTHORS proper_name ("David MacKenzie") + +#if HAVE_SYS_WAIT_H +# include +#endif + +static int selinux_enabled = 0; +static bool use_default_selinux_context = true; + +#if ! HAVE_ENDGRENT +# define endgrent() ((void) 0) +#endif + +#if ! HAVE_ENDPWENT +# define endpwent() ((void) 0) +#endif + +#if ! HAVE_LCHOWN +# define lchown(name, uid, gid) chown (name, uid, gid) +#endif + +#if ! HAVE_MATCHPATHCON_INIT_PREFIX +# define matchpathcon_init_prefix(a, p) /* empty */ +#endif + +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 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, + const struct cp_options *x); +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, char const *component, + void *options); +void usage (int status); + +/* The user name that will own the files, or NULL to make the owner + the current user ID. */ +static char *owner_name; + +/* The user ID corresponding to `owner_name'. */ +static uid_t owner_id; + +/* The group name that will own the files, or NULL to make the group + the current group ID. */ +static char *group_name; + +/* The group ID corresponding to `group_name'. */ +static gid_t group_id; + +#define DEFAULT_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) + +/* The file mode bits to which non-directory files will be set. The umask has + no effect. */ +static mode_t mode = DEFAULT_MODE; + +/* Similar, but for directories. */ +static mode_t dir_mode = DEFAULT_MODE; + +/* The file mode bits that the user cares about. This should be a + superset of DIR_MODE and a subset of CHMOD_MODE_BITS. This matters + for directories, since otherwise directories may keep their S_ISUID + 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; + +/* If true, install a directory instead of a regular file. */ +static bool dir_arg; + +/* Program used to strip binaries, "strip" is default */ +static char const *strip_program = "strip"; + +/* 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, + PRESERVE_CONTEXT_OPTION_DEPRECATED, + STRIP_PROGRAM_OPTION +}; + +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'}, + {"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}, + /* --preserve_context was silently supported until Apr 2009. + FIXME: disable altogether in a year or so. */ + {"preserve_context", no_argument, NULL, PRESERVE_CONTEXT_OPTION_DEPRECATED}, + {"strip", no_argument, NULL, 's'}, + {"strip-program", required_argument, NULL, STRIP_PROGRAM_OPTION}, + {"suffix", required_argument, NULL, 'S'}, + {"target-directory", required_argument, NULL, 't'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {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 | O_BINARY); + if (src_fd < 0) + return true; + + dest_fd = open (dest_name, O_RDONLY | O_BINARY); + 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) +{ + cp_options_default (x); + x->copy_as_regular = true; + x->reflink_mode = REFLINK_NEVER; + x->dereference = DEREF_ALWAYS; + x->unlink_dest_before_opening = true; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = false; + x->one_file_system = false; + x->preserve_ownership = false; + x->preserve_links = false; + x->preserve_mode = false; + x->preserve_timestamps = false; + x->reduce_diagnostics=false; + x->require_preserve = false; + x->require_preserve_context = false; + x->require_preserve_xattr = false; + x->recursive = false; + x->sparse_mode = SPARSE_AUTO; + x->symbolic_link = false; + x->backup_type = no_backups; + + /* Create destination files initially writable so we can run strip on them. + Although GNU strip works fine on read-only files, some others + would fail. */ + x->set_mode = true; + x->mode = S_IRUSR | S_IWUSR; + x->stdin_tty = false; + + x->open_dangling_dest_symlink = false; + x->update = false; + x->preserve_security_context = false; + x->preserve_xattr = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +#ifdef ENABLE_MATCHPATHCON +/* 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; + static bool first_call = true; + + if (selinux_enabled != 1) + { + /* Indicate no context found. */ + return; + } + if (lstat (file, &st) != 0) + return; + + if (first_call && IS_ABSOLUTE_FILE_NAME (file)) + { + /* Calling matchpathcon_init_prefix (NULL, "/first_component/") + is an optimization to minimize the expense of the following + matchpathcon call. Do it only once, just before the first + matchpathcon call. We *could* call matchpathcon_fini after + the final matchpathcon call, but that's not necessary, since + by then we're about to exit, and besides, the buffers it + would free are still reachable. */ + char const *p0; + char const *p = file + 1; + while (ISSLASH (*p)) + ++p; + + /* Record final leading slash, for when FILE starts with two or more. */ + p0 = p - 1; + + if (*p) + { + char *prefix; + do + { + ++p; + } + while (*p && !ISSLASH (*p)); + + prefix = malloc (p - p0 + 2); + if (prefix) + { + stpcpy (stpncpy (prefix, p0, p - p0), "/"); + matchpathcon_init_prefix (NULL, prefix); + free (prefix); + } + } + } + first_call = false; + + /* 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) || + STREQ (scontext, "<>")) + { + 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; +} +#else +static void +setdefaultfilecon (char const *file) +{ + (void) file; +} +#endif + +/* 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 + directory if it referred to anything at all. */ + +static bool +target_directory_operand (char const *file) +{ + char const *b = last_component (file); + size_t blen = strlen (b); + bool looks_like_a_dir = (blen == 0 || ISSLASH (b[blen - 1])); + struct stat st; + int err = (stat (file, &st) == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st.st_mode); + if (err && err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + if (is_a_dir < looks_like_a_dir) + error (EXIT_FAILURE, err, _("target %s is not a directory"), quote (file)); + return is_a_dir; +} + +/* Process a command-line file name, for the -d option. */ +static int +process_dir (char *dir, struct savewd *wd, void *options) +{ + return (make_dir_parents (dir, wd, + make_ancestor, options, + dir_mode, announce_mkdir, + dir_mode_bits, owner_id, group_id, false) + ? EXIT_SUCCESS + : EXIT_FAILURE); +} + +int +main (int argc, char **argv) +{ + int optc; + int exit_status = EXIT_SUCCESS; + const char *specified_mode = NULL; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + bool mkdir_and_install = false; + struct cp_options x; + char const *target_directory = NULL; + bool no_target_directory = false; + int n_files; + char **file; + bool strip_program_specified = false; + security_context_t scontext = NULL; + /* set iff kernel has extra selinux system calls */ + selinux_enabled = (0 < is_selinux_enabled ()); + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdin); + + cp_option_init (&x); + + owner_name = NULL; + group_name = NULL; + strip_files = false; + dir_arg = false; + umask (0); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + 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) + { + switch (optc) + { + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + case 'c': + break; + case 'C': + copy_only_if_needed = true; + break; + case 's': + strip_files = true; +#ifdef SIGCHLD + /* System V fork+wait does not work if SIGCHLD is ignored. */ + signal (SIGCHLD, SIG_DFL); +#endif + break; + case STRIP_PROGRAM_OPTION: + strip_program = xstrdup (optarg); + strip_program_specified = true; + break; + case 'd': + dir_arg = true; + break; + case 'D': + mkdir_and_install = true; + break; + case 'v': + x.verbose = true; + break; + case 'g': + group_name = optarg; + break; + case 'm': + specified_mode = optarg; + break; + case 'o': + owner_name = optarg; + break; + case 'p': + x.preserve_timestamps = true; + break; + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, + _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + case 'T': + no_target_directory = true; + break; + + case PRESERVE_CONTEXT_OPTION_DEPRECATED: + error (0, 0, _("WARNING: --preserve_context is deprecated; " + "use --preserve-context instead")); + /* fall through */ + 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: + usage (EXIT_FAILURE); + } + } + + /* Check for invalid combinations of arguments. */ + if (dir_arg && strip_files) + error (EXIT_FAILURE, 0, + _("the strip option may not be used when installing a directory")); + if (dir_arg && target_directory) + 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.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; + + if (n_files <= ! (dir_arg || target_directory)) + { + if (n_files <= 0) + error (0, 0, _("missing file operand")); + else + error (0, 0, _("missing destination file operand after %s"), + quote (file[0])); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("cannot combine --target-directory (-t) " + "and --no-target-directory (-T)")); + if (2 < n_files) + { + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (! (dir_arg || target_directory)) + { + if (2 <= n_files && target_directory_operand (file[n_files - 1])) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (specified_mode) + { + struct mode_change *change = mode_compile (specified_mode); + if (!change) + error (EXIT_FAILURE, 0, _("invalid mode %s"), quote (specified_mode)); + mode = mode_adjust (0, false, 0, change, NULL); + dir_mode = mode_adjust (0, true, 0, change, &dir_mode_bits); + free (change); + } + + if (strip_program_specified && !strip_files) + 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) + exit_status = savewd_process_files (n_files, file, process_dir, &x); + else + { + /* FIXME: it's a little gross that this initialization is + required by copy.c::copy. */ + hash_init (); + + if (!target_directory) + { + if (! (mkdir_and_install + ? install_file_in_file_parents (file[0], file[1], &x) + : install_file_in_file (file[0], file[1], &x))) + exit_status = EXIT_FAILURE; + } + else + { + int i; + dest_info_init (&x); + for (i = 0; i < n_files; i++) + if (! install_file_in_dir (file[i], target_directory, &x)) + exit_status = EXIT_FAILURE; + } + } + + exit (exit_status); +} + +/* Copy file FROM onto file TO, creating any missing parent directories of TO. + Return true if successful. */ + +static bool +install_file_in_file_parents (char const *from, char *to, + struct cp_options *x) +{ + bool save_working_directory = + ! (IS_ABSOLUTE_FILE_NAME (from) && IS_ABSOLUTE_FILE_NAME (to)); + int status = EXIT_SUCCESS; + + struct savewd wd; + savewd_init (&wd); + if (! save_working_directory) + savewd_finish (&wd); + + if (mkancesdirs (to, &wd, make_ancestor, x) == -1) + { + error (0, errno, _("cannot create directory %s"), to); + status = EXIT_FAILURE; + } + + if (save_working_directory) + { + int restore_result = savewd_restore (&wd, status); + int restore_errno = errno; + savewd_finish (&wd); + if (EXIT_SUCCESS < restore_result) + return false; + if (restore_result < 0 && status == EXIT_SUCCESS) + { + error (0, restore_errno, _("cannot create directory %s"), to); + return false; + } + } + + return (status == EXIT_SUCCESS && install_file_in_file (from, to, x)); +} + +/* Copy file FROM onto file TO and give TO the appropriate + attributes. + Return true if successful. */ + +static bool +install_file_in_file (const char *from, const char *to, + const struct cp_options *x) +{ + struct stat from_sb; + if (x->preserve_timestamps && stat (from, &from_sb) != 0) + { + error (0, errno, _("cannot stat %s"), quote (from)); + return false; + } + if (! copy_file (from, to, x)) + return false; + if (strip_files) + strip (to); + if (x->preserve_timestamps && (strip_files || ! S_ISREG (from_sb.st_mode)) + && ! change_timestamps (&from_sb, to)) + return false; + return change_attributes (to); +} + +/* Copy file FROM into directory TO_DIR, keeping its same name, + and give the copy the appropriate attributes. + Return true if successful. */ + +static bool +install_file_in_dir (const char *from, const char *to_dir, + const struct cp_options *x) +{ + const char *from_base = last_component (from); + char *to = file_name_concat (to_dir, from_base, NULL); + bool ret = install_file_in_file (from, to, x); + free (to); + return ret; +} + +/* Copy file FROM onto file TO, creating TO if necessary. + Return true if successful. */ + +static bool +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. + However, since !x->recursive, the call to "copy" will fail if FROM + is a directory. */ + + return copy (from, to, false, x, ©_into_self, NULL); +} + +/* Set the attributes of file or directory NAME. + Return true if successful. */ + +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. + On System V, users can give away files with chown and then not + be able to chmod them. So don't give files away. + + We don't normally ignore errors from chown because the idea of + the install command is that the file is supposed to end up with + precisely the attributes that the user specified (or defaulted). + If the file doesn't end up with the group they asked for, they'll + want to know. */ + + if (! (owner_id == (uid_t) -1 && group_id == (gid_t) -1) + && lchown (name, owner_id, group_id) != 0) + error (0, errno, _("cannot change ownership of %s"), quote (name)); + else if (chmod (name, mode) != 0) + error (0, errno, _("cannot change permissions of %s"), quote (name)); + else + ok = true; + + if (use_default_selinux_context) + setdefaultfilecon (name); + + return ok; +} + +/* Set the timestamps of file TO to match those of file FROM. + Return true if successful. */ + +static bool +change_timestamps (struct stat const *from_sb, char const *to) +{ + struct timespec timespec[2]; + timespec[0] = get_stat_atime (from_sb); + timespec[1] = get_stat_mtime (from_sb); + + if (utimens (to, timespec)) + { + error (0, errno, _("cannot set time stamps for %s"), quote (to)); + return false; + } + return true; +} + +/* Strip the symbol table from the file NAME. + We could dig the magic number out of the file first to + determine whether to strip it, but the header files and + magic numbers vary so much from system to system that making + it portable would be very difficult. Not worth the effort. */ + +static void +strip (char const *name) +{ + int status; + pid_t pid = fork (); + + switch (pid) + { + case -1: + error (EXIT_FAILURE, errno, _("fork system call failed")); + break; + case 0: /* Child. */ + execlp (strip_program, strip_program, name, NULL); + error (EXIT_FAILURE, errno, _("cannot run %s"), strip_program); + break; + default: /* Parent. */ + 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; + } +} + +/* Initialize the user and group ownership of the files to install. */ + +static void +get_ids (void) +{ + struct passwd *pw; + struct group *gr; + + if (owner_name) + { + pw = getpwnam (owner_name); + if (pw == NULL) + { + unsigned long int tmp; + if (xstrtoul (owner_name, NULL, 0, &tmp, NULL) != LONGINT_OK + || UID_T_MAX < tmp) + error (EXIT_FAILURE, 0, _("invalid user %s"), quote (owner_name)); + owner_id = tmp; + } + else + owner_id = pw->pw_uid; + endpwent (); + } + else + owner_id = (uid_t) -1; + + if (group_name) + { + gr = getgrnam (group_name); + if (gr == NULL) + { + unsigned long int tmp; + if (xstrtoul (group_name, NULL, 0, &tmp, NULL) != LONGINT_OK + || GID_T_MAX < tmp) + error (EXIT_FAILURE, 0, _("invalid group %s"), quote (group_name)); + group_id = tmp; + } + else + group_id = gr->gr_gid; + endgrent (); + } + else + group_id = (gid_t) -1; +} + +/* Report that directory DIR was made, if OPTIONS requests this. */ +static void +announce_mkdir (char const *dir, void *options) +{ + struct cp_options const *x = options; + if (x->verbose) + prog_fprintf (stdout, _("creating directory %s"), quote (dir)); +} + +/* 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, char const *component, void *options) +{ + int r = mkdir (component, DEFAULT_MODE); + if (r == 0) + announce_mkdir (dir, options); + return r; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] SOURCE DEST\n\ + or: %s [OPTION]... SOURCE... DIRECTORY\n\ + or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ + or: %s [OPTION]... -d DIRECTORY...\n\ +"), + program_name, program_name, program_name, program_name); + fputs (_("\ +\n\ +This install program copies files (often just compiled) into destination\n\ +locations you choose. If you want to download and install a ready-to-use\n\ +package on a GNU/Linux system, you should instead be using a package manager\n\ +like yum(1) or apt-get(1).\n\ +\n\ +In the first three forms, copy SOURCE to DEST or multiple SOURCE(s) to\n\ +the existing DIRECTORY, while setting permission modes and owner/group.\n\ +In the 4th form, create all components of the given DIRECTORY(ies).\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + --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); + fputs (_("\ + -D create all leading components of DEST except the last,\n\ + then copy SOURCE to DEST\n\ + -g, --group=GROUP set group ownership, instead of process' current group\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); + fputs (_("\ + -p, --preserve-timestamps apply access/modification times of SOURCE files\n\ + to corresponding destination files\n\ + -s, --strip strip symbol tables\n\ + --strip-program=PROGRAM program used to strip binaries\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 (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + emit_ancillary_info (); + } + exit (status); +} diff -urNp coreutils-8.0-orig/src/ls.c coreutils-8.0/src/ls.c --- coreutils-8.0-orig/src/ls.c 2009-10-07 10:09:43.000000000 +0200 +++ coreutils-8.0/src/ls.c 2009-10-07 10:10:11.000000000 +0200 @@ -162,7 +162,8 @@ enum filetype symbolic_link, sock, whiteout, - arg_directory + arg_directory, + command_line }; /* Display letters and indicators for each filetype. @@ -279,6 +280,7 @@ static void queue_directory (char const static void sort_files (void); static void parse_ls_color (void); void usage (int status); +static void print_scontext_format (const struct fileinfo *f); /* Initial size of hash table. Most hierarchies are likely to be shallower than this. */ @@ -348,7 +350,7 @@ static struct pending *pending_dirs; static struct timespec current_time; -static bool print_scontext; +static int print_scontext = 0; static char UNKNOWN_SECURITY_CONTEXT[] = "?"; /* Whether any of the files has an ACL. This affects the width of the @@ -388,7 +390,9 @@ enum format one_per_line, /* -1 */ many_per_line, /* -C */ horizontal, /* -x */ - with_commas /* -m */ + with_commas, /* -m */ + security_format, /* -Z */ + invalid_format }; static enum format format; @@ -790,6 +794,9 @@ enum SHOW_CONTROL_CHARS_OPTION, SI_OPTION, SORT_OPTION, + CONTEXT_OPTION, + LCONTEXT_OPTION, + SCONTEXT_OPTION, TIME_OPTION, TIME_STYLE_OPTION }; @@ -835,7 +842,9 @@ static struct option const long_options[ {"time-style", required_argument, NULL, TIME_STYLE_OPTION}, {"color", optional_argument, NULL, COLOR_OPTION}, {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION}, - {"context", no_argument, 0, 'Z'}, + {"context", no_argument, 0, CONTEXT_OPTION}, + {"lcontext", no_argument, 0, LCONTEXT_OPTION}, + {"scontext", no_argument, 0, SCONTEXT_OPTION}, {"author", no_argument, NULL, AUTHOR_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, @@ -845,12 +854,12 @@ static struct option const long_options[ static char const *const format_args[] = { "verbose", "long", "commas", "horizontal", "across", - "vertical", "single-column", NULL + "vertical", "single-column", "context", NULL }; static enum format const format_types[] = { long_format, long_format, with_commas, horizontal, horizontal, - many_per_line, one_per_line + many_per_line, one_per_line, security_format }; ARGMATCH_VERIFY (format_args, format_types); @@ -1281,7 +1290,8 @@ main (int argc, char **argv) /* Avoid following symbolic links when possible. */ if (is_colored (C_ORPHAN) || (is_colored (C_EXEC) && color_symlink_as_referent) - || (is_colored (C_MISSING) && format == long_format)) + || (is_colored (C_MISSING) && (format == long_format + || format == security_format))) check_symlink_color = true; /* If the standard output is a controlling terminal, watch out @@ -1328,7 +1338,7 @@ main (int argc, char **argv) if (dereference == DEREF_UNDEFINED) dereference = ((immediate_dirs || indicator_style == classify - || format == long_format) + || format == long_format || format == security_format) ? DEREF_NEVER : DEREF_COMMAND_LINE_SYMLINK_TO_DIR); @@ -1348,7 +1358,7 @@ main (int argc, char **argv) format_needs_stat = sort_type == sort_time || sort_type == sort_size || format == long_format - || print_scontext + || format == security_format || print_scontext || print_block_size; format_needs_type = (! format_needs_stat && (recursive @@ -1379,7 +1389,7 @@ main (int argc, char **argv) } else do - gobble_file (argv[i++], unknown, NOT_AN_INODE_NUMBER, true, ""); + gobble_file (argv[i++], command_line, NOT_AN_INODE_NUMBER, true, ""); while (i < argc); if (cwd_n_used) @@ -1542,7 +1552,7 @@ decode_switches (int argc, char **argv) ignore_mode = IGNORE_DEFAULT; ignore_patterns = NULL; hide_patterns = NULL; - print_scontext = false; + print_scontext = 0; /* FIXME: put this in a function. */ { @@ -1924,13 +1934,27 @@ decode_switches (int argc, char **argv) break; case 'Z': - print_scontext = true; + print_scontext = 1; + format = security_format; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + case CONTEXT_OPTION: /* default security context format */ + print_scontext = 1; + format = security_format; + break; + case LCONTEXT_OPTION: /* long format plus security context */ + print_scontext = 1; + format = long_format; + break; + case SCONTEXT_OPTION: /* short form of new security format */ + print_scontext = 0; + format = security_format; + break; + default: usage (LS_FAILURE); } @@ -2651,8 +2675,10 @@ clear_files (void) struct fileinfo *f = sorted_file[i]; free (f->name); free (f->linkname); - if (f->scontext != UNKNOWN_SECURITY_CONTEXT) - freecon (f->scontext); + if (f->scontext != UNKNOWN_SECURITY_CONTEXT) { + freecon (f->scontext); + f->scontext = NULL; + } } cwd_n_used = 0; @@ -2694,6 +2720,7 @@ gobble_file (char const *name, enum file memset (f, '\0', sizeof *f); f->stat.st_ino = inode; f->filetype = type; + f->scontext = NULL; if (command_line_arg || format_needs_stat @@ -2793,7 +2820,7 @@ gobble_file (char const *name, enum file f->stat_ok = true; - if (format == long_format || print_scontext) + if (format == long_format || format == security_format || print_scontext) { bool have_selinux = false; bool have_acl = false; @@ -2827,7 +2854,7 @@ gobble_file (char const *name, enum file err = 0; } - if (err == 0 && format == long_format) + if (err == 0 && (format == long_format || format == security_format)) { int n = file_has_acl (absolute_name, &f->stat); err = (n < 0); @@ -2846,7 +2873,8 @@ gobble_file (char const *name, enum file } if (S_ISLNK (f->stat.st_mode) - && (format == long_format || check_symlink_color)) + && (format == long_format || format == security_format + || check_symlink_color)) { char *linkname; struct stat linkstats; @@ -2866,6 +2894,7 @@ gobble_file (char const *name, enum file command line are automatically traced if not being listed as files. */ if (!command_line_arg || format == long_format + || format == security_format || !S_ISDIR (linkstats.st_mode)) { /* Get the linked-to file's mode for the filetype indicator @@ -2905,7 +2934,7 @@ gobble_file (char const *name, enum file block_size_width = len; } - if (format == long_format) + if (format == long_format || format == security_format) { if (print_owner) { @@ -3406,6 +3435,13 @@ print_current_files (void) print_long_format (sorted_file[i]); DIRED_PUTCHAR ('\n'); } + break; + case security_format: + for (i = 0; i < cwd_n_used; i++) + { + print_scontext_format (sorted_file[i]); + DIRED_PUTCHAR ('\n'); + } break; } } @@ -3568,6 +3604,69 @@ format_inode (char *buf, size_t buflen, : (char *) "?"); } +/* Print info about f in scontext format */ +static void +print_scontext_format (const struct fileinfo *f) +{ + char modebuf[12]; + + /* 7 fields that may require LONGEST_HUMAN_READABLE bytes, + 1 10-byte mode string, + 9 spaces, one following each of these fields, and + 1 trailing NUL byte. */ + + char init_bigbuf[7 * LONGEST_HUMAN_READABLE + 10 + 9 + 1]; + char *buf = init_bigbuf; + char *p; + + p = buf; + + if ( print_scontext ) { /* zero means terse listing */ + filemodestring (&f->stat, modebuf); + if (! any_has_acl) + modebuf[10] = '\0'; + else if (f->acl_type == ACL_T_SELINUX_ONLY) + modebuf[10] = '.'; + else if (f->acl_type == ACL_T_YES) + modebuf[10] = '+'; + modebuf[11] = '\0'; + + /* print mode */ + + (void) sprintf (p, "%s ", modebuf); + p += strlen (p); + + /* print standard user and group */ + + DIRED_FPUTS (buf, stdout, p - buf); + format_user (f->stat.st_uid, owner_width, f->stat_ok); + format_group (f->stat.st_gid, group_width, f->stat_ok); + p = buf; + } + + (void) sprintf (p, "%-32s ", f->scontext ?: ""); + p += strlen (p); + + DIRED_INDENT (); + DIRED_FPUTS (buf, stdout, p - buf); + size_t w = print_name_with_quoting (f->name, FILE_OR_LINK_MODE(f), f->linkok, + f->stat_ok, f->filetype, &dired_obstack, f->stat.st_nlink, p - buf); + + if (f->filetype == symbolic_link) { + if (f->linkname) { + DIRED_FPUTS_LITERAL (" -> ", stdout); + print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1, + f->stat_ok, f->filetype, NULL, f->stat.st_nlink, (p-buf) + w + 4 ); + if (indicator_style != none) + print_type_indicator (f->stat_ok, f->linkmode, f->filetype); + } + } + else { + if (indicator_style != none) + print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); + } +} + /* Print information about F in long format. */ static void print_long_format (const struct fileinfo *f) @@ -3659,9 +3758,15 @@ print_long_format (const struct fileinfo The latter is wrong when nlink_width is zero. */ p += strlen (p); + if (print_scontext) + { + sprintf (p, "%-32s ", f->scontext ? f->scontext : ""); + p += strlen (p); + } + DIRED_INDENT (); - if (print_owner || print_group || print_author || print_scontext) + if (print_owner || print_group || print_author) { DIRED_FPUTS (buf, stdout, p - buf); @@ -3674,9 +3779,6 @@ print_long_format (const struct fileinfo if (print_author) format_user (f->stat.st_author, author_width, f->stat_ok); - if (print_scontext) - format_user_or_group (f->scontext, 0, scontext_width); - p = buf; } @@ -4020,9 +4122,6 @@ print_file_name_and_frills (const struct : human_readable (ST_NBLOCKS (f->stat), buf, human_output_opts, ST_NBLOCKSIZE, output_block_size)); - if (print_scontext) - printf ("%*s ", format == with_commas ? 0 : scontext_width, f->scontext); - size_t width = print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok, f->stat_ok, f->filetype, NULL, f->stat.st_nlink, start_col); @@ -4241,9 +4340,6 @@ length_of_file_name_and_frills (const st output_block_size)) : block_size_width); - if (print_scontext) - len += 1 + (format == with_commas ? strlen (f->scontext) : scontext_width); - quote_name (NULL, f->name, filename_quoting_options, &name_width); len += name_width; @@ -4674,9 +4770,16 @@ Mandatory arguments to long options are -w, --width=COLS assume screen width instead of current value\n\ -x list entries by lines instead of by columns\n\ -X sort alphabetically by entry extension\n\ - -Z, --context print any SELinux security context of each file\n\ -1 list one file per line\n\ "), stdout); + fputs(_("\nSELinux options:\n\n\ + --lcontext Display security context. Enable -l. Lines\n\ + will probably be too wide for most displays.\n\ + -Z, --context Display security context so it fits on most\n\ + displays. Displays only mode, user, group,\n\ + security context and file name.\n\ + --scontext Display only security context and file name.\n\ +"), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); emit_size_note (); diff -urNp coreutils-8.0-orig/src/ls.c.orig coreutils-8.0/src/ls.c.orig --- coreutils-8.0-orig/src/ls.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/ls.c.orig 2009-10-07 10:09:43.000000000 +0200 @@ -0,0 +1,4700 @@ +/* `dir', `vdir' and `ls' directory listing programs for GNU. + Copyright (C) 85, 88, 90, 91, 1995-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +/* If ls_mode is LS_MULTI_COL, + the multi-column format is the default regardless + of the type of output device. + This is for the `dir' program. + + If ls_mode is LS_LONG_FORMAT, + the long format is the default regardless of the + type of output device. + This is for the `vdir' program. + + If ls_mode is LS_LS, + the output format depends on whether the output + device is a terminal. + This is for the `ls' program. */ + +/* Written by Richard Stallman and David MacKenzie. */ + +/* Color support by Peter Anvin and Dennis + Flaherty based on original patches by + Greg Lee . */ + +#include +#include + +#if HAVE_TERMIOS_H +# include +#endif +#if HAVE_STROPTS_H +# include +#endif +#if HAVE_SYS_IOCTL_H +# include +#endif + +#ifdef WINSIZE_IN_PTEM +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_LANGINFO_CODESET +# include +#endif + +/* Use SA_NOCLDSTOP as a proxy for whether the sigaction machinery is + present. */ +#ifndef SA_NOCLDSTOP +# define SA_NOCLDSTOP 0 +# define sigprocmask(How, Set, Oset) /* empty */ +# define sigset_t int +# if ! HAVE_SIGINTERRUPT +# define siginterrupt(sig, flag) /* empty */ +# endif +#endif +#ifndef SA_RESTART +# define SA_RESTART 0 +#endif + +#include "system.h" +#include + +#ifdef HAVE_CAP +# include +#endif + +#include "acl.h" +#include "argmatch.h" +#include "dev-ino.h" +#include "error.h" +#include "filenamecat.h" +#include "hard-locale.h" +#include "hash.h" +#include "human.h" +#include "filemode.h" +#include "filevercmp.h" +#include "idcache.h" +#include "ls.h" +#include "mbswidth.h" +#include "mpsort.h" +#include "obstack.h" +#include "quote.h" +#include "quotearg.h" +#include "same.h" +#include "stat-time.h" +#include "strftime.h" +#include "xstrtol.h" +#include "areadlink.h" +#include "mbsalign.h" + +#define PROGRAM_NAME (ls_mode == LS_LS ? "ls" \ + : (ls_mode == LS_MULTI_COL \ + ? "dir" : "vdir")) + +#define AUTHORS \ + proper_name ("Richard M. Stallman"), \ + proper_name ("David MacKenzie") + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +/* Return an int indicating the result of comparing two integers. + Subtracting doesn't always work, due to overflow. */ +#define longdiff(a, b) ((a) < (b) ? -1 : (a) > (b)) + +/* Unix-based readdir implementations have historically returned a dirent.d_ino + value that is sometimes not equal to the stat-obtained st_ino value for + that same entry. This error occurs for a readdir entry that refers + to a mount point. readdir's error is to return the inode number of + the underlying directory -- one that typically cannot be stat'ed, as + long as a file system is mounted on that directory. RELIABLE_D_INO + encapsulates whether we can use the more efficient approach of relying + on readdir-supplied d_ino values, or whether we must incur the cost of + calling stat or lstat to obtain each guaranteed-valid inode number. */ + +#ifndef READDIR_LIES_ABOUT_MOUNTPOINT_D_INO +# define READDIR_LIES_ABOUT_MOUNTPOINT_D_INO 1 +#endif + +#if READDIR_LIES_ABOUT_MOUNTPOINT_D_INO +# define RELIABLE_D_INO(dp) NOT_AN_INODE_NUMBER +#else +# define RELIABLE_D_INO(dp) D_INO (dp) +#endif + +#if ! HAVE_STRUCT_STAT_ST_AUTHOR +# define st_author st_uid +#endif + +enum filetype + { + unknown, + fifo, + chardev, + directory, + blockdev, + normal, + symbolic_link, + sock, + whiteout, + arg_directory + }; + +/* Display letters and indicators for each filetype. + Keep these in sync with enum filetype. */ +static char const filetype_letter[] = "?pcdb-lswd"; + +/* Ensure that filetype and filetype_letter have the same + number of elements. */ +verify (sizeof filetype_letter - 1 == arg_directory + 1); + +#define FILETYPE_INDICATORS \ + { \ + C_ORPHAN, C_FIFO, C_CHR, C_DIR, C_BLK, C_FILE, \ + C_LINK, C_SOCK, C_FILE, C_DIR \ + } + +enum acl_type + { + ACL_T_NONE, + ACL_T_SELINUX_ONLY, + ACL_T_YES + }; + +struct fileinfo + { + /* The file name. */ + char *name; + + /* For symbolic link, name of the file linked to, otherwise zero. */ + char *linkname; + + struct stat stat; + + enum filetype filetype; + + /* For symbolic link and long listing, st_mode of file linked to, otherwise + zero. */ + mode_t linkmode; + + /* SELinux security context. */ + security_context_t scontext; + + bool stat_ok; + + /* For symbolic link and color printing, true if linked-to file + exists, otherwise false. */ + bool linkok; + + /* For long listings, true if the file has an access control list, + or an SELinux security context. */ + enum acl_type acl_type; + }; + +#define LEN_STR_PAIR(s) sizeof (s) - 1, s + +/* Null is a valid character in a color indicator (think about Epson + printers, for example) so we have to use a length/buffer string + type. */ + +struct bin_str + { + size_t len; /* Number of bytes */ + const char *string; /* Pointer to the same */ + }; + +#if ! HAVE_TCGETPGRP +# define tcgetpgrp(Fd) 0 +#endif + +static size_t quote_name (FILE *out, const char *name, + struct quoting_options const *options, + size_t *width); +static char *make_link_name (char const *name, char const *linkname); +static int decode_switches (int argc, char **argv); +static bool file_ignored (char const *name); +static uintmax_t gobble_file (char const *name, enum filetype type, + ino_t inode, bool command_line_arg, + char const *dirname); +static bool print_color_indicator (const char *name, mode_t mode, int linkok, + bool stat_ok, enum filetype type, + nlink_t nlink); +static void put_indicator (const struct bin_str *ind); +static void add_ignore_pattern (const char *pattern); +static void attach (char *dest, const char *dirname, const char *name); +static void clear_files (void); +static void extract_dirs_from_files (char const *dirname, + bool command_line_arg); +static void get_link_name (char const *filename, struct fileinfo *f, + bool command_line_arg); +static void indent (size_t from, size_t to); +static size_t calculate_columns (bool by_columns); +static void print_current_files (void); +static void print_dir (char const *name, char const *realname, + bool command_line_arg); +static size_t print_file_name_and_frills (const struct fileinfo *f, + size_t start_col); +static void print_horizontal (void); +static int format_user_width (uid_t u); +static int format_group_width (gid_t g); +static void print_long_format (const struct fileinfo *f); +static void print_many_per_line (void); +static size_t print_name_with_quoting (const char *p, mode_t mode, + int linkok, bool stat_ok, + enum filetype type, + struct obstack *stack, + nlink_t nlink, + size_t start_col); +static void prep_non_filename_text (void); +static bool print_type_indicator (bool stat_ok, mode_t mode, + enum filetype type); +static void print_with_commas (void); +static void queue_directory (char const *name, char const *realname, + bool command_line_arg); +static void sort_files (void); +static void parse_ls_color (void); +void usage (int status); + +/* Initial size of hash table. + Most hierarchies are likely to be shallower than this. */ +#define INITIAL_TABLE_SIZE 30 + +/* The set of `active' directories, from the current command-line argument + to the level in the hierarchy at which files are being listed. + A directory is represented by its device and inode numbers (struct dev_ino). + A directory is added to this set when ls begins listing it or its + entries, and it is removed from the set just after ls has finished + processing it. This set is used solely to detect loops, e.g., with + mkdir loop; cd loop; ln -s ../loop sub; ls -RL */ +static Hash_table *active_dir_set; + +#define LOOP_DETECT (!!active_dir_set) + +/* The table of files in the current directory: + + `cwd_file' points to a vector of `struct fileinfo', one per file. + `cwd_n_alloc' is the number of elements space has been allocated for. + `cwd_n_used' is the number actually in use. */ + +/* Address of block containing the files that are described. */ +static struct fileinfo *cwd_file; + +/* Length of block that `cwd_file' points to, measured in files. */ +static size_t cwd_n_alloc; + +/* Index of first unused slot in `cwd_file'. */ +static size_t cwd_n_used; + +/* Vector of pointers to files, in proper sorted order, and the number + of entries allocated for it. */ +static void **sorted_file; +static size_t sorted_file_alloc; + +/* When true, in a color listing, color each symlink name according to the + type of file it points to. Otherwise, color them according to the `ln' + directive in LS_COLORS. Dangling (orphan) symlinks are treated specially, + regardless. This is set when `ln=target' appears in LS_COLORS. */ + +static bool color_symlink_as_referent; + +/* mode of appropriate file for colorization */ +#define FILE_OR_LINK_MODE(File) \ + ((color_symlink_as_referent && (File)->linkok) \ + ? (File)->linkmode : (File)->stat.st_mode) + + +/* Record of one pending directory waiting to be listed. */ + +struct pending + { + char *name; + /* If the directory is actually the file pointed to by a symbolic link we + were told to list, `realname' will contain the name of the symbolic + link, otherwise zero. */ + char *realname; + bool command_line_arg; + struct pending *next; + }; + +static struct pending *pending_dirs; + +/* Current time in seconds and nanoseconds since 1970, updated as + needed when deciding whether a file is recent. */ + +static struct timespec current_time; + +static bool print_scontext; +static char UNKNOWN_SECURITY_CONTEXT[] = "?"; + +/* Whether any of the files has an ACL. This affects the width of the + mode column. */ + +static bool any_has_acl; + +/* The number of columns to use for columns containing inode numbers, + block sizes, link counts, owners, groups, authors, major device + numbers, minor device numbers, and file sizes, respectively. */ + +static int inode_number_width; +static int block_size_width; +static int nlink_width; +static int scontext_width; +static int owner_width; +static int group_width; +static int author_width; +static int major_device_number_width; +static int minor_device_number_width; +static int file_size_width; + +/* Option flags */ + +/* long_format for lots of info, one per line. + one_per_line for just names, one per line. + many_per_line for just names, many per line, sorted vertically. + horizontal for just names, many per line, sorted horizontally. + with_commas for just names, many per line, separated by commas. + + -l (and other options that imply -l), -1, -C, -x and -m control + this parameter. */ + +enum format + { + long_format, /* -l and other options that imply -l */ + one_per_line, /* -1 */ + many_per_line, /* -C */ + horizontal, /* -x */ + with_commas /* -m */ + }; + +static enum format format; + +/* `full-iso' uses full ISO-style dates and times. `long-iso' uses longer + ISO-style time stamps, though shorter than `full-iso'. `iso' uses shorter + ISO-style time stamps. `locale' uses locale-dependent time stamps. */ +enum time_style + { + full_iso_time_style, /* --time-style=full-iso */ + long_iso_time_style, /* --time-style=long-iso */ + iso_time_style, /* --time-style=iso */ + locale_time_style /* --time-style=locale */ + }; + +static char const *const time_style_args[] = +{ + "full-iso", "long-iso", "iso", "locale", NULL +}; +static enum time_style const time_style_types[] = +{ + full_iso_time_style, long_iso_time_style, iso_time_style, + locale_time_style +}; +ARGMATCH_VERIFY (time_style_args, time_style_types); + +/* Type of time to print or sort by. Controlled by -c and -u. + The values of each item of this enum are important since they are + used as indices in the sort functions array (see sort_files()). */ + +enum time_type + { + time_mtime, /* default */ + time_ctime, /* -c */ + time_atime, /* -u */ + time_numtypes /* the number of elements of this enum */ + }; + +static enum time_type time_type; + +/* The file characteristic to sort by. Controlled by -t, -S, -U, -X, -v. + The values of each item of this enum are important since they are + used as indices in the sort functions array (see sort_files()). */ + +enum sort_type + { + sort_none = -1, /* -U */ + sort_name, /* default */ + sort_extension, /* -X */ + sort_size, /* -S */ + sort_version, /* -v */ + sort_time, /* -t */ + sort_numtypes /* the number of elements of this enum */ + }; + +static enum sort_type sort_type; + +/* Direction of sort. + false means highest first if numeric, + lowest first if alphabetic; + these are the defaults. + true means the opposite order in each case. -r */ + +static bool sort_reverse; + +/* True means to display owner information. -g turns this off. */ + +static bool print_owner = true; + +/* True means to display author information. */ + +static bool print_author; + +/* True means to display group information. -G and -o turn this off. */ + +static bool print_group = true; + +/* True means print the user and group id's as numbers rather + than as names. -n */ + +static bool numeric_ids; + +/* True means mention the size in blocks of each file. -s */ + +static bool print_block_size; + +/* Human-readable options for output. */ +static int human_output_opts; + +/* The units to use when printing sizes other than file sizes. */ +static uintmax_t output_block_size; + +/* Likewise, but for file sizes. */ +static uintmax_t file_output_block_size = 1; + +/* Follow the output with a special string. Using this format, + Emacs' dired mode starts up twice as fast, and can handle all + strange characters in file names. */ +static bool dired; + +/* `none' means don't mention the type of files. + `slash' means mention directories only, with a '/'. + `file_type' means mention file types. + `classify' means mention file types and mark executables. + + Controlled by -F, -p, and --indicator-style. */ + +enum indicator_style + { + none, /* --indicator-style=none */ + slash, /* -p, --indicator-style=slash */ + file_type, /* --indicator-style=file-type */ + classify /* -F, --indicator-style=classify */ + }; + +static enum indicator_style indicator_style; + +/* Names of indicator styles. */ +static char const *const indicator_style_args[] = +{ + "none", "slash", "file-type", "classify", NULL +}; +static enum indicator_style const indicator_style_types[] = +{ + none, slash, file_type, classify +}; +ARGMATCH_VERIFY (indicator_style_args, indicator_style_types); + +/* True means use colors to mark types. Also define the different + colors as well as the stuff for the LS_COLORS environment variable. + The LS_COLORS variable is now in a termcap-like format. */ + +static bool print_with_color; + +/* Whether we used any colors in the output so far. If so, we will + need to restore the default color later. If not, we will need to + call prep_non_filename_text before using color for the first time. */ + +static bool used_color = false; + +enum color_type + { + color_never, /* 0: default or --color=never */ + color_always, /* 1: --color=always */ + color_if_tty /* 2: --color=tty */ + }; + +enum Dereference_symlink + { + DEREF_UNDEFINED = 1, + DEREF_NEVER, + DEREF_COMMAND_LINE_ARGUMENTS, /* -H */ + DEREF_COMMAND_LINE_SYMLINK_TO_DIR, /* the default, in certain cases */ + DEREF_ALWAYS /* -L */ + }; + +enum indicator_no + { + C_LEFT, C_RIGHT, C_END, C_RESET, C_NORM, C_FILE, C_DIR, C_LINK, + C_FIFO, C_SOCK, + C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR, C_SETUID, C_SETGID, + C_STICKY, C_OTHER_WRITABLE, C_STICKY_OTHER_WRITABLE, C_CAP, C_MULTIHARDLINK, + C_CLR_TO_EOL + }; + +static const char *const indicator_name[]= + { + "lc", "rc", "ec", "rs", "no", "fi", "di", "ln", "pi", "so", + "bd", "cd", "mi", "or", "ex", "do", "su", "sg", "st", + "ow", "tw", "ca", "mh", "cl", NULL + }; + +struct color_ext_type + { + struct bin_str ext; /* The extension we're looking for */ + struct bin_str seq; /* The sequence to output when we do */ + struct color_ext_type *next; /* Next in list */ + }; + +static struct bin_str color_indicator[] = + { + { LEN_STR_PAIR ("\033[") }, /* lc: Left of color sequence */ + { LEN_STR_PAIR ("m") }, /* rc: Right of color sequence */ + { 0, NULL }, /* ec: End color (replaces lc+no+rc) */ + { LEN_STR_PAIR ("0") }, /* rs: Reset to ordinary colors */ + { 0, NULL }, /* no: Normal */ + { 0, NULL }, /* fi: File: default */ + { LEN_STR_PAIR ("01;34") }, /* di: Directory: bright blue */ + { LEN_STR_PAIR ("01;36") }, /* ln: Symlink: bright cyan */ + { LEN_STR_PAIR ("33") }, /* pi: Pipe: yellow/brown */ + { LEN_STR_PAIR ("01;35") }, /* so: Socket: bright magenta */ + { LEN_STR_PAIR ("01;33") }, /* bd: Block device: bright yellow */ + { LEN_STR_PAIR ("01;33") }, /* cd: Char device: bright yellow */ + { 0, NULL }, /* mi: Missing file: undefined */ + { 0, NULL }, /* or: Orphaned symlink: undefined */ + { LEN_STR_PAIR ("01;32") }, /* ex: Executable: bright green */ + { LEN_STR_PAIR ("01;35") }, /* do: Door: bright magenta */ + { LEN_STR_PAIR ("37;41") }, /* su: setuid: white on red */ + { LEN_STR_PAIR ("30;43") }, /* sg: setgid: black on yellow */ + { LEN_STR_PAIR ("37;44") }, /* st: sticky: black on blue */ + { LEN_STR_PAIR ("34;42") }, /* ow: other-writable: blue on green */ + { LEN_STR_PAIR ("30;42") }, /* tw: ow w/ sticky: black on green */ + { LEN_STR_PAIR ("30;41") }, /* ca: black on red */ + { 0, NULL }, /* mh: disabled by default */ + { LEN_STR_PAIR ("\033[K") }, /* cl: clear to end of line */ + }; + +/* FIXME: comment */ +static struct color_ext_type *color_ext_list = NULL; + +/* Buffer for color sequences */ +static char *color_buf; + +/* True means to check for orphaned symbolic link, for displaying + colors. */ + +static bool check_symlink_color; + +/* True means mention the inode number of each file. -i */ + +static bool print_inode; + +/* What to do with symbolic links. Affected by -d, -F, -H, -l (and + other options that imply -l), and -L. */ + +static enum Dereference_symlink dereference; + +/* True means when a directory is found, display info on its + contents. -R */ + +static bool recursive; + +/* True means when an argument is a directory name, display info + on it itself. -d */ + +static bool immediate_dirs; + +/* True means that directories are grouped before files. */ + +static bool directories_first; + +/* Which files to ignore. */ + +static enum +{ + /* Ignore files whose names start with `.', and files specified by + --hide and --ignore. */ + IGNORE_DEFAULT, + + /* Ignore `.', `..', and files specified by --ignore. */ + IGNORE_DOT_AND_DOTDOT, + + /* Ignore only files specified by --ignore. */ + IGNORE_MINIMAL +} ignore_mode; + +/* A linked list of shell-style globbing patterns. If a non-argument + file name matches any of these patterns, it is ignored. + Controlled by -I. Multiple -I options accumulate. + The -B option adds `*~' and `.*~' to this list. */ + +struct ignore_pattern + { + const char *pattern; + struct ignore_pattern *next; + }; + +static struct ignore_pattern *ignore_patterns; + +/* Similar to IGNORE_PATTERNS, except that -a or -A causes this + variable itself to be ignored. */ +static struct ignore_pattern *hide_patterns; + +/* True means output nongraphic chars in file names as `?'. + (-q, --hide-control-chars) + qmark_funny_chars and the quoting style (-Q, --quoting-style=WORD) are + independent. The algorithm is: first, obey the quoting style to get a + string representing the file name; then, if qmark_funny_chars is set, + replace all nonprintable chars in that string with `?'. It's necessary + to replace nonprintable chars even in quoted strings, because we don't + want to mess up the terminal if control chars get sent to it, and some + quoting methods pass through control chars as-is. */ +static bool qmark_funny_chars; + +/* Quoting options for file and dir name output. */ + +static struct quoting_options *filename_quoting_options; +static struct quoting_options *dirname_quoting_options; + +/* The number of chars per hardware tab stop. Setting this to zero + inhibits the use of TAB characters for separating columns. -T */ +static size_t tabsize; + +/* True means print each directory name before listing it. */ + +static bool print_dir_name; + +/* The line length to use for breaking lines in many-per-line format. + Can be set with -w. */ + +static size_t line_length; + +/* If true, the file listing format requires that stat be called on + each file. */ + +static bool format_needs_stat; + +/* Similar to `format_needs_stat', but set if only the file type is + needed. */ + +static bool format_needs_type; + +/* An arbitrary limit on the number of bytes in a printed time stamp. + This is set to a relatively small value to avoid the need to worry + about denial-of-service attacks on servers that run "ls" on behalf + of remote clients. 1000 bytes should be enough for any practical + time stamp format. */ + +enum { TIME_STAMP_LEN_MAXIMUM = MAX (1000, INT_STRLEN_BOUND (time_t)) }; + +/* strftime formats for non-recent and recent files, respectively, in + -l output. */ + +static char const *long_time_format[2] = + { + /* strftime format for non-recent files (older than 6 months), in + -l output. This should contain the year, month and day (at + least), in an order that is understood by people in your + locale's territory. Please try to keep the number of used + screen columns small, because many people work in windows with + only 80 columns. But make this as wide as the other string + below, for recent files. */ + /* TRANSLATORS: ls output needs to be aligned for ease of reading, + so be wary of using variable width fields from the locale. + Note %b is handled specially by ls and aligned correctly. + Note also that specifying a width as in %5b is erroneous as strftime + will count bytes rather than characters in multibyte locales. */ + N_("%b %e %Y"), + /* strftime format for recent files (younger than 6 months), in -l + output. This should contain the month, day and time (at + least), in an order that is understood by people in your + locale's territory. Please try to keep the number of used + screen columns small, because many people work in windows with + only 80 columns. But make this as wide as the other string + above, for non-recent files. */ + /* TRANSLATORS: ls output needs to be aligned for ease of reading, + so be wary of using variable width fields from the locale. + Note %b is handled specially by ls and aligned correctly. + Note also that specifying a width as in %5b is erroneous as strftime + will count bytes rather than characters in multibyte locales. */ + N_("%b %e %H:%M") + }; + +/* The set of signals that are caught. */ + +static sigset_t caught_signals; + +/* If nonzero, the value of the pending fatal signal. */ + +static sig_atomic_t volatile interrupt_signal; + +/* A count of the number of pending stop signals that have been received. */ + +static sig_atomic_t volatile stop_signal_count; + +/* Desired exit status. */ + +static int exit_status; + +/* Exit statuses. */ +enum + { + /* "ls" had a minor problem. E.g., while processing a directory, + ls obtained the name of an entry via readdir, yet was later + unable to stat that name. This happens when listing a directory + in which entries are actively being removed or renamed. */ + LS_MINOR_PROBLEM = 1, + + /* "ls" had more serious trouble (e.g., memory exhausted, invalid + option or failure to stat a command line argument. */ + LS_FAILURE = 2 + }; + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + AUTHOR_OPTION = CHAR_MAX + 1, + BLOCK_SIZE_OPTION, + COLOR_OPTION, + DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION, + FILE_TYPE_INDICATOR_OPTION, + FORMAT_OPTION, + FULL_TIME_OPTION, + GROUP_DIRECTORIES_FIRST_OPTION, + HIDE_OPTION, + INDICATOR_STYLE_OPTION, + QUOTING_STYLE_OPTION, + SHOW_CONTROL_CHARS_OPTION, + SI_OPTION, + SORT_OPTION, + TIME_OPTION, + TIME_STYLE_OPTION +}; + +static struct option const long_options[] = +{ + {"all", no_argument, NULL, 'a'}, + {"escape", no_argument, NULL, 'b'}, + {"directory", no_argument, NULL, 'd'}, + {"dired", no_argument, NULL, 'D'}, + {"full-time", no_argument, NULL, FULL_TIME_OPTION}, + {"group-directories-first", no_argument, NULL, + GROUP_DIRECTORIES_FIRST_OPTION}, + {"human-readable", no_argument, NULL, 'h'}, + {"inode", no_argument, NULL, 'i'}, + {"numeric-uid-gid", no_argument, NULL, 'n'}, + {"no-group", no_argument, NULL, 'G'}, + {"hide-control-chars", no_argument, NULL, 'q'}, + {"reverse", no_argument, NULL, 'r'}, + {"size", no_argument, NULL, 's'}, + {"width", required_argument, NULL, 'w'}, + {"almost-all", no_argument, NULL, 'A'}, + {"ignore-backups", no_argument, NULL, 'B'}, + {"classify", no_argument, NULL, 'F'}, + {"file-type", no_argument, NULL, FILE_TYPE_INDICATOR_OPTION}, + {"si", no_argument, NULL, SI_OPTION}, + {"dereference-command-line", no_argument, NULL, 'H'}, + {"dereference-command-line-symlink-to-dir", no_argument, NULL, + DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION}, + {"hide", required_argument, NULL, HIDE_OPTION}, + {"ignore", required_argument, NULL, 'I'}, + {"indicator-style", required_argument, NULL, INDICATOR_STYLE_OPTION}, + {"dereference", no_argument, NULL, 'L'}, + {"literal", no_argument, NULL, 'N'}, + {"quote-name", no_argument, NULL, 'Q'}, + {"quoting-style", required_argument, NULL, QUOTING_STYLE_OPTION}, + {"recursive", no_argument, NULL, 'R'}, + {"format", required_argument, NULL, FORMAT_OPTION}, + {"show-control-chars", no_argument, NULL, SHOW_CONTROL_CHARS_OPTION}, + {"sort", required_argument, NULL, SORT_OPTION}, + {"tabsize", required_argument, NULL, 'T'}, + {"time", required_argument, NULL, TIME_OPTION}, + {"time-style", required_argument, NULL, TIME_STYLE_OPTION}, + {"color", optional_argument, NULL, COLOR_OPTION}, + {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION}, + {"context", no_argument, 0, 'Z'}, + {"author", no_argument, NULL, AUTHOR_OPTION}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static char const *const format_args[] = +{ + "verbose", "long", "commas", "horizontal", "across", + "vertical", "single-column", NULL +}; +static enum format const format_types[] = +{ + long_format, long_format, with_commas, horizontal, horizontal, + many_per_line, one_per_line +}; +ARGMATCH_VERIFY (format_args, format_types); + +static char const *const sort_args[] = +{ + "none", "time", "size", "extension", "version", NULL +}; +static enum sort_type const sort_types[] = +{ + sort_none, sort_time, sort_size, sort_extension, sort_version +}; +ARGMATCH_VERIFY (sort_args, sort_types); + +static char const *const time_args[] = +{ + "atime", "access", "use", "ctime", "status", NULL +}; +static enum time_type const time_types[] = +{ + time_atime, time_atime, time_atime, time_ctime, time_ctime +}; +ARGMATCH_VERIFY (time_args, time_types); + +static char const *const color_args[] = +{ + /* force and none are for compatibility with another color-ls version */ + "always", "yes", "force", + "never", "no", "none", + "auto", "tty", "if-tty", NULL +}; +static enum color_type const color_types[] = +{ + color_always, color_always, color_always, + color_never, color_never, color_never, + color_if_tty, color_if_tty, color_if_tty +}; +ARGMATCH_VERIFY (color_args, color_types); + +/* Information about filling a column. */ +struct column_info +{ + bool valid_len; + size_t line_len; + size_t *col_arr; +}; + +/* Array with information about column filledness. */ +static struct column_info *column_info; + +/* Maximum number of columns ever possible for this display. */ +static size_t max_idx; + +/* The minimum width of a column is 3: 1 character for the name and 2 + for the separating white space. */ +#define MIN_COLUMN_WIDTH 3 + + +/* This zero-based index is used solely with the --dired option. + When that option is in effect, this counter is incremented for each + byte of output generated by this program so that the beginning + and ending indices (in that output) of every file name can be recorded + and later output themselves. */ +static size_t dired_pos; + +#define DIRED_PUTCHAR(c) do {putchar ((c)); ++dired_pos;} while (0) + +/* Write S to STREAM and increment DIRED_POS by S_LEN. */ +#define DIRED_FPUTS(s, stream, s_len) \ + do {fputs (s, stream); dired_pos += s_len;} while (0) + +/* Like DIRED_FPUTS, but for use when S is a literal string. */ +#define DIRED_FPUTS_LITERAL(s, stream) \ + do {fputs (s, stream); dired_pos += sizeof (s) - 1;} while (0) + +#define DIRED_INDENT() \ + do \ + { \ + if (dired) \ + DIRED_FPUTS_LITERAL (" ", stdout); \ + } \ + while (0) + +/* With --dired, store pairs of beginning and ending indices of filenames. */ +static struct obstack dired_obstack; + +/* With --dired, store pairs of beginning and ending indices of any + directory names that appear as headers (just before `total' line) + for lists of directory entries. Such directory names are seen when + listing hierarchies using -R and when a directory is listed with at + least one other command line argument. */ +static struct obstack subdired_obstack; + +/* Save the current index on the specified obstack, OBS. */ +#define PUSH_CURRENT_DIRED_POS(obs) \ + do \ + { \ + if (dired) \ + obstack_grow (obs, &dired_pos, sizeof (dired_pos)); \ + } \ + while (0) + +/* With -R, this stack is used to help detect directory cycles. + The device/inode pairs on this stack mirror the pairs in the + active_dir_set hash table. */ +static struct obstack dev_ino_obstack; + +/* Push a pair onto the device/inode stack. */ +#define DEV_INO_PUSH(Dev, Ino) \ + do \ + { \ + struct dev_ino *di; \ + obstack_blank (&dev_ino_obstack, sizeof (struct dev_ino)); \ + di = -1 + (struct dev_ino *) obstack_next_free (&dev_ino_obstack); \ + di->st_dev = (Dev); \ + di->st_ino = (Ino); \ + } \ + while (0) + +/* Pop a dev/ino struct off the global dev_ino_obstack + and return that struct. */ +static struct dev_ino +dev_ino_pop (void) +{ + assert (sizeof (struct dev_ino) <= obstack_object_size (&dev_ino_obstack)); + obstack_blank (&dev_ino_obstack, -(int) (sizeof (struct dev_ino))); + return *(struct dev_ino *) obstack_next_free (&dev_ino_obstack); +} + +/* Note the use commented out below: +#define ASSERT_MATCHING_DEV_INO(Name, Di) \ + do \ + { \ + struct stat sb; \ + assert (Name); \ + assert (0 <= stat (Name, &sb)); \ + assert (sb.st_dev == Di.st_dev); \ + assert (sb.st_ino == Di.st_ino); \ + } \ + while (0) +*/ + +/* Write to standard output PREFIX, followed by the quoting style and + a space-separated list of the integers stored in OS all on one line. */ + +static void +dired_dump_obstack (const char *prefix, struct obstack *os) +{ + size_t n_pos; + + n_pos = obstack_object_size (os) / sizeof (dired_pos); + if (n_pos > 0) + { + size_t i; + size_t *pos; + + pos = (size_t *) obstack_finish (os); + fputs (prefix, stdout); + for (i = 0; i < n_pos; i++) + printf (" %lu", (unsigned long int) pos[i]); + putchar ('\n'); + } +} + +/* Read the abbreviated month names from the locale, to align them + and to determine the max width of the field and to truncate names + greater than our max allowed. + Note even though this handles multibyte locales correctly + it's not restricted to them as single byte locales can have + variable width abbreviated months and also precomputing/caching + the names was seen to increase the performance of ls significantly. */ + +/* max number of display cells to use */ +enum { MAX_MON_WIDTH = 5 }; +/* In the unlikely event that the abmon[] storage is not big enough + an error message will be displayed, and we revert to using + unmodified abbreviated month names from the locale database. */ +static char abmon[12][MAX_MON_WIDTH * 2 * MB_LEN_MAX + 1]; +/* minimum width needed to align %b, 0 => don't use precomputed values. */ +static size_t required_mon_width; + +static size_t +abmon_init (void) +{ +#ifdef HAVE_NL_LANGINFO + required_mon_width = MAX_MON_WIDTH; + size_t curr_max_width; + do + { + curr_max_width = required_mon_width; + required_mon_width = 0; + for (int i = 0; i < 12; i++) + { + size_t width = curr_max_width; + + size_t req = mbsalign (nl_langinfo (ABMON_1 + i), + abmon[i], sizeof (abmon[i]), + &width, MBS_ALIGN_LEFT, 0); + + if (req == (size_t) -1 || req >= sizeof (abmon[i])) + { + required_mon_width = 0; /* ignore precomputed strings. */ + return required_mon_width; + } + + required_mon_width = MAX (required_mon_width, width); + } + } + while (curr_max_width > required_mon_width); +#endif + + return required_mon_width; +} + +static size_t +dev_ino_hash (void const *x, size_t table_size) +{ + struct dev_ino const *p = x; + return (uintmax_t) p->st_ino % table_size; +} + +static bool +dev_ino_compare (void const *x, void const *y) +{ + struct dev_ino const *a = x; + struct dev_ino const *b = y; + return SAME_INODE (*a, *b) ? true : false; +} + +static void +dev_ino_free (void *x) +{ + free (x); +} + +/* Add the device/inode pair (P->st_dev/P->st_ino) to the set of + active directories. Return true if there is already a matching + entry in the table. */ + +static bool +visit_dir (dev_t dev, ino_t ino) +{ + struct dev_ino *ent; + struct dev_ino *ent_from_table; + bool found_match; + + ent = xmalloc (sizeof *ent); + ent->st_ino = ino; + ent->st_dev = dev; + + /* Attempt to insert this entry into the table. */ + ent_from_table = hash_insert (active_dir_set, ent); + + if (ent_from_table == NULL) + { + /* Insertion failed due to lack of memory. */ + xalloc_die (); + } + + found_match = (ent_from_table != ent); + + if (found_match) + { + /* ent was not inserted, so free it. */ + free (ent); + } + + return found_match; +} + +static void +free_pending_ent (struct pending *p) +{ + free (p->name); + free (p->realname); + free (p); +} + +static bool +is_colored (enum indicator_no type) +{ + size_t len = color_indicator[type].len; + char const *s = color_indicator[type].string; + return ! (len == 0 + || (len == 1 && strncmp (s, "0", 1) == 0) + || (len == 2 && strncmp (s, "00", 2) == 0)); +} + +static void +restore_default_color (void) +{ + put_indicator (&color_indicator[C_LEFT]); + put_indicator (&color_indicator[C_RIGHT]); +} + +/* An ordinary signal was received; arrange for the program to exit. */ + +static void +sighandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, SIG_IGN); + if (! interrupt_signal) + interrupt_signal = sig; +} + +/* A SIGTSTP was received; arrange for the program to suspend itself. */ + +static void +stophandler (int sig) +{ + if (! SA_NOCLDSTOP) + signal (sig, stophandler); + if (! interrupt_signal) + stop_signal_count++; +} + +/* Process any pending signals. If signals are caught, this function + should be called periodically. Ideally there should never be an + unbounded amount of time when signals are not being processed. + Signal handling can restore the default colors, so callers must + immediately change colors after invoking this function. */ + +static void +process_signals (void) +{ + while (interrupt_signal || stop_signal_count) + { + int sig; + int stops; + sigset_t oldset; + + if (used_color) + restore_default_color (); + fflush (stdout); + + sigprocmask (SIG_BLOCK, &caught_signals, &oldset); + + /* Reload interrupt_signal and stop_signal_count, in case a new + signal was handled before sigprocmask took effect. */ + sig = interrupt_signal; + stops = stop_signal_count; + + /* SIGTSTP is special, since the application can receive that signal + more than once. In this case, don't set the signal handler to the + default. Instead, just raise the uncatchable SIGSTOP. */ + if (stops) + { + stop_signal_count = stops - 1; + sig = SIGSTOP; + } + else + signal (sig, SIG_DFL); + + /* Exit or suspend the program. */ + raise (sig); + sigprocmask (SIG_SETMASK, &oldset, NULL); + + /* If execution reaches here, then the program has been + continued (after being suspended). */ + } +} + +int +main (int argc, char **argv) +{ + int i; + struct pending *thispend; + int n_files; + + /* The signals that are trapped, and the number of such signals. */ + static int const sig[] = + { + /* This one is handled specially. */ + SIGTSTP, + + /* The usual suspects. */ + SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif + }; + enum { nsigs = ARRAY_CARDINALITY (sig) }; + +#if ! SA_NOCLDSTOP + bool caught_sig[nsigs]; +#endif + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + initialize_exit_failure (LS_FAILURE); + atexit (close_stdout); + + assert (ARRAY_CARDINALITY (color_indicator) + 1 + == ARRAY_CARDINALITY (indicator_name)); + + exit_status = EXIT_SUCCESS; + print_dir_name = true; + pending_dirs = NULL; + + current_time.tv_sec = TYPE_MINIMUM (time_t); + current_time.tv_nsec = -1; + + i = decode_switches (argc, argv); + + if (print_with_color) + parse_ls_color (); + + /* Test print_with_color again, because the call to parse_ls_color + may have just reset it -- e.g., if LS_COLORS is invalid. */ + if (print_with_color) + { + /* Avoid following symbolic links when possible. */ + if (is_colored (C_ORPHAN) + || (is_colored (C_EXEC) && color_symlink_as_referent) + || (is_colored (C_MISSING) && format == long_format)) + check_symlink_color = true; + + /* If the standard output is a controlling terminal, watch out + for signals, so that the colors can be restored to the + default state if "ls" is suspended or interrupted. */ + + if (0 <= tcgetpgrp (STDOUT_FILENO)) + { + int j; +#if SA_NOCLDSTOP + struct sigaction act; + + sigemptyset (&caught_signals); + for (j = 0; j < nsigs; j++) + { + sigaction (sig[j], NULL, &act); + if (act.sa_handler != SIG_IGN) + sigaddset (&caught_signals, sig[j]); + } + + act.sa_mask = caught_signals; + act.sa_flags = SA_RESTART; + + for (j = 0; j < nsigs; j++) + if (sigismember (&caught_signals, sig[j])) + { + act.sa_handler = sig[j] == SIGTSTP ? stophandler : sighandler; + sigaction (sig[j], &act, NULL); + } +#else + for (j = 0; j < nsigs; j++) + { + caught_sig[j] = (signal (sig[j], SIG_IGN) != SIG_IGN); + if (caught_sig[j]) + { + signal (sig[j], sig[j] == SIGTSTP ? stophandler : sighandler); + siginterrupt (sig[j], 0); + } + } +#endif + } + } + + if (dereference == DEREF_UNDEFINED) + dereference = ((immediate_dirs + || indicator_style == classify + || format == long_format) + ? DEREF_NEVER + : DEREF_COMMAND_LINE_SYMLINK_TO_DIR); + + /* When using -R, initialize a data structure we'll use to + detect any directory cycles. */ + if (recursive) + { + active_dir_set = hash_initialize (INITIAL_TABLE_SIZE, NULL, + dev_ino_hash, + dev_ino_compare, + dev_ino_free); + if (active_dir_set == NULL) + xalloc_die (); + + obstack_init (&dev_ino_obstack); + } + + format_needs_stat = sort_type == sort_time || sort_type == sort_size + || format == long_format + || print_scontext + || print_block_size; + format_needs_type = (! format_needs_stat + && (recursive + || print_with_color + || indicator_style != none + || directories_first)); + + if (dired) + { + obstack_init (&dired_obstack); + obstack_init (&subdired_obstack); + } + + cwd_n_alloc = 100; + cwd_file = xnmalloc (cwd_n_alloc, sizeof *cwd_file); + cwd_n_used = 0; + + clear_files (); + + n_files = argc - i; + + if (n_files <= 0) + { + if (immediate_dirs) + gobble_file (".", directory, NOT_AN_INODE_NUMBER, true, ""); + else + queue_directory (".", NULL, true); + } + else + do + gobble_file (argv[i++], unknown, NOT_AN_INODE_NUMBER, true, ""); + while (i < argc); + + if (cwd_n_used) + { + sort_files (); + if (!immediate_dirs) + extract_dirs_from_files (NULL, true); + /* `cwd_n_used' might be zero now. */ + } + + /* In the following if/else blocks, it is sufficient to test `pending_dirs' + (and not pending_dirs->name) because there may be no markers in the queue + at this point. A marker may be enqueued when extract_dirs_from_files is + called with a non-empty string or via print_dir. */ + if (cwd_n_used) + { + print_current_files (); + if (pending_dirs) + DIRED_PUTCHAR ('\n'); + } + else if (n_files <= 1 && pending_dirs && pending_dirs->next == 0) + print_dir_name = false; + + while (pending_dirs) + { + thispend = pending_dirs; + pending_dirs = pending_dirs->next; + + if (LOOP_DETECT) + { + if (thispend->name == NULL) + { + /* thispend->name == NULL means this is a marker entry + indicating we've finished processing the directory. + Use its dev/ino numbers to remove the corresponding + entry from the active_dir_set hash table. */ + struct dev_ino di = dev_ino_pop (); + struct dev_ino *found = hash_delete (active_dir_set, &di); + /* ASSERT_MATCHING_DEV_INO (thispend->realname, di); */ + assert (found); + dev_ino_free (found); + free_pending_ent (thispend); + continue; + } + } + + print_dir (thispend->name, thispend->realname, + thispend->command_line_arg); + + free_pending_ent (thispend); + print_dir_name = true; + } + + if (print_with_color) + { + int j; + + if (used_color) + restore_default_color (); + fflush (stdout); + + /* Restore the default signal handling. */ +#if SA_NOCLDSTOP + for (j = 0; j < nsigs; j++) + if (sigismember (&caught_signals, sig[j])) + signal (sig[j], SIG_DFL); +#else + for (j = 0; j < nsigs; j++) + if (caught_sig[j]) + signal (sig[j], SIG_DFL); +#endif + + /* Act on any signals that arrived before the default was restored. + This can process signals out of order, but there doesn't seem to + be an easy way to do them in order, and the order isn't that + important anyway. */ + for (j = stop_signal_count; j; j--) + raise (SIGSTOP); + j = interrupt_signal; + if (j) + raise (j); + } + + if (dired) + { + /* No need to free these since we're about to exit. */ + dired_dump_obstack ("//DIRED//", &dired_obstack); + dired_dump_obstack ("//SUBDIRED//", &subdired_obstack); + printf ("//DIRED-OPTIONS// --quoting-style=%s\n", + quoting_style_args[get_quoting_style (filename_quoting_options)]); + } + + if (LOOP_DETECT) + { + assert (hash_get_n_entries (active_dir_set) == 0); + hash_free (active_dir_set); + } + + exit (exit_status); +} + +/* Set all the option flags according to the switches specified. + Return the index of the first non-option argument. */ + +static int +decode_switches (int argc, char **argv) +{ + char *time_style_option = NULL; + + /* Record whether there is an option specifying sort type. */ + bool sort_type_specified = false; + + qmark_funny_chars = false; + + /* initialize all switches to default settings */ + + switch (ls_mode) + { + case LS_MULTI_COL: + /* This is for the `dir' program. */ + format = many_per_line; + set_quoting_style (NULL, escape_quoting_style); + break; + + case LS_LONG_FORMAT: + /* This is for the `vdir' program. */ + format = long_format; + set_quoting_style (NULL, escape_quoting_style); + break; + + case LS_LS: + /* This is for the `ls' program. */ + if (isatty (STDOUT_FILENO)) + { + format = many_per_line; + /* See description of qmark_funny_chars, above. */ + qmark_funny_chars = true; + } + else + { + format = one_per_line; + qmark_funny_chars = false; + } + break; + + default: + abort (); + } + + time_type = time_mtime; + sort_type = sort_name; + sort_reverse = false; + numeric_ids = false; + print_block_size = false; + indicator_style = none; + print_inode = false; + dereference = DEREF_UNDEFINED; + recursive = false; + immediate_dirs = false; + ignore_mode = IGNORE_DEFAULT; + ignore_patterns = NULL; + hide_patterns = NULL; + print_scontext = false; + + /* FIXME: put this in a function. */ + { + char const *q_style = getenv ("QUOTING_STYLE"); + if (q_style) + { + int i = ARGMATCH (q_style, quoting_style_args, quoting_style_vals); + if (0 <= i) + set_quoting_style (NULL, quoting_style_vals[i]); + else + error (0, 0, + _("ignoring invalid value of environment variable QUOTING_STYLE: %s"), + quotearg (q_style)); + } + } + + { + char const *ls_block_size = getenv ("LS_BLOCK_SIZE"); + human_options (ls_block_size, + &human_output_opts, &output_block_size); + if (ls_block_size || getenv ("BLOCK_SIZE")) + file_output_block_size = output_block_size; + } + + line_length = 80; + { + char const *p = getenv ("COLUMNS"); + if (p && *p) + { + unsigned long int tmp_ulong; + if (xstrtoul (p, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK + && 0 < tmp_ulong && tmp_ulong <= SIZE_MAX) + { + line_length = tmp_ulong; + } + else + { + error (0, 0, + _("ignoring invalid width in environment variable COLUMNS: %s"), + quotearg (p)); + } + } + } + +#ifdef TIOCGWINSZ + { + struct winsize ws; + + if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 + && 0 < ws.ws_col && ws.ws_col == (size_t) ws.ws_col) + line_length = ws.ws_col; + } +#endif + + { + char const *p = getenv ("TABSIZE"); + tabsize = 8; + if (p) + { + unsigned long int tmp_ulong; + if (xstrtoul (p, NULL, 0, &tmp_ulong, NULL) == LONGINT_OK + && tmp_ulong <= SIZE_MAX) + { + tabsize = tmp_ulong; + } + else + { + error (0, 0, + _("ignoring invalid tab size in environment variable TABSIZE: %s"), + quotearg (p)); + } + } + } + + for (;;) + { + int oi = -1; + int c = getopt_long (argc, argv, + "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UXZ1", + long_options, &oi); + if (c == -1) + break; + + switch (c) + { + case 'a': + ignore_mode = IGNORE_MINIMAL; + break; + + case 'b': + set_quoting_style (NULL, escape_quoting_style); + break; + + case 'c': + time_type = time_ctime; + break; + + case 'd': + immediate_dirs = true; + break; + + case 'f': + /* Same as enabling -a -U and disabling -l -s. */ + ignore_mode = IGNORE_MINIMAL; + sort_type = sort_none; + sort_type_specified = true; + /* disable -l */ + if (format == long_format) + format = (isatty (STDOUT_FILENO) ? many_per_line : one_per_line); + print_block_size = false; /* disable -s */ + print_with_color = false; /* disable --color */ + break; + + case FILE_TYPE_INDICATOR_OPTION: /* --file-type */ + indicator_style = file_type; + break; + + case 'g': + format = long_format; + print_owner = false; + break; + + case 'h': + human_output_opts = human_autoscale | human_SI | human_base_1024; + file_output_block_size = output_block_size = 1; + break; + + case 'i': + print_inode = true; + break; + + case 'k': + human_output_opts = 0; + file_output_block_size = output_block_size = 1024; + break; + + case 'l': + format = long_format; + break; + + case 'm': + format = with_commas; + break; + + case 'n': + numeric_ids = true; + format = long_format; + break; + + case 'o': /* Just like -l, but don't display group info. */ + format = long_format; + print_group = false; + break; + + case 'p': + indicator_style = slash; + break; + + case 'q': + qmark_funny_chars = true; + break; + + case 'r': + sort_reverse = true; + break; + + case 's': + print_block_size = true; + break; + + case 't': + sort_type = sort_time; + sort_type_specified = true; + break; + + case 'u': + time_type = time_atime; + break; + + case 'v': + sort_type = sort_version; + sort_type_specified = true; + break; + + case 'w': + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) != LONGINT_OK + || ! (0 < tmp_ulong && tmp_ulong <= SIZE_MAX)) + error (LS_FAILURE, 0, _("invalid line width: %s"), + quotearg (optarg)); + line_length = tmp_ulong; + break; + } + + case 'x': + format = horizontal; + break; + + case 'A': + if (ignore_mode == IGNORE_DEFAULT) + ignore_mode = IGNORE_DOT_AND_DOTDOT; + break; + + case 'B': + add_ignore_pattern ("*~"); + add_ignore_pattern (".*~"); + break; + + case 'C': + format = many_per_line; + break; + + case 'D': + dired = true; + break; + + case 'F': + indicator_style = classify; + break; + + case 'G': /* inhibit display of group info */ + print_group = false; + break; + + case 'H': + dereference = DEREF_COMMAND_LINE_ARGUMENTS; + break; + + case DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR_OPTION: + dereference = DEREF_COMMAND_LINE_SYMLINK_TO_DIR; + break; + + case 'I': + add_ignore_pattern (optarg); + break; + + case 'L': + dereference = DEREF_ALWAYS; + break; + + case 'N': + set_quoting_style (NULL, literal_quoting_style); + break; + + case 'Q': + set_quoting_style (NULL, c_quoting_style); + break; + + case 'R': + recursive = true; + break; + + case 'S': + sort_type = sort_size; + sort_type_specified = true; + break; + + case 'T': + { + unsigned long int tmp_ulong; + if (xstrtoul (optarg, NULL, 0, &tmp_ulong, NULL) != LONGINT_OK + || SIZE_MAX < tmp_ulong) + error (LS_FAILURE, 0, _("invalid tab size: %s"), + quotearg (optarg)); + tabsize = tmp_ulong; + break; + } + + case 'U': + sort_type = sort_none; + sort_type_specified = true; + break; + + case 'X': + sort_type = sort_extension; + sort_type_specified = true; + break; + + case '1': + /* -1 has no effect after -l. */ + if (format != long_format) + format = one_per_line; + break; + + case AUTHOR_OPTION: + print_author = true; + break; + + case HIDE_OPTION: + { + struct ignore_pattern *hide = xmalloc (sizeof *hide); + hide->pattern = optarg; + hide->next = hide_patterns; + hide_patterns = hide; + } + break; + + case SORT_OPTION: + sort_type = XARGMATCH ("--sort", optarg, sort_args, sort_types); + sort_type_specified = true; + break; + + case GROUP_DIRECTORIES_FIRST_OPTION: + directories_first = true; + break; + + case TIME_OPTION: + time_type = XARGMATCH ("--time", optarg, time_args, time_types); + break; + + case FORMAT_OPTION: + format = XARGMATCH ("--format", optarg, format_args, format_types); + break; + + case FULL_TIME_OPTION: + format = long_format; + time_style_option = bad_cast ("full-iso"); + break; + + case COLOR_OPTION: + { + int i; + if (optarg) + i = XARGMATCH ("--color", optarg, color_args, color_types); + else + /* Using --color with no argument is equivalent to using + --color=always. */ + i = color_always; + + print_with_color = (i == color_always + || (i == color_if_tty + && isatty (STDOUT_FILENO))); + + if (print_with_color) + { + /* Don't use TAB characters in output. Some terminal + emulators can't handle the combination of tabs and + color codes on the same line. */ + tabsize = 0; + } + break; + } + + case INDICATOR_STYLE_OPTION: + indicator_style = XARGMATCH ("--indicator-style", optarg, + indicator_style_args, + indicator_style_types); + break; + + case QUOTING_STYLE_OPTION: + set_quoting_style (NULL, + XARGMATCH ("--quoting-style", optarg, + quoting_style_args, + quoting_style_vals)); + break; + + case TIME_STYLE_OPTION: + time_style_option = optarg; + break; + + case SHOW_CONTROL_CHARS_OPTION: + qmark_funny_chars = false; + break; + + case BLOCK_SIZE_OPTION: + { + enum strtol_error e = human_options (optarg, &human_output_opts, + &output_block_size); + if (e != LONGINT_OK) + xstrtol_fatal (e, oi, 0, long_options, optarg); + file_output_block_size = output_block_size; + } + break; + + case SI_OPTION: + human_output_opts = human_autoscale | human_SI; + file_output_block_size = output_block_size = 1; + break; + + case 'Z': + print_scontext = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (LS_FAILURE); + } + } + + max_idx = MAX (1, line_length / MIN_COLUMN_WIDTH); + + filename_quoting_options = clone_quoting_options (NULL); + if (get_quoting_style (filename_quoting_options) == escape_quoting_style) + set_char_quoting (filename_quoting_options, ' ', 1); + if (file_type <= indicator_style) + { + char const *p; + for (p = "*=>@|" + indicator_style - file_type; *p; p++) + set_char_quoting (filename_quoting_options, *p, 1); + } + + dirname_quoting_options = clone_quoting_options (NULL); + set_char_quoting (dirname_quoting_options, ':', 1); + + /* --dired is meaningful only with --format=long (-l). + Otherwise, ignore it. FIXME: warn about this? + Alternatively, make --dired imply --format=long? */ + if (dired && format != long_format) + dired = false; + + /* If -c or -u is specified and not -l (or any other option that implies -l), + and no sort-type was specified, then sort by the ctime (-c) or atime (-u). + The behavior of ls when using either -c or -u but with neither -l nor -t + appears to be unspecified by POSIX. So, with GNU ls, `-u' alone means + sort by atime (this is the one that's not specified by the POSIX spec), + -lu means show atime and sort by name, -lut means show atime and sort + by atime. */ + + if ((time_type == time_ctime || time_type == time_atime) + && !sort_type_specified && format != long_format) + { + sort_type = sort_time; + } + + if (format == long_format) + { + char *style = time_style_option; + static char const posix_prefix[] = "posix-"; + + if (! style) + if (! (style = getenv ("TIME_STYLE"))) + style = bad_cast ("locale"); + + while (strncmp (style, posix_prefix, sizeof posix_prefix - 1) == 0) + { + if (! hard_locale (LC_TIME)) + return optind; + style += sizeof posix_prefix - 1; + } + + if (*style == '+') + { + char *p0 = style + 1; + char *p1 = strchr (p0, '\n'); + if (! p1) + p1 = p0; + else + { + if (strchr (p1 + 1, '\n')) + error (LS_FAILURE, 0, _("invalid time style format %s"), + quote (p0)); + *p1++ = '\0'; + } + long_time_format[0] = p0; + long_time_format[1] = p1; + } + else + switch (XARGMATCH ("time style", style, + time_style_args, + time_style_types)) + { + case full_iso_time_style: + long_time_format[0] = long_time_format[1] = + "%Y-%m-%d %H:%M:%S.%N %z"; + break; + + case long_iso_time_style: + case_long_iso_time_style: + long_time_format[0] = long_time_format[1] = "%Y-%m-%d %H:%M"; + break; + + case iso_time_style: + long_time_format[0] = "%Y-%m-%d "; + long_time_format[1] = "%m-%d %H:%M"; + break; + + case locale_time_style: + if (hard_locale (LC_TIME)) + { + /* Ensure that the locale has translations for both + formats. If not, fall back on long-iso format. */ + int i; + for (i = 0; i < 2; i++) + { + char const *locale_format = + dcgettext (NULL, long_time_format[i], LC_TIME); + if (locale_format == long_time_format[i]) + goto case_long_iso_time_style; + long_time_format[i] = locale_format; + } + } + } + /* Note we leave %5b etc. alone so user widths/flags are honored. */ + if (strstr (long_time_format[0],"%b") || strstr (long_time_format[1],"%b")) + if (!abmon_init ()) + error (0, 0, _("error initializing month strings")); + } + + return optind; +} + +/* Parse a string as part of the LS_COLORS variable; this may involve + decoding all kinds of escape characters. If equals_end is set an + unescaped equal sign ends the string, otherwise only a : or \0 + does. Set *OUTPUT_COUNT to the number of bytes output. Return + true if successful. + + The resulting string is *not* null-terminated, but may contain + embedded nulls. + + Note that both dest and src are char **; on return they point to + the first free byte after the array and the character that ended + the input string, respectively. */ + +static bool +get_funky_string (char **dest, const char **src, bool equals_end, + size_t *output_count) +{ + char num; /* For numerical codes */ + size_t count; /* Something to count with */ + enum { + ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX, ST_CARET, ST_END, ST_ERROR + } state; + const char *p; + char *q; + + p = *src; /* We don't want to double-indirect */ + q = *dest; /* the whole darn time. */ + + count = 0; /* No characters counted in yet. */ + num = 0; + + state = ST_GND; /* Start in ground state. */ + while (state < ST_END) + { + switch (state) + { + case ST_GND: /* Ground state (no escapes) */ + switch (*p) + { + case ':': + case '\0': + state = ST_END; /* End of string */ + break; + case '\\': + state = ST_BACKSLASH; /* Backslash scape sequence */ + ++p; + break; + case '^': + state = ST_CARET; /* Caret escape */ + ++p; + break; + case '=': + if (equals_end) + { + state = ST_END; /* End */ + break; + } + /* else fall through */ + default: + *(q++) = *(p++); + ++count; + break; + } + break; + + case ST_BACKSLASH: /* Backslash escaped character */ + switch (*p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state = ST_OCTAL; /* Octal sequence */ + num = *p - '0'; + break; + case 'x': + case 'X': + state = ST_HEX; /* Hex sequence */ + num = 0; + break; + case 'a': /* Bell */ + num = '\a'; + break; + case 'b': /* Backspace */ + num = '\b'; + break; + case 'e': /* Escape */ + num = 27; + break; + case 'f': /* Form feed */ + num = '\f'; + break; + case 'n': /* Newline */ + num = '\n'; + break; + case 'r': /* Carriage return */ + num = '\r'; + break; + case 't': /* Tab */ + num = '\t'; + break; + case 'v': /* Vtab */ + num = '\v'; + break; + case '?': /* Delete */ + num = 127; + break; + case '_': /* Space */ + num = ' '; + break; + case '\0': /* End of string */ + state = ST_ERROR; /* Error! */ + break; + default: /* Escaped character like \ ^ : = */ + num = *p; + break; + } + if (state == ST_BACKSLASH) + { + *(q++) = num; + ++count; + state = ST_GND; + } + ++p; + break; + + case ST_OCTAL: /* Octal sequence */ + if (*p < '0' || *p > '7') + { + *(q++) = num; + ++count; + state = ST_GND; + } + else + num = (num << 3) + (*(p++) - '0'); + break; + + case ST_HEX: /* Hex sequence */ + switch (*p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + num = (num << 4) + (*(p++) - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + num = (num << 4) + (*(p++) - 'a') + 10; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + num = (num << 4) + (*(p++) - 'A') + 10; + break; + default: + *(q++) = num; + ++count; + state = ST_GND; + break; + } + break; + + case ST_CARET: /* Caret escape */ + state = ST_GND; /* Should be the next state... */ + if (*p >= '@' && *p <= '~') + { + *(q++) = *(p++) & 037; + ++count; + } + else if (*p == '?') + { + *(q++) = 127; + ++count; + } + else + state = ST_ERROR; + break; + + default: + abort (); + } + } + + *dest = q; + *src = p; + *output_count = count; + + return state != ST_ERROR; +} + +static void +parse_ls_color (void) +{ + const char *p; /* Pointer to character being parsed */ + char *buf; /* color_buf buffer pointer */ + int state; /* State of parser */ + int ind_no; /* Indicator number */ + char label[3]; /* Indicator label */ + struct color_ext_type *ext; /* Extension we are working on */ + + if ((p = getenv ("LS_COLORS")) == NULL || *p == '\0') + return; + + ext = NULL; + strcpy (label, "??"); + + /* This is an overly conservative estimate, but any possible + LS_COLORS string will *not* generate a color_buf longer than + itself, so it is a safe way of allocating a buffer in + advance. */ + buf = color_buf = xstrdup (p); + + state = 1; + while (state > 0) + { + switch (state) + { + case 1: /* First label character */ + switch (*p) + { + case ':': + ++p; + break; + + case '*': + /* Allocate new extension block and add to head of + linked list (this way a later definition will + override an earlier one, which can be useful for + having terminal-specific defs override global). */ + + ext = xmalloc (sizeof *ext); + ext->next = color_ext_list; + color_ext_list = ext; + + ++p; + ext->ext.string = buf; + + state = (get_funky_string (&buf, &p, true, &ext->ext.len) + ? 4 : -1); + break; + + case '\0': + state = 0; /* Done! */ + break; + + default: /* Assume it is file type label */ + label[0] = *(p++); + state = 2; + break; + } + break; + + case 2: /* Second label character */ + if (*p) + { + label[1] = *(p++); + state = 3; + } + else + state = -1; /* Error */ + break; + + case 3: /* Equal sign after indicator label */ + state = -1; /* Assume failure... */ + if (*(p++) == '=')/* It *should* be... */ + { + for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) + { + if (STREQ (label, indicator_name[ind_no])) + { + color_indicator[ind_no].string = buf; + state = (get_funky_string (&buf, &p, false, + &color_indicator[ind_no].len) + ? 1 : -1); + break; + } + } + if (state == -1) + error (0, 0, _("unrecognized prefix: %s"), quotearg (label)); + } + break; + + case 4: /* Equal sign after *.ext */ + if (*(p++) == '=') + { + ext->seq.string = buf; + state = (get_funky_string (&buf, &p, false, &ext->seq.len) + ? 1 : -1); + } + else + state = -1; + break; + } + } + + if (state < 0) + { + struct color_ext_type *e; + struct color_ext_type *e2; + + error (0, 0, + _("unparsable value for LS_COLORS environment variable")); + free (color_buf); + for (e = color_ext_list; e != NULL; /* empty */) + { + e2 = e; + e = e->next; + free (e2); + } + print_with_color = false; + } + + if (color_indicator[C_LINK].len == 6 + && !strncmp (color_indicator[C_LINK].string, "target", 6)) + color_symlink_as_referent = true; +} + +/* Set the exit status to report a failure. If SERIOUS, it is a + serious failure; otherwise, it is merely a minor problem. */ + +static void +set_exit_status (bool serious) +{ + if (serious) + exit_status = LS_FAILURE; + else if (exit_status == EXIT_SUCCESS) + exit_status = LS_MINOR_PROBLEM; +} + +/* Assuming a failure is serious if SERIOUS, use the printf-style + MESSAGE to report the failure to access a file named FILE. Assume + errno is set appropriately for the failure. */ + +static void +file_failure (bool serious, char const *message, char const *file) +{ + error (0, errno, message, quotearg_colon (file)); + set_exit_status (serious); +} + +/* Request that the directory named NAME have its contents listed later. + If REALNAME is nonzero, it will be used instead of NAME when the + directory name is printed. This allows symbolic links to directories + to be treated as regular directories but still be listed under their + real names. NAME == NULL is used to insert a marker entry for the + directory named in REALNAME. + If NAME is non-NULL, we use its dev/ino information to save + a call to stat -- when doing a recursive (-R) traversal. + COMMAND_LINE_ARG means this directory was mentioned on the command line. */ + +static void +queue_directory (char const *name, char const *realname, bool command_line_arg) +{ + struct pending *new = xmalloc (sizeof *new); + new->realname = realname ? xstrdup (realname) : NULL; + new->name = name ? xstrdup (name) : NULL; + new->command_line_arg = command_line_arg; + new->next = pending_dirs; + pending_dirs = new; +} + +/* Read directory NAME, and list the files in it. + If REALNAME is nonzero, print its name instead of NAME; + this is used for symbolic links to directories. + COMMAND_LINE_ARG means this directory was mentioned on the command line. */ + +static void +print_dir (char const *name, char const *realname, bool command_line_arg) +{ + DIR *dirp; + struct dirent *next; + uintmax_t total_blocks = 0; + static bool first = true; + + errno = 0; + dirp = opendir (name); + if (!dirp) + { + file_failure (command_line_arg, _("cannot open directory %s"), name); + return; + } + + if (LOOP_DETECT) + { + struct stat dir_stat; + int fd = dirfd (dirp); + + /* If dirfd failed, endure the overhead of using stat. */ + if ((0 <= fd + ? fstat (fd, &dir_stat) + : stat (name, &dir_stat)) < 0) + { + file_failure (command_line_arg, + _("cannot determine device and inode of %s"), name); + closedir (dirp); + return; + } + + /* If we've already visited this dev/inode pair, warn that + we've found a loop, and do not process this directory. */ + if (visit_dir (dir_stat.st_dev, dir_stat.st_ino)) + { + error (0, 0, _("%s: not listing already-listed directory"), + quotearg_colon (name)); + closedir (dirp); + set_exit_status (true); + return; + } + + DEV_INO_PUSH (dir_stat.st_dev, dir_stat.st_ino); + } + + if (recursive || print_dir_name) + { + if (!first) + DIRED_PUTCHAR ('\n'); + first = false; + DIRED_INDENT (); + PUSH_CURRENT_DIRED_POS (&subdired_obstack); + dired_pos += quote_name (stdout, realname ? realname : name, + dirname_quoting_options, NULL); + PUSH_CURRENT_DIRED_POS (&subdired_obstack); + DIRED_FPUTS_LITERAL (":\n", stdout); + } + + /* Read the directory entries, and insert the subfiles into the `cwd_file' + table. */ + + clear_files (); + + while (1) + { + /* Set errno to zero so we can distinguish between a readdir failure + and when readdir simply finds that there are no more entries. */ + errno = 0; + next = readdir (dirp); + if (next) + { + if (! file_ignored (next->d_name)) + { + enum filetype type = unknown; + +#if HAVE_STRUCT_DIRENT_D_TYPE + switch (next->d_type) + { + case DT_BLK: type = blockdev; break; + case DT_CHR: type = chardev; break; + case DT_DIR: type = directory; break; + case DT_FIFO: type = fifo; break; + case DT_LNK: type = symbolic_link; break; + case DT_REG: type = normal; break; + case DT_SOCK: type = sock; break; +# ifdef DT_WHT + case DT_WHT: type = whiteout; break; +# endif + } +#endif + total_blocks += gobble_file (next->d_name, type, + RELIABLE_D_INO (next), + false, name); + + /* In this narrow case, print out each name right away, so + ls uses constant memory while processing the entries of + this directory. Useful when there are many (millions) + of entries in a directory. */ + if (format == one_per_line && sort_type == sort_none + && !print_block_size && !recursive) + { + /* We must call sort_files in spite of + "sort_type == sort_none" for its initialization + of the sorted_file vector. */ + sort_files (); + print_current_files (); + clear_files (); + } + } + } + else if (errno != 0) + { + file_failure (command_line_arg, _("reading directory %s"), name); + if (errno != EOVERFLOW) + break; + } + else + break; + } + + if (closedir (dirp) != 0) + { + file_failure (command_line_arg, _("closing directory %s"), name); + /* Don't return; print whatever we got. */ + } + + /* Sort the directory contents. */ + sort_files (); + + /* If any member files are subdirectories, perhaps they should have their + contents listed rather than being mentioned here as files. */ + + if (recursive) + extract_dirs_from_files (name, command_line_arg); + + if (format == long_format || print_block_size) + { + const char *p; + char buf[LONGEST_HUMAN_READABLE + 1]; + + DIRED_INDENT (); + p = _("total"); + DIRED_FPUTS (p, stdout, strlen (p)); + DIRED_PUTCHAR (' '); + p = human_readable (total_blocks, buf, human_output_opts, + ST_NBLOCKSIZE, output_block_size); + DIRED_FPUTS (p, stdout, strlen (p)); + DIRED_PUTCHAR ('\n'); + } + + if (cwd_n_used) + print_current_files (); +} + +/* Add `pattern' to the list of patterns for which files that match are + not listed. */ + +static void +add_ignore_pattern (const char *pattern) +{ + struct ignore_pattern *ignore; + + ignore = xmalloc (sizeof *ignore); + ignore->pattern = pattern; + /* Add it to the head of the linked list. */ + ignore->next = ignore_patterns; + ignore_patterns = ignore; +} + +/* Return true if one of the PATTERNS matches FILE. */ + +static bool +patterns_match (struct ignore_pattern const *patterns, char const *file) +{ + struct ignore_pattern const *p; + for (p = patterns; p; p = p->next) + if (fnmatch (p->pattern, file, FNM_PERIOD) == 0) + return true; + return false; +} + +/* Return true if FILE should be ignored. */ + +static bool +file_ignored (char const *name) +{ + return ((ignore_mode != IGNORE_MINIMAL + && name[0] == '.' + && (ignore_mode == IGNORE_DEFAULT || ! name[1 + (name[1] == '.')])) + || (ignore_mode == IGNORE_DEFAULT + && patterns_match (hide_patterns, name)) + || patterns_match (ignore_patterns, name)); +} + +/* POSIX requires that a file size be printed without a sign, even + when negative. Assume the typical case where negative sizes are + actually positive values that have wrapped around. */ + +static uintmax_t +unsigned_file_size (off_t size) +{ + return size + (size < 0) * ((uintmax_t) OFF_T_MAX - OFF_T_MIN + 1); +} + +/* Enter and remove entries in the table `cwd_file'. */ + +/* Empty the table of files. */ + +static void +clear_files (void) +{ + size_t i; + + for (i = 0; i < cwd_n_used; i++) + { + struct fileinfo *f = sorted_file[i]; + free (f->name); + free (f->linkname); + if (f->scontext != UNKNOWN_SECURITY_CONTEXT) + freecon (f->scontext); + } + + cwd_n_used = 0; + any_has_acl = false; + inode_number_width = 0; + block_size_width = 0; + nlink_width = 0; + owner_width = 0; + group_width = 0; + author_width = 0; + scontext_width = 0; + major_device_number_width = 0; + minor_device_number_width = 0; + file_size_width = 0; +} + +/* Add a file to the current table of files. + Verify that the file exists, and print an error message if it does not. + Return the number of blocks that the file occupies. */ + +static uintmax_t +gobble_file (char const *name, enum filetype type, ino_t inode, + bool command_line_arg, char const *dirname) +{ + uintmax_t blocks = 0; + struct fileinfo *f; + + /* An inode value prior to gobble_file necessarily came from readdir, + which is not used for command line arguments. */ + assert (! command_line_arg || inode == NOT_AN_INODE_NUMBER); + + if (cwd_n_used == cwd_n_alloc) + { + cwd_file = xnrealloc (cwd_file, cwd_n_alloc, 2 * sizeof *cwd_file); + cwd_n_alloc *= 2; + } + + f = &cwd_file[cwd_n_used]; + memset (f, '\0', sizeof *f); + f->stat.st_ino = inode; + f->filetype = type; + + if (command_line_arg + || format_needs_stat + /* When coloring a directory (we may know the type from + direct.d_type), we have to stat it in order to indicate + sticky and/or other-writable attributes. */ + || (type == directory && print_with_color) + /* When dereferencing symlinks, the inode and type must come from + stat, but readdir provides the inode and type of lstat. */ + || ((print_inode || format_needs_type) + && (type == symbolic_link || type == unknown) + && (dereference == DEREF_ALWAYS + || (command_line_arg && dereference != DEREF_NEVER) + || color_symlink_as_referent || check_symlink_color)) + /* Command line dereferences are already taken care of by the above + assertion that the inode number is not yet known. */ + || (print_inode && inode == NOT_AN_INODE_NUMBER) + || (format_needs_type + && (type == unknown || command_line_arg + /* --indicator-style=classify (aka -F) + requires that we stat each regular file + to see if it's executable. */ + || (type == normal && (indicator_style == classify + /* This is so that --color ends up + highlighting files with the executable + bit set even when options like -F are + not specified. */ + || (print_with_color + && is_colored (C_EXEC)) + ))))) + + { + /* Absolute name of this file. */ + char *absolute_name; + bool do_deref; + int err; + + if (name[0] == '/' || dirname[0] == 0) + absolute_name = (char *) name; + else + { + absolute_name = alloca (strlen (name) + strlen (dirname) + 2); + attach (absolute_name, dirname, name); + } + + switch (dereference) + { + case DEREF_ALWAYS: + err = stat (absolute_name, &f->stat); + do_deref = true; + break; + + case DEREF_COMMAND_LINE_ARGUMENTS: + case DEREF_COMMAND_LINE_SYMLINK_TO_DIR: + if (command_line_arg) + { + bool need_lstat; + err = stat (absolute_name, &f->stat); + do_deref = true; + + if (dereference == DEREF_COMMAND_LINE_ARGUMENTS) + break; + + need_lstat = (err < 0 + ? errno == ENOENT + : ! S_ISDIR (f->stat.st_mode)); + if (!need_lstat) + break; + + /* stat failed because of ENOENT, maybe indicating a dangling + symlink. Or stat succeeded, ABSOLUTE_NAME does not refer to a + directory, and --dereference-command-line-symlink-to-dir is + in effect. Fall through so that we call lstat instead. */ + } + + default: /* DEREF_NEVER */ + err = lstat (absolute_name, &f->stat); + do_deref = false; + break; + } + + if (err != 0) + { + /* Failure to stat a command line argument leads to + an exit status of 2. For other files, stat failure + provokes an exit status of 1. */ + file_failure (command_line_arg, + _("cannot access %s"), absolute_name); + if (command_line_arg) + return 0; + + f->name = xstrdup (name); + cwd_n_used++; + + return 0; + } + + f->stat_ok = true; + + if (format == long_format || print_scontext) + { + bool have_selinux = false; + bool have_acl = false; + int attr_len = (do_deref + ? getfilecon (absolute_name, &f->scontext) + : lgetfilecon (absolute_name, &f->scontext)); + err = (attr_len < 0); + + /* Contrary to its documented API, getfilecon may return 0, + yet set f->scontext to NULL (on at least Debian's libselinux1 + 2.0.15-2+b1), so work around that bug. + FIXME: remove this work-around in 2011, or whenever affected + versions of libselinux are long gone. */ + if (attr_len == 0) + { + err = 0; + f->scontext = xstrdup ("unlabeled"); + } + + if (err == 0) + have_selinux = ! STREQ ("unlabeled", f->scontext); + else + { + f->scontext = UNKNOWN_SECURITY_CONTEXT; + + /* When requesting security context information, don't make + ls fail just because the file (even a command line argument) + isn't on the right type of file system. I.e., a getfilecon + failure isn't in the same class as a stat failure. */ + if (errno == ENOTSUP || errno == EOPNOTSUPP || errno == ENODATA) + err = 0; + } + + if (err == 0 && format == long_format) + { + int n = file_has_acl (absolute_name, &f->stat); + err = (n < 0); + have_acl = (0 < n); + } + + f->acl_type = (!have_selinux && !have_acl + ? ACL_T_NONE + : (have_selinux && !have_acl + ? ACL_T_SELINUX_ONLY + : ACL_T_YES)); + any_has_acl |= f->acl_type != ACL_T_NONE; + + if (err) + error (0, errno, "%s", quotearg_colon (absolute_name)); + } + + if (S_ISLNK (f->stat.st_mode) + && (format == long_format || check_symlink_color)) + { + char *linkname; + struct stat linkstats; + + get_link_name (absolute_name, f, command_line_arg); + linkname = make_link_name (absolute_name, f->linkname); + + /* Avoid following symbolic links when possible, ie, when + they won't be traced and when no indicator is needed. */ + if (linkname + && (file_type <= indicator_style || check_symlink_color) + && stat (linkname, &linkstats) == 0) + { + f->linkok = true; + + /* Symbolic links to directories that are mentioned on the + command line are automatically traced if not being + listed as files. */ + if (!command_line_arg || format == long_format + || !S_ISDIR (linkstats.st_mode)) + { + /* Get the linked-to file's mode for the filetype indicator + in long listings. */ + f->linkmode = linkstats.st_mode; + } + } + free (linkname); + } + + /* When not distinguishing types of symlinks, pretend we know that + it is stat'able, so that it will be colored as a regular symlink, + and not as an orphan. */ + if (S_ISLNK (f->stat.st_mode) && !check_symlink_color) + f->linkok = true; + + if (S_ISLNK (f->stat.st_mode)) + f->filetype = symbolic_link; + else if (S_ISDIR (f->stat.st_mode)) + { + if (command_line_arg && !immediate_dirs) + f->filetype = arg_directory; + else + f->filetype = directory; + } + else + f->filetype = normal; + + blocks = ST_NBLOCKS (f->stat); + if (format == long_format || print_block_size) + { + char buf[LONGEST_HUMAN_READABLE + 1]; + int len = mbswidth (human_readable (blocks, buf, human_output_opts, + ST_NBLOCKSIZE, output_block_size), + 0); + if (block_size_width < len) + block_size_width = len; + } + + if (format == long_format) + { + if (print_owner) + { + int len = format_user_width (f->stat.st_uid); + if (owner_width < len) + owner_width = len; + } + + if (print_group) + { + int len = format_group_width (f->stat.st_gid); + if (group_width < len) + group_width = len; + } + + if (print_author) + { + int len = format_user_width (f->stat.st_author); + if (author_width < len) + author_width = len; + } + } + + if (print_scontext) + { + int len = strlen (f->scontext); + if (scontext_width < len) + scontext_width = len; + } + + if (format == long_format) + { + char b[INT_BUFSIZE_BOUND (uintmax_t)]; + int b_len = strlen (umaxtostr (f->stat.st_nlink, b)); + if (nlink_width < b_len) + nlink_width = b_len; + + if (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode)) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + int len = strlen (umaxtostr (major (f->stat.st_rdev), buf)); + if (major_device_number_width < len) + major_device_number_width = len; + len = strlen (umaxtostr (minor (f->stat.st_rdev), buf)); + if (minor_device_number_width < len) + minor_device_number_width = len; + len = major_device_number_width + 2 + minor_device_number_width; + if (file_size_width < len) + file_size_width = len; + } + else + { + char buf[LONGEST_HUMAN_READABLE + 1]; + uintmax_t size = unsigned_file_size (f->stat.st_size); + int len = mbswidth (human_readable (size, buf, human_output_opts, + 1, file_output_block_size), + 0); + if (file_size_width < len) + file_size_width = len; + } + } + } + + if (print_inode) + { + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + int len = strlen (umaxtostr (f->stat.st_ino, buf)); + if (inode_number_width < len) + inode_number_width = len; + } + + f->name = xstrdup (name); + cwd_n_used++; + + return blocks; +} + +/* Return true if F refers to a directory. */ +static bool +is_directory (const struct fileinfo *f) +{ + return f->filetype == directory || f->filetype == arg_directory; +} + +/* Put the name of the file that FILENAME is a symbolic link to + into the LINKNAME field of `f'. COMMAND_LINE_ARG indicates whether + FILENAME is a command-line argument. */ + +static void +get_link_name (char const *filename, struct fileinfo *f, bool command_line_arg) +{ + f->linkname = areadlink_with_size (filename, f->stat.st_size); + if (f->linkname == NULL) + file_failure (command_line_arg, _("cannot read symbolic link %s"), + filename); +} + +/* If `linkname' is a relative name and `name' contains one or more + leading directories, return `linkname' with those directories + prepended; otherwise, return a copy of `linkname'. + If `linkname' is zero, return zero. */ + +static char * +make_link_name (char const *name, char const *linkname) +{ + char *linkbuf; + size_t bufsiz; + + if (!linkname) + return NULL; + + if (*linkname == '/') + return xstrdup (linkname); + + /* The link is to a relative name. Prepend any leading directory + in `name' to the link name. */ + linkbuf = strrchr (name, '/'); + if (linkbuf == 0) + return xstrdup (linkname); + + bufsiz = linkbuf - name + 1; + linkbuf = xmalloc (bufsiz + strlen (linkname) + 1); + strncpy (linkbuf, name, bufsiz); + strcpy (linkbuf + bufsiz, linkname); + return linkbuf; +} + +/* Return true if the last component of NAME is `.' or `..' + This is so we don't try to recurse on `././././. ...' */ + +static bool +basename_is_dot_or_dotdot (const char *name) +{ + char const *base = last_component (name); + return dot_or_dotdot (base); +} + +/* Remove any entries from CWD_FILE that are for directories, + and queue them to be listed as directories instead. + DIRNAME is the prefix to prepend to each dirname + to make it correct relative to ls's working dir; + if it is null, no prefix is needed and "." and ".." should not be ignored. + If COMMAND_LINE_ARG is true, this directory was mentioned at the top level, + This is desirable when processing directories recursively. */ + +static void +extract_dirs_from_files (char const *dirname, bool command_line_arg) +{ + size_t i; + size_t j; + bool ignore_dot_and_dot_dot = (dirname != NULL); + + if (dirname && LOOP_DETECT) + { + /* Insert a marker entry first. When we dequeue this marker entry, + we'll know that DIRNAME has been processed and may be removed + from the set of active directories. */ + queue_directory (NULL, dirname, false); + } + + /* Queue the directories last one first, because queueing reverses the + order. */ + for (i = cwd_n_used; i-- != 0; ) + { + struct fileinfo *f = sorted_file[i]; + + if (is_directory (f) + && (! ignore_dot_and_dot_dot + || ! basename_is_dot_or_dotdot (f->name))) + { + if (!dirname || f->name[0] == '/') + queue_directory (f->name, f->linkname, command_line_arg); + else + { + char *name = file_name_concat (dirname, f->name, NULL); + queue_directory (name, f->linkname, command_line_arg); + free (name); + } + if (f->filetype == arg_directory) + free (f->name); + } + } + + /* Now delete the directories from the table, compacting all the remaining + entries. */ + + for (i = 0, j = 0; i < cwd_n_used; i++) + { + struct fileinfo *f = sorted_file[i]; + sorted_file[j] = f; + j += (f->filetype != arg_directory); + } + cwd_n_used = j; +} + +/* Use strcoll to compare strings in this locale. If an error occurs, + report an error and longjmp to failed_strcoll. */ + +static jmp_buf failed_strcoll; + +static int +xstrcoll (char const *a, char const *b) +{ + int diff; + errno = 0; + diff = strcoll (a, b); + if (errno) + { + error (0, errno, _("cannot compare file names %s and %s"), + quote_n (0, a), quote_n (1, b)); + set_exit_status (false); + longjmp (failed_strcoll, 1); + } + return diff; +} + +/* Comparison routines for sorting the files. */ + +typedef void const *V; +typedef int (*qsortFunc)(V a, V b); + +/* Used below in DEFINE_SORT_FUNCTIONS for _df_ sort function variants. + The do { ... } while(0) makes it possible to use the macro more like + a statement, without violating C89 rules: */ +#define DIRFIRST_CHECK(a, b) \ + do \ + { \ + bool a_is_dir = is_directory ((struct fileinfo const *) a); \ + bool b_is_dir = is_directory ((struct fileinfo const *) b); \ + if (a_is_dir && !b_is_dir) \ + return -1; /* a goes before b */ \ + if (!a_is_dir && b_is_dir) \ + return 1; /* b goes before a */ \ + } \ + while (0) + +/* Define the 8 different sort function variants required for each sortkey. + KEY_NAME is a token describing the sort key, e.g., ctime, atime, size. + KEY_CMP_FUNC is a function to compare records based on that key, e.g., + ctime_cmp, atime_cmp, size_cmp. Append KEY_NAME to the string, + '[rev_][x]str{cmp|coll}[_df]_', to create each function name. */ +#define DEFINE_SORT_FUNCTIONS(key_name, key_cmp_func) \ + /* direct, non-dirfirst versions */ \ + static int xstrcoll_##key_name (V a, V b) \ + { return key_cmp_func (a, b, xstrcoll); } \ + static int strcmp_##key_name (V a, V b) \ + { return key_cmp_func (a, b, strcmp); } \ + \ + /* reverse, non-dirfirst versions */ \ + static int rev_xstrcoll_##key_name (V a, V b) \ + { return key_cmp_func (b, a, xstrcoll); } \ + static int rev_strcmp_##key_name (V a, V b) \ + { return key_cmp_func (b, a, strcmp); } \ + \ + /* direct, dirfirst versions */ \ + static int xstrcoll_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (a, b, xstrcoll); } \ + static int strcmp_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (a, b, strcmp); } \ + \ + /* reverse, dirfirst versions */ \ + static int rev_xstrcoll_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (b, a, xstrcoll); } \ + static int rev_strcmp_df_##key_name (V a, V b) \ + { DIRFIRST_CHECK (a, b); return key_cmp_func (b, a, strcmp); } + +static inline int +cmp_ctime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_ctime (&b->stat), + get_stat_ctime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_mtime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_mtime (&b->stat), + get_stat_mtime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_atime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_atime (&b->stat), + get_stat_atime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_size (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = longdiff (b->stat.st_size, a->stat.st_size); + return diff ? diff : cmp (a->name, b->name); +} + +static inline int +cmp_name (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + return cmp (a->name, b->name); +} + +/* Compare file extensions. Files with no extension are `smallest'. + If extensions are the same, compare by filenames instead. */ + +static inline int +cmp_extension (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + char const *base1 = strrchr (a->name, '.'); + char const *base2 = strrchr (b->name, '.'); + int diff = cmp (base1 ? base1 : "", base2 ? base2 : ""); + return diff ? diff : cmp (a->name, b->name); +} + +DEFINE_SORT_FUNCTIONS (ctime, cmp_ctime) +DEFINE_SORT_FUNCTIONS (mtime, cmp_mtime) +DEFINE_SORT_FUNCTIONS (atime, cmp_atime) +DEFINE_SORT_FUNCTIONS (size, cmp_size) +DEFINE_SORT_FUNCTIONS (name, cmp_name) +DEFINE_SORT_FUNCTIONS (extension, cmp_extension) + +/* Compare file versions. + Unlike all other compare functions above, cmp_version depends only + on filevercmp, which does not fail (even for locale reasons), and does not + need a secondary sort key. See lib/filevercmp.h for function description. + + All the other sort options, in fact, need xstrcoll and strcmp variants, + because they all use a string comparison (either as the primary or secondary + sort key), and xstrcoll has the ability to do a longjmp if strcoll fails for + locale reasons. Last, strverscmp is ALWAYS available in coreutils, + thanks to the gnulib library. */ +static inline int +cmp_version (struct fileinfo const *a, struct fileinfo const *b) +{ + return filevercmp (a->name, b->name); +} + +static int xstrcoll_version (V a, V b) +{ return cmp_version (a, b); } +static int rev_xstrcoll_version (V a, V b) +{ return cmp_version (b, a); } +static int xstrcoll_df_version (V a, V b) +{ DIRFIRST_CHECK (a, b); return cmp_version (a, b); } +static int rev_xstrcoll_df_version (V a, V b) +{ DIRFIRST_CHECK (a, b); return cmp_version (b, a); } + + +/* We have 2^3 different variants for each sortkey function + (for 3 independent sort modes). + The function pointers stored in this array must be dereferenced as: + + sort_variants[sort_key][use_strcmp][reverse][dirs_first] + + Note that the order in which sortkeys are listed in the function pointer + array below is defined by the order of the elements in the time_type and + sort_type enums! */ + +#define LIST_SORTFUNCTION_VARIANTS(key_name) \ + { \ + { \ + { xstrcoll_##key_name, xstrcoll_df_##key_name }, \ + { rev_xstrcoll_##key_name, rev_xstrcoll_df_##key_name }, \ + }, \ + { \ + { strcmp_##key_name, strcmp_df_##key_name }, \ + { rev_strcmp_##key_name, rev_strcmp_df_##key_name }, \ + } \ + } + +static qsortFunc const sort_functions[][2][2][2] = + { + LIST_SORTFUNCTION_VARIANTS (name), + LIST_SORTFUNCTION_VARIANTS (extension), + LIST_SORTFUNCTION_VARIANTS (size), + + { + { + { xstrcoll_version, xstrcoll_df_version }, + { rev_xstrcoll_version, rev_xstrcoll_df_version }, + }, + + /* We use NULL for the strcmp variants of version comparison + since as explained in cmp_version definition, version comparison + does not rely on xstrcoll, so it will never longjmp, and never + need to try the strcmp fallback. */ + { + { NULL, NULL }, + { NULL, NULL }, + } + }, + + /* last are time sort functions */ + LIST_SORTFUNCTION_VARIANTS (mtime), + LIST_SORTFUNCTION_VARIANTS (ctime), + LIST_SORTFUNCTION_VARIANTS (atime) + }; + +/* The number of sortkeys is calculated as + the number of elements in the sort_type enum (i.e. sort_numtypes) + + the number of elements in the time_type enum (i.e. time_numtypes) - 1 + This is because when sort_type==sort_time, we have up to + time_numtypes possible sortkeys. + + This line verifies at compile-time that the array of sort functions has been + initialized for all possible sortkeys. */ +verify (ARRAY_CARDINALITY (sort_functions) + == sort_numtypes + time_numtypes - 1 ); + +/* Set up SORTED_FILE to point to the in-use entries in CWD_FILE, in order. */ + +static void +initialize_ordering_vector (void) +{ + size_t i; + for (i = 0; i < cwd_n_used; i++) + sorted_file[i] = &cwd_file[i]; +} + +/* Sort the files now in the table. */ + +static void +sort_files (void) +{ + bool use_strcmp; + + if (sorted_file_alloc < cwd_n_used + cwd_n_used / 2) + { + free (sorted_file); + sorted_file = xnmalloc (cwd_n_used, 3 * sizeof *sorted_file); + sorted_file_alloc = 3 * cwd_n_used; + } + + initialize_ordering_vector (); + + if (sort_type == sort_none) + return; + + /* Try strcoll. If it fails, fall back on strcmp. We can't safely + ignore strcoll failures, as a failing strcoll might be a + comparison function that is not a total order, and if we ignored + the failure this might cause qsort to dump core. */ + + if (! setjmp (failed_strcoll)) + use_strcmp = false; /* strcoll() succeeded */ + else + { + use_strcmp = true; + assert (sort_type != sort_version); + initialize_ordering_vector (); + } + + /* When sort_type == sort_time, use time_type as subindex. */ + mpsort ((void const **) sorted_file, cwd_n_used, + sort_functions[sort_type + (sort_type == sort_time ? time_type : 0)] + [use_strcmp][sort_reverse] + [directories_first]); +} + +/* List all the files now in the table. */ + +static void +print_current_files (void) +{ + size_t i; + + switch (format) + { + case one_per_line: + for (i = 0; i < cwd_n_used; i++) + { + print_file_name_and_frills (sorted_file[i], 0); + putchar ('\n'); + } + break; + + case many_per_line: + print_many_per_line (); + break; + + case horizontal: + print_horizontal (); + break; + + case with_commas: + print_with_commas (); + break; + + case long_format: + for (i = 0; i < cwd_n_used; i++) + { + print_long_format (sorted_file[i]); + DIRED_PUTCHAR ('\n'); + } + break; + } +} + +/* Replace the first %b with precomputed aligned month names. + Note on glibc-2.7 at least, this speeds up the whole `ls -lU` + process by around 17%, compared to letting strftime() handle the %b. */ + +static size_t +align_nstrftime (char *buf, size_t size, char const *fmt, struct tm const *tm, + int __utc, int __ns) +{ + const char *nfmt = fmt; + /* In the unlikely event that rpl_fmt below is not large enough, + the replacement is not done. A malloc here slows ls down by 2% */ + char rpl_fmt[sizeof (abmon[0]) + 100]; + const char *pb; + if (required_mon_width && (pb = strstr (fmt, "%b"))) + { + if (strlen (fmt) < (sizeof (rpl_fmt) - sizeof (abmon[0]) + 2)) + { + char *pfmt = rpl_fmt; + nfmt = rpl_fmt; + + pfmt = mempcpy (pfmt, fmt, pb - fmt); + pfmt = stpcpy (pfmt, abmon[tm->tm_mon]); + strcpy (pfmt, pb + 2); + } + } + size_t ret = nstrftime (buf, size, nfmt, tm, __utc, __ns); + return ret; +} + +/* Return the expected number of columns in a long-format time stamp, + or zero if it cannot be calculated. */ + +static int +long_time_expected_width (void) +{ + static int width = -1; + + if (width < 0) + { + time_t epoch = 0; + struct tm const *tm = localtime (&epoch); + char buf[TIME_STAMP_LEN_MAXIMUM + 1]; + + /* In case you're wondering if localtime can fail with an input time_t + value of 0, let's just say it's very unlikely, but not inconceivable. + The TZ environment variable would have to specify a time zone that + is 2**31-1900 years or more ahead of UTC. This could happen only on + a 64-bit system that blindly accepts e.g., TZ=UTC+20000000000000. + However, this is not possible with Solaris 10 or glibc-2.3.5, since + their implementations limit the offset to 167:59 and 24:00, resp. */ + if (tm) + { + size_t len = + align_nstrftime (buf, sizeof buf, long_time_format[0], tm, 0, 0); + if (len != 0) + width = mbsnwidth (buf, len, 0); + } + + if (width < 0) + width = 0; + } + + return width; +} + +/* Print the user or group name NAME, with numeric id ID, using a + print width of WIDTH columns. */ + +static void +format_user_or_group (char const *name, unsigned long int id, int width) +{ + size_t len; + + if (name) + { + int width_gap = width - mbswidth (name, 0); + int pad = MAX (0, width_gap); + fputs (name, stdout); + len = strlen (name) + pad; + + do + putchar (' '); + while (pad--); + } + else + { + printf ("%*lu ", width, id); + len = width; + } + + dired_pos += len + 1; +} + +/* Print the name or id of the user with id U, using a print width of + WIDTH. */ + +static void +format_user (uid_t u, int width, bool stat_ok) +{ + format_user_or_group (! stat_ok ? "?" : + (numeric_ids ? NULL : getuser (u)), u, width); +} + +/* Likewise, for groups. */ + +static void +format_group (gid_t g, int width, bool stat_ok) +{ + format_user_or_group (! stat_ok ? "?" : + (numeric_ids ? NULL : getgroup (g)), g, width); +} + +/* Return the number of columns that format_user_or_group will print. */ + +static int +format_user_or_group_width (char const *name, unsigned long int id) +{ + if (name) + { + int len = mbswidth (name, 0); + return MAX (0, len); + } + else + { + char buf[INT_BUFSIZE_BOUND (unsigned long int)]; + sprintf (buf, "%lu", id); + return strlen (buf); + } +} + +/* Return the number of columns that format_user will print. */ + +static int +format_user_width (uid_t u) +{ + return format_user_or_group_width (numeric_ids ? NULL : getuser (u), u); +} + +/* Likewise, for groups. */ + +static int +format_group_width (gid_t g) +{ + return format_user_or_group_width (numeric_ids ? NULL : getgroup (g), g); +} + +/* Return a pointer to a formatted version of F->stat.st_ino, + possibly using buffer, BUF, of length BUFLEN, which must be at least + INT_BUFSIZE_BOUND (uintmax_t) bytes. */ +static char * +format_inode (char *buf, size_t buflen, const struct fileinfo *f) +{ + assert (INT_BUFSIZE_BOUND (uintmax_t) <= buflen); + return (f->stat_ok && f->stat.st_ino != NOT_AN_INODE_NUMBER + ? umaxtostr (f->stat.st_ino, buf) + : (char *) "?"); +} + +/* Print information about F in long format. */ +static void +print_long_format (const struct fileinfo *f) +{ + char modebuf[12]; + char buf + [LONGEST_HUMAN_READABLE + 1 /* inode */ + + LONGEST_HUMAN_READABLE + 1 /* size in blocks */ + + sizeof (modebuf) - 1 + 1 /* mode string */ + + INT_BUFSIZE_BOUND (uintmax_t) /* st_nlink */ + + LONGEST_HUMAN_READABLE + 2 /* major device number */ + + LONGEST_HUMAN_READABLE + 1 /* minor device number */ + + TIME_STAMP_LEN_MAXIMUM + 1 /* max length of time/date */ + ]; + size_t s; + char *p; + struct timespec when_timespec; + struct tm *when_local; + + /* Compute the mode string, except remove the trailing space if no + file in this directory has an ACL or SELinux security context. */ + if (f->stat_ok) + filemodestring (&f->stat, modebuf); + else + { + modebuf[0] = filetype_letter[f->filetype]; + memset (modebuf + 1, '?', 10); + modebuf[11] = '\0'; + } + if (! any_has_acl) + modebuf[10] = '\0'; + else if (f->acl_type == ACL_T_SELINUX_ONLY) + modebuf[10] = '.'; + else if (f->acl_type == ACL_T_YES) + modebuf[10] = '+'; + + switch (time_type) + { + case time_ctime: + when_timespec = get_stat_ctime (&f->stat); + break; + case time_mtime: + when_timespec = get_stat_mtime (&f->stat); + break; + case time_atime: + when_timespec = get_stat_atime (&f->stat); + break; + default: + abort (); + } + + p = buf; + + if (print_inode) + { + char hbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + sprintf (p, "%*s ", inode_number_width, + format_inode (hbuf, sizeof hbuf, f)); + /* Increment by strlen (p) here, rather than by inode_number_width + 1. + The latter is wrong when inode_number_width is zero. */ + p += strlen (p); + } + + if (print_block_size) + { + char hbuf[LONGEST_HUMAN_READABLE + 1]; + char const *blocks = + (! f->stat_ok + ? "?" + : human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts, + ST_NBLOCKSIZE, output_block_size)); + int pad; + for (pad = block_size_width - mbswidth (blocks, 0); 0 < pad; pad--) + *p++ = ' '; + while ((*p++ = *blocks++)) + continue; + p[-1] = ' '; + } + + /* The last byte of the mode string is the POSIX + "optional alternate access method flag". */ + { + char hbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + sprintf (p, "%s %*s ", modebuf, nlink_width, + ! f->stat_ok ? "?" : umaxtostr (f->stat.st_nlink, hbuf)); + } + /* Increment by strlen (p) here, rather than by, e.g., + sizeof modebuf - 2 + any_has_acl + 1 + nlink_width + 1. + The latter is wrong when nlink_width is zero. */ + p += strlen (p); + + DIRED_INDENT (); + + if (print_owner || print_group || print_author || print_scontext) + { + DIRED_FPUTS (buf, stdout, p - buf); + + if (print_owner) + format_user (f->stat.st_uid, owner_width, f->stat_ok); + + if (print_group) + format_group (f->stat.st_gid, group_width, f->stat_ok); + + if (print_author) + format_user (f->stat.st_author, author_width, f->stat_ok); + + if (print_scontext) + format_user_or_group (f->scontext, 0, scontext_width); + + p = buf; + } + + if (f->stat_ok + && (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode))) + { + char majorbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + char minorbuf[INT_BUFSIZE_BOUND (uintmax_t)]; + int blanks_width = (file_size_width + - (major_device_number_width + 2 + + minor_device_number_width)); + sprintf (p, "%*s, %*s ", + major_device_number_width + MAX (0, blanks_width), + umaxtostr (major (f->stat.st_rdev), majorbuf), + minor_device_number_width, + umaxtostr (minor (f->stat.st_rdev), minorbuf)); + p += file_size_width + 1; + } + else + { + char hbuf[LONGEST_HUMAN_READABLE + 1]; + char const *size = + (! f->stat_ok + ? "?" + : human_readable (unsigned_file_size (f->stat.st_size), + hbuf, human_output_opts, 1, file_output_block_size)); + int pad; + for (pad = file_size_width - mbswidth (size, 0); 0 < pad; pad--) + *p++ = ' '; + while ((*p++ = *size++)) + continue; + p[-1] = ' '; + } + + when_local = localtime (&when_timespec.tv_sec); + s = 0; + *p = '\1'; + + if (f->stat_ok && when_local) + { + struct timespec six_months_ago; + bool recent; + char const *fmt; + + /* If the file appears to be in the future, update the current + time, in case the file happens to have been modified since + the last time we checked the clock. */ + if (timespec_cmp (current_time, when_timespec) < 0) + { + /* Note that gettime may call gettimeofday which, on some non- + compliant systems, clobbers the buffer used for localtime's result. + But it's ok here, because we use a gettimeofday wrapper that + saves and restores the buffer around the gettimeofday call. */ + gettime (¤t_time); + } + + /* Consider a time to be recent if it is within the past six + months. A Gregorian year has 365.2425 * 24 * 60 * 60 == + 31556952 seconds on the average. Write this value as an + integer constant to avoid floating point hassles. */ + six_months_ago.tv_sec = current_time.tv_sec - 31556952 / 2; + six_months_ago.tv_nsec = current_time.tv_nsec; + + recent = (timespec_cmp (six_months_ago, when_timespec) < 0 + && (timespec_cmp (when_timespec, current_time) < 0)); + fmt = long_time_format[recent]; + + /* We assume here that all time zones are offset from UTC by a + whole number of seconds. */ + s = align_nstrftime (p, TIME_STAMP_LEN_MAXIMUM + 1, fmt, + when_local, 0, when_timespec.tv_nsec); + } + + if (s || !*p) + { + p += s; + *p++ = ' '; + + /* NUL-terminate the string -- fputs (via DIRED_FPUTS) requires it. */ + *p = '\0'; + } + else + { + /* The time cannot be converted using the desired format, so + print it as a huge integer number of seconds. */ + char hbuf[INT_BUFSIZE_BOUND (intmax_t)]; + sprintf (p, "%*s ", long_time_expected_width (), + (! f->stat_ok + ? "?" + : timetostr (when_timespec.tv_sec, hbuf))); + /* FIXME: (maybe) We discarded when_timespec.tv_nsec. */ + p += strlen (p); + } + + DIRED_FPUTS (buf, stdout, p - buf); + size_t w = print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok, + f->stat_ok, f->filetype, &dired_obstack, + f->stat.st_nlink, p - buf); + + if (f->filetype == symbolic_link) + { + if (f->linkname) + { + DIRED_FPUTS_LITERAL (" -> ", stdout); + print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1, + f->stat_ok, f->filetype, NULL, + f->stat.st_nlink, (p - buf) + w + 4); + if (indicator_style != none) + print_type_indicator (true, f->linkmode, unknown); + } + } + else if (indicator_style != none) + print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); +} + +/* Output to OUT a quoted representation of the file name NAME, + using OPTIONS to control quoting. Produce no output if OUT is NULL. + Store the number of screen columns occupied by NAME's quoted + representation into WIDTH, if non-NULL. Return the number of bytes + produced. */ + +static size_t +quote_name (FILE *out, const char *name, struct quoting_options const *options, + size_t *width) +{ + char smallbuf[BUFSIZ]; + size_t len = quotearg_buffer (smallbuf, sizeof smallbuf, name, -1, options); + char *buf; + size_t displayed_width IF_LINT (= 0); + + if (len < sizeof smallbuf) + buf = smallbuf; + else + { + buf = alloca (len + 1); + quotearg_buffer (buf, len + 1, name, -1, options); + } + + if (qmark_funny_chars) + { + if (MB_CUR_MAX > 1) + { + char const *p = buf; + char const *plimit = buf + len; + char *q = buf; + displayed_width = 0; + + while (p < plimit) + switch (*p) + { + case ' ': case '!': case '"': case '#': case '%': + case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case '-': case '.': case '/': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case ':': case ';': case '<': case '=': case '>': + case '?': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + case '[': case '\\': case ']': case '^': case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': + case 'z': case '{': case '|': case '}': case '~': + /* These characters are printable ASCII characters. */ + *q++ = *p++; + displayed_width += 1; + break; + default: + /* If we have a multibyte sequence, copy it until we + reach its end, replacing each non-printable multibyte + character with a single question mark. */ + { + DECLARE_ZEROED_AGGREGATE (mbstate_t, mbstate); + do + { + wchar_t wc; + size_t bytes; + int w; + + bytes = mbrtowc (&wc, p, plimit - p, &mbstate); + + if (bytes == (size_t) -1) + { + /* An invalid multibyte sequence was + encountered. Skip one input byte, and + put a question mark. */ + p++; + *q++ = '?'; + displayed_width += 1; + break; + } + + if (bytes == (size_t) -2) + { + /* An incomplete multibyte character + at the end. Replace it entirely with + a question mark. */ + p = plimit; + *q++ = '?'; + displayed_width += 1; + break; + } + + if (bytes == 0) + /* A null wide character was encountered. */ + bytes = 1; + + w = wcwidth (wc); + if (w >= 0) + { + /* A printable multibyte character. + Keep it. */ + for (; bytes > 0; --bytes) + *q++ = *p++; + displayed_width += w; + } + else + { + /* An unprintable multibyte character. + Replace it entirely with a question + mark. */ + p += bytes; + *q++ = '?'; + displayed_width += 1; + } + } + while (! mbsinit (&mbstate)); + } + break; + } + + /* The buffer may have shrunk. */ + len = q - buf; + } + else + { + char *p = buf; + char const *plimit = buf + len; + + while (p < plimit) + { + if (! isprint (to_uchar (*p))) + *p = '?'; + p++; + } + displayed_width = len; + } + } + else if (width != NULL) + { + if (MB_CUR_MAX > 1) + displayed_width = mbsnwidth (buf, len, 0); + else + { + char const *p = buf; + char const *plimit = buf + len; + + displayed_width = 0; + while (p < plimit) + { + if (isprint (to_uchar (*p))) + displayed_width++; + p++; + } + } + } + + if (out != NULL) + fwrite (buf, 1, len, out); + if (width != NULL) + *width = displayed_width; + return len; +} + +static size_t +print_name_with_quoting (const char *p, mode_t mode, int linkok, + bool stat_ok, enum filetype type, + struct obstack *stack, nlink_t nlink, + size_t start_col) +{ + bool used_color_this_time + = (print_with_color + && print_color_indicator (p, mode, linkok, stat_ok, type, nlink)); + + if (stack) + PUSH_CURRENT_DIRED_POS (stack); + + size_t width = quote_name (stdout, p, filename_quoting_options, NULL); + dired_pos += width; + + if (stack) + PUSH_CURRENT_DIRED_POS (stack); + + if (used_color_this_time) + { + process_signals (); + prep_non_filename_text (); + if (start_col / line_length != (start_col + width - 1) / line_length) + put_indicator (&color_indicator[C_CLR_TO_EOL]); + } + + return width; +} + +static void +prep_non_filename_text (void) +{ + if (color_indicator[C_END].string != NULL) + put_indicator (&color_indicator[C_END]); + else + { + put_indicator (&color_indicator[C_LEFT]); + put_indicator (&color_indicator[C_RESET]); + put_indicator (&color_indicator[C_RIGHT]); + } +} + +/* Print the file name of `f' with appropriate quoting. + Also print file size, inode number, and filetype indicator character, + as requested by switches. */ + +static size_t +print_file_name_and_frills (const struct fileinfo *f, size_t start_col) +{ + char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))]; + + if (print_inode) + printf ("%*s ", format == with_commas ? 0 : inode_number_width, + format_inode (buf, sizeof buf, f)); + + if (print_block_size) + printf ("%*s ", format == with_commas ? 0 : block_size_width, + ! f->stat_ok ? "?" + : human_readable (ST_NBLOCKS (f->stat), buf, human_output_opts, + ST_NBLOCKSIZE, output_block_size)); + + if (print_scontext) + printf ("%*s ", format == with_commas ? 0 : scontext_width, f->scontext); + + size_t width = print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), + f->linkok, f->stat_ok, f->filetype, + NULL, f->stat.st_nlink, start_col); + + if (indicator_style != none) + width += print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); + + return width; +} + +/* Given these arguments describing a file, return the single-byte + type indicator, or 0. */ +static char +get_type_indicator (bool stat_ok, mode_t mode, enum filetype type) +{ + char c; + + if (stat_ok ? S_ISREG (mode) : type == normal) + { + if (stat_ok && indicator_style == classify && (mode & S_IXUGO)) + c = '*'; + else + c = 0; + } + else + { + if (stat_ok ? S_ISDIR (mode) : type == directory || type == arg_directory) + c = '/'; + else if (indicator_style == slash) + c = 0; + else if (stat_ok ? S_ISLNK (mode) : type == symbolic_link) + c = '@'; + else if (stat_ok ? S_ISFIFO (mode) : type == fifo) + c = '|'; + else if (stat_ok ? S_ISSOCK (mode) : type == sock) + c = '='; + else if (stat_ok && S_ISDOOR (mode)) + c = '>'; + else + c = 0; + } + return c; +} + +static bool +print_type_indicator (bool stat_ok, mode_t mode, enum filetype type) +{ + char c = get_type_indicator (stat_ok, mode, type); + if (c) + DIRED_PUTCHAR (c); + return !!c; +} + +#ifdef HAVE_CAP +/* Return true if NAME has a capability (see linux/capability.h) */ +static bool +has_capability (char const *name) +{ + char *result; + bool has_cap; + + cap_t cap_d = cap_get_file (name); + if (cap_d == NULL) + return false; + + result = cap_to_text (cap_d, NULL); + cap_free (cap_d); + if (!result) + return false; + + /* check if human-readable capability string is empty */ + has_cap = !!*result; + + cap_free (result); + return has_cap; +} +#else +static bool +has_capability (char const *name ATTRIBUTE_UNUSED) +{ + return false; +} +#endif + +/* Returns whether any color sequence was printed. */ +static bool +print_color_indicator (const char *name, mode_t mode, int linkok, + bool stat_ok, enum filetype filetype, + nlink_t nlink) +{ + enum indicator_no type; + struct color_ext_type *ext; /* Color extension */ + size_t len; /* Length of name */ + + /* Is this a nonexistent file? If so, linkok == -1. */ + + if (linkok == -1 && color_indicator[C_MISSING].string != NULL) + type = C_MISSING; + else if (! stat_ok) + { + static enum indicator_no filetype_indicator[] = FILETYPE_INDICATORS; + type = filetype_indicator[filetype]; + } + else + { + if (S_ISREG (mode)) + { + type = C_FILE; + + if ((mode & S_ISUID) != 0 && is_colored (C_SETUID)) + type = C_SETUID; + else if ((mode & S_ISGID) != 0 && is_colored (C_SETGID)) + type = C_SETGID; + /* has_capability() called second for performance. */ + else if (is_colored (C_CAP) && has_capability (name)) + type = C_CAP; + else if ((mode & S_IXUGO) != 0 && is_colored (C_EXEC)) + type = C_EXEC; + else if ((1 < nlink) && is_colored (C_MULTIHARDLINK)) + type = C_MULTIHARDLINK; + } + else if (S_ISDIR (mode)) + { + type = C_DIR; + + if ((mode & S_ISVTX) && (mode & S_IWOTH) + && is_colored (C_STICKY_OTHER_WRITABLE)) + type = C_STICKY_OTHER_WRITABLE; + else if ((mode & S_IWOTH) != 0 && is_colored (C_OTHER_WRITABLE)) + type = C_OTHER_WRITABLE; + else if ((mode & S_ISVTX) != 0 && is_colored (C_STICKY)) + type = C_STICKY; + } + else if (S_ISLNK (mode)) + type = ((!linkok && color_indicator[C_ORPHAN].string) + ? C_ORPHAN : C_LINK); + else if (S_ISFIFO (mode)) + type = C_FIFO; + else if (S_ISSOCK (mode)) + type = C_SOCK; + else if (S_ISBLK (mode)) + type = C_BLK; + else if (S_ISCHR (mode)) + type = C_CHR; + else if (S_ISDOOR (mode)) + type = C_DOOR; + else + { + /* Classify a file of some other type as C_ORPHAN. */ + type = C_ORPHAN; + } + } + + /* Check the file's suffix only if still classified as C_FILE. */ + ext = NULL; + if (type == C_FILE) + { + /* Test if NAME has a recognized suffix. */ + + len = strlen (name); + name += len; /* Pointer to final \0. */ + for (ext = color_ext_list; ext != NULL; ext = ext->next) + { + if (ext->ext.len <= len + && strncmp (name - ext->ext.len, ext->ext.string, + ext->ext.len) == 0) + break; + } + } + + { + const struct bin_str *const s + = ext ? &(ext->seq) : &color_indicator[type]; + if (s->string != NULL) + { + put_indicator (&color_indicator[C_LEFT]); + put_indicator (s); + put_indicator (&color_indicator[C_RIGHT]); + return true; + } + else + return false; + } +} + +/* Output a color indicator (which may contain nulls). */ +static void +put_indicator (const struct bin_str *ind) +{ + if (! used_color) + { + used_color = true; + prep_non_filename_text (); + } + + fwrite (ind->string, ind->len, 1, stdout); +} + +static size_t +length_of_file_name_and_frills (const struct fileinfo *f) +{ + size_t len = 0; + size_t name_width; + char buf[MAX (LONGEST_HUMAN_READABLE + 1, INT_BUFSIZE_BOUND (uintmax_t))]; + + if (print_inode) + len += 1 + (format == with_commas + ? strlen (umaxtostr (f->stat.st_ino, buf)) + : inode_number_width); + + if (print_block_size) + len += 1 + (format == with_commas + ? strlen (! f->stat_ok ? "?" + : human_readable (ST_NBLOCKS (f->stat), buf, + human_output_opts, ST_NBLOCKSIZE, + output_block_size)) + : block_size_width); + + if (print_scontext) + len += 1 + (format == with_commas ? strlen (f->scontext) : scontext_width); + + quote_name (NULL, f->name, filename_quoting_options, &name_width); + len += name_width; + + if (indicator_style != none) + { + char c = get_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype); + len += (c != 0); + } + + return len; +} + +static void +print_many_per_line (void) +{ + size_t row; /* Current row. */ + size_t cols = calculate_columns (true); + struct column_info const *line_fmt = &column_info[cols - 1]; + + /* Calculate the number of rows that will be in each column except possibly + for a short column on the right. */ + size_t rows = cwd_n_used / cols + (cwd_n_used % cols != 0); + + for (row = 0; row < rows; row++) + { + size_t col = 0; + size_t filesno = row; + size_t pos = 0; + + /* Print the next row. */ + while (1) + { + struct fileinfo const *f = sorted_file[filesno]; + size_t name_length = length_of_file_name_and_frills (f); + size_t max_name_length = line_fmt->col_arr[col++]; + print_file_name_and_frills (f, pos); + + filesno += rows; + if (filesno >= cwd_n_used) + break; + + indent (pos + name_length, pos + max_name_length); + pos += max_name_length; + } + putchar ('\n'); + } +} + +static void +print_horizontal (void) +{ + size_t filesno; + size_t pos = 0; + size_t cols = calculate_columns (false); + struct column_info const *line_fmt = &column_info[cols - 1]; + struct fileinfo const *f = sorted_file[0]; + size_t name_length = length_of_file_name_and_frills (f); + size_t max_name_length = line_fmt->col_arr[0]; + + /* Print first entry. */ + print_file_name_and_frills (f, 0); + + /* Now the rest. */ + for (filesno = 1; filesno < cwd_n_used; ++filesno) + { + size_t col = filesno % cols; + + if (col == 0) + { + putchar ('\n'); + pos = 0; + } + else + { + indent (pos + name_length, pos + max_name_length); + pos += max_name_length; + } + + f = sorted_file[filesno]; + print_file_name_and_frills (f, pos); + + name_length = length_of_file_name_and_frills (f); + max_name_length = line_fmt->col_arr[col]; + } + putchar ('\n'); +} + +static void +print_with_commas (void) +{ + size_t filesno; + size_t pos = 0; + + for (filesno = 0; filesno < cwd_n_used; filesno++) + { + struct fileinfo const *f = sorted_file[filesno]; + size_t len = length_of_file_name_and_frills (f); + + if (filesno != 0) + { + char separator; + + if (pos + len + 2 < line_length) + { + pos += 2; + separator = ' '; + } + else + { + pos = 0; + separator = '\n'; + } + + putchar (','); + putchar (separator); + } + + print_file_name_and_frills (f, pos); + pos += len; + } + putchar ('\n'); +} + +/* Assuming cursor is at position FROM, indent up to position TO. + Use a TAB character instead of two or more spaces whenever possible. */ + +static void +indent (size_t from, size_t to) +{ + while (from < to) + { + if (tabsize != 0 && to / tabsize > (from + 1) / tabsize) + { + putchar ('\t'); + from += tabsize - from % tabsize; + } + else + { + putchar (' '); + from++; + } + } +} + +/* Put DIRNAME/NAME into DEST, handling `.' and `/' properly. */ +/* FIXME: maybe remove this function someday. See about using a + non-malloc'ing version of file_name_concat. */ + +static void +attach (char *dest, const char *dirname, const char *name) +{ + const char *dirnamep = dirname; + + /* Copy dirname if it is not ".". */ + if (dirname[0] != '.' || dirname[1] != 0) + { + while (*dirnamep) + *dest++ = *dirnamep++; + /* Add '/' if `dirname' doesn't already end with it. */ + if (dirnamep > dirname && dirnamep[-1] != '/') + *dest++ = '/'; + } + while (*name) + *dest++ = *name++; + *dest = 0; +} + +/* Allocate enough column info suitable for the current number of + files and display columns, and initialize the info to represent the + narrowest possible columns. */ + +static void +init_column_info (void) +{ + size_t i; + size_t max_cols = MIN (max_idx, cwd_n_used); + + /* Currently allocated columns in column_info. */ + static size_t column_info_alloc; + + if (column_info_alloc < max_cols) + { + size_t new_column_info_alloc; + size_t *p; + + if (max_cols < max_idx / 2) + { + /* The number of columns is far less than the display width + allows. Grow the allocation, but only so that it's + double the current requirements. If the display is + extremely wide, this avoids allocating a lot of memory + that is never needed. */ + column_info = xnrealloc (column_info, max_cols, + 2 * sizeof *column_info); + new_column_info_alloc = 2 * max_cols; + } + else + { + column_info = xnrealloc (column_info, max_idx, sizeof *column_info); + new_column_info_alloc = max_idx; + } + + /* Allocate the new size_t objects by computing the triangle + formula n * (n + 1) / 2, except that we don't need to + allocate the part of the triangle that we've already + allocated. Check for address arithmetic overflow. */ + { + size_t column_info_growth = new_column_info_alloc - column_info_alloc; + size_t s = column_info_alloc + 1 + new_column_info_alloc; + size_t t = s * column_info_growth; + if (s < new_column_info_alloc || t / column_info_growth != s) + xalloc_die (); + p = xnmalloc (t / 2, sizeof *p); + } + + /* Grow the triangle by parceling out the cells just allocated. */ + for (i = column_info_alloc; i < new_column_info_alloc; i++) + { + column_info[i].col_arr = p; + p += i + 1; + } + + column_info_alloc = new_column_info_alloc; + } + + for (i = 0; i < max_cols; ++i) + { + size_t j; + + column_info[i].valid_len = true; + column_info[i].line_len = (i + 1) * MIN_COLUMN_WIDTH; + for (j = 0; j <= i; ++j) + column_info[i].col_arr[j] = MIN_COLUMN_WIDTH; + } +} + +/* Calculate the number of columns needed to represent the current set + of files in the current display width. */ + +static size_t +calculate_columns (bool by_columns) +{ + size_t filesno; /* Index into cwd_file. */ + size_t cols; /* Number of files across. */ + + /* Normally the maximum number of columns is determined by the + screen width. But if few files are available this might limit it + as well. */ + size_t max_cols = MIN (max_idx, cwd_n_used); + + init_column_info (); + + /* Compute the maximum number of possible columns. */ + for (filesno = 0; filesno < cwd_n_used; ++filesno) + { + struct fileinfo const *f = sorted_file[filesno]; + size_t name_length = length_of_file_name_and_frills (f); + size_t i; + + for (i = 0; i < max_cols; ++i) + { + if (column_info[i].valid_len) + { + size_t idx = (by_columns + ? filesno / ((cwd_n_used + i) / (i + 1)) + : filesno % (i + 1)); + size_t real_length = name_length + (idx == i ? 0 : 2); + + if (column_info[i].col_arr[idx] < real_length) + { + column_info[i].line_len += (real_length + - column_info[i].col_arr[idx]); + column_info[i].col_arr[idx] = real_length; + column_info[i].valid_len = (column_info[i].line_len + < line_length); + } + } + } + } + + /* Find maximum allowed columns. */ + for (cols = max_cols; 1 < cols; --cols) + { + if (column_info[cols - 1].valid_len) + break; + } + + return cols; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name); + fputs (_("\ +List information about the FILEs (the current directory by default).\n\ +Sort entries alphabetically if none of -cftuvSUX nor --sort.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + -a, --all do not ignore entries starting with .\n\ + -A, --almost-all do not list implied . and ..\n\ + --author with -l, print the author of each file\n\ + -b, --escape print octal escapes for nongraphic characters\n\ +"), stdout); + fputs (_("\ + --block-size=SIZE use SIZE-byte blocks. See SIZE format below\n\ + -B, --ignore-backups do not list implied entries ending with ~\n\ + -c with -lt: sort by, and show, ctime (time of last\n\ + modification of file status information)\n\ + with -l: show ctime and sort by name\n\ + otherwise: sort by ctime\n\ +"), stdout); + fputs (_("\ + -C list entries by columns\n\ + --color[=WHEN] colorize the output. WHEN defaults to `always'\n\ + or can be `never' or `auto'. More info below\n\ + -d, --directory list directory entries instead of contents,\n\ + and do not dereference symbolic links\n\ + -D, --dired generate output designed for Emacs' dired mode\n\ +"), stdout); + fputs (_("\ + -f do not sort, enable -aU, disable -ls --color\n\ + -F, --classify append indicator (one of */=>@|) to entries\n\ + --file-type likewise, except do not append `*'\n\ + --format=WORD across -x, commas -m, horizontal -x, long -l,\n\ + single-column -1, verbose -l, vertical -C\n\ + --full-time like -l --time-style=full-iso\n\ +"), stdout); + fputs (_("\ + -g like -l, but do not list owner\n\ +"), stdout); + fputs (_("\ + --group-directories-first\n\ + group directories before files.\n\ + augment with a --sort option, but any\n\ + use of --sort=none (-U) disables grouping\n\ +"), stdout); + fputs (_("\ + -G, --no-group in a long listing, don't print group names\n\ + -h, --human-readable with -l, print sizes in human readable format\n\ + (e.g., 1K 234M 2G)\n\ + --si likewise, but use powers of 1000 not 1024\n\ +"), stdout); + fputs (_("\ + -H, --dereference-command-line\n\ + follow symbolic links listed on the command line\n\ + --dereference-command-line-symlink-to-dir\n\ + follow each command line symbolic link\n\ + that points to a directory\n\ + --hide=PATTERN do not list implied entries matching shell PATTERN\n\ + (overridden by -a or -A)\n\ +"), stdout); + fputs (_("\ + --indicator-style=WORD append indicator with style WORD to entry names:\n\ + none (default), slash (-p),\n\ + file-type (--file-type), classify (-F)\n\ + -i, --inode print the index number of each file\n\ + -I, --ignore=PATTERN do not list implied entries matching shell PATTERN\n\ + -k like --block-size=1K\n\ +"), stdout); + fputs (_("\ + -l use a long listing format\n\ + -L, --dereference when showing file information for a symbolic\n\ + link, show information for the file the link\n\ + references rather than for the link itself\n\ + -m fill width with a comma separated list of entries\n\ +"), stdout); + fputs (_("\ + -n, --numeric-uid-gid like -l, but list numeric user and group IDs\n\ + -N, --literal print raw entry names (don't treat e.g. control\n\ + characters specially)\n\ + -o like -l, but do not list group information\n\ + -p, --indicator-style=slash\n\ + append / indicator to directories\n\ +"), stdout); + fputs (_("\ + -q, --hide-control-chars print ? instead of non graphic characters\n\ + --show-control-chars show non graphic characters as-is (default\n\ + unless program is `ls' and output is a terminal)\n\ + -Q, --quote-name enclose entry names in double quotes\n\ + --quoting-style=WORD use quoting style WORD for entry names:\n\ + literal, locale, shell, shell-always, c, escape\n\ +"), stdout); + fputs (_("\ + -r, --reverse reverse order while sorting\n\ + -R, --recursive list subdirectories recursively\n\ + -s, --size print the allocated size of each file, in blocks\n\ +"), stdout); + fputs (_("\ + -S sort by file size\n\ + --sort=WORD sort by WORD instead of name: none -U,\n\ + extension -X, size -S, time -t, version -v\n\ + --time=WORD with -l, show time as WORD instead of modification\n\ + time: atime -u, access -u, use -u, ctime -c,\n\ + or status -c; use specified time as sort key\n\ + if --sort=time\n\ +"), stdout); + fputs (_("\ + --time-style=STYLE with -l, show times using style STYLE:\n\ + full-iso, long-iso, iso, locale, +FORMAT.\n\ + FORMAT is interpreted like `date'; if FORMAT is\n\ + FORMAT1FORMAT2, FORMAT1 applies to\n\ + non-recent files and FORMAT2 to recent files;\n\ + if STYLE is prefixed with `posix-', STYLE\n\ + takes effect only outside the POSIX locale\n\ +"), stdout); + fputs (_("\ + -t sort by modification time\n\ + -T, --tabsize=COLS assume tab stops at each COLS instead of 8\n\ +"), stdout); + fputs (_("\ + -u with -lt: sort by, and show, access time\n\ + with -l: show access time and sort by name\n\ + otherwise: sort by access time\n\ + -U do not sort; list entries in directory order\n\ + -v natural sort of (version) numbers within text\n\ +"), stdout); + fputs (_("\ + -w, --width=COLS assume screen width instead of current value\n\ + -x list entries by lines instead of by columns\n\ + -X sort alphabetically by entry extension\n\ + -Z, --context print any SELinux security context of each file\n\ + -1 list one file per line\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + emit_size_note (); + fputs (_("\ +\n\ +Using color to distinguish file types is disabled both by default and\n\ +with --color=never. With --color=auto, ls emits color codes only when\n\ +standard output is connected to a terminal. The LS_COLORS environment\n\ +variable can change the settings. Use the dircolors command to set it.\n\ +"), stdout); + fputs (_("\ +\n\ +Exit status:\n\ + 0 if OK,\n\ + 1 if minor problems (e.g., cannot access subdirectory),\n\ + 2 if serious trouble (e.g., cannot access command-line argument).\n\ +"), stdout); + emit_ancillary_info (); + } + exit (status); +} diff -urNp coreutils-8.0-orig/src/mkdir.c coreutils-8.0/src/mkdir.c --- coreutils-8.0-orig/src/mkdir.c 2009-09-23 10:25:44.000000000 +0200 +++ coreutils-8.0/src/mkdir.c 2009-10-07 10:10:11.000000000 +0200 @@ -38,6 +38,7 @@ static struct option const longopts[] = { {GETOPT_SELINUX_CONTEXT_OPTION_DECL}, + {"context", required_argument, NULL, 'Z'}, {"mode", required_argument, NULL, 'm'}, {"parents", no_argument, NULL, 'p'}, {"verbose", no_argument, NULL, 'v'}, diff -urNp coreutils-8.0-orig/src/mknod.c coreutils-8.0/src/mknod.c --- coreutils-8.0-orig/src/mknod.c 2009-09-23 10:25:44.000000000 +0200 +++ coreutils-8.0/src/mknod.c 2009-10-07 10:10:11.000000000 +0200 @@ -35,7 +35,7 @@ static struct option const longopts[] = { - {GETOPT_SELINUX_CONTEXT_OPTION_DECL}, + {GETOPT_SELINUX_CONTEXT_OPTION_DECL}, {"mode", required_argument, NULL, 'm'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, diff -urNp coreutils-8.0-orig/src/mv.c coreutils-8.0/src/mv.c --- coreutils-8.0-orig/src/mv.c 2009-09-23 10:25:44.000000000 +0200 +++ coreutils-8.0/src/mv.c 2009-10-07 10:10:11.000000000 +0200 @@ -118,6 +118,7 @@ cp_option_init (struct cp_options *x) x->preserve_mode = true; x->preserve_timestamps = true; x->preserve_security_context = selinux_enabled; + x->set_security_context = false; x->reduce_diagnostics = false; x->require_preserve = false; /* FIXME: maybe make this an option */ x->require_preserve_context = false; diff -urNp coreutils-8.0-orig/src/mv.c.orig coreutils-8.0/src/mv.c.orig --- coreutils-8.0-orig/src/mv.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/mv.c.orig 2009-09-23 10:25:44.000000000 +0200 @@ -0,0 +1,495 @@ +/* mv -- move or rename files + Copyright (C) 86, 89, 90, 91, 1995-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Mike Parker, David MacKenzie, and Jim Meyering */ + +#include +#include +#include +#include +#include +#include + +#include "system.h" +#include "backupfile.h" +#include "copy.h" +#include "cp-hash.h" +#include "error.h" +#include "filenamecat.h" +#include "quote.h" +#include "remove.h" +#include "root-dev-ino.h" +#include "priv-set.h" + +/* The official name of this program (e.g., no `g' prefix). */ +#define PROGRAM_NAME "mv" + +#define AUTHORS \ + proper_name ("Mike Parker"), \ + proper_name ("David MacKenzie"), \ + proper_name ("Jim Meyering") + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1 +}; + +/* Remove any trailing slashes from each SOURCE argument. */ +static bool remove_trailing_slashes; + +static struct option const long_options[] = +{ + {"backup", optional_argument, NULL, 'b'}, + {"force", no_argument, NULL, 'f'}, + {"interactive", no_argument, NULL, 'i'}, + {"no-clobber", no_argument, NULL, 'n'}, + {"no-target-directory", no_argument, NULL, 'T'}, + {"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, + {"suffix", required_argument, NULL, 'S'}, + {"target-directory", required_argument, NULL, 't'}, + {"update", no_argument, NULL, 'u'}, + {"verbose", no_argument, NULL, 'v'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +static void +rm_option_init (struct rm_options *x) +{ + x->ignore_missing_files = false; + x->recursive = true; + x->one_file_system = false; + + /* Should we prompt for removal, too? No. Prompting for the `move' + part is enough. It implies removal. */ + x->interactive = RMI_NEVER; + x->stdin_tty = false; + + x->verbose = false; + + /* Since this program may well have to process additional command + line arguments after any call to `rm', that function must preserve + the initial working directory, in case one of those is a + `.'-relative name. */ + x->require_restore_cwd = true; + + { + static struct dev_ino dev_ino_buf; + x->root_dev_ino = get_root_dev_ino (&dev_ino_buf); + if (x->root_dev_ino == NULL) + error (EXIT_FAILURE, errno, _("failed to get attributes of %s"), + quote ("/")); + } +} + +static void +cp_option_init (struct cp_options *x) +{ + bool selinux_enabled = (0 < is_selinux_enabled ()); + + cp_options_default (x); + x->copy_as_regular = false; /* FIXME: maybe make this an option */ + x->reflink_mode = REFLINK_NEVER; + x->dereference = DEREF_NEVER; + x->unlink_dest_before_opening = false; + x->unlink_dest_after_failed_open = false; + x->hard_link = false; + x->interactive = I_UNSPECIFIED; + x->move_mode = true; + x->one_file_system = false; + x->preserve_ownership = true; + x->preserve_links = true; + x->preserve_mode = true; + x->preserve_timestamps = true; + x->preserve_security_context = selinux_enabled; + x->reduce_diagnostics = false; + x->require_preserve = false; /* FIXME: maybe make this an option */ + x->require_preserve_context = false; + x->preserve_xattr = true; + x->require_preserve_xattr = false; + x->recursive = true; + x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */ + x->symbolic_link = false; + x->set_mode = false; + x->mode = 0; + x->stdin_tty = isatty (STDIN_FILENO); + + x->open_dangling_dest_symlink = false; + x->update = false; + x->verbose = false; + x->dest_info = NULL; + x->src_info = NULL; +} + +/* FILE is the last operand of this command. Return true if FILE is a + directory. But report an error if there is a problem accessing FILE, other + than nonexistence (errno == ENOENT). */ + +static bool +target_directory_operand (char const *file) +{ + struct stat st; + int err = (stat (file, &st) == 0 ? 0 : errno); + bool is_a_dir = !err && S_ISDIR (st.st_mode); + if (err && err != ENOENT) + error (EXIT_FAILURE, err, _("accessing %s"), quote (file)); + return is_a_dir; +} + +/* Move SOURCE onto DEST. Handles cross-file-system moves. + If SOURCE is a directory, DEST must not exist. + Return true if successful. */ + +static bool +do_move (const char *source, const char *dest, const struct cp_options *x) +{ + bool copy_into_self; + bool rename_succeeded; + bool ok = copy (source, dest, false, x, ©_into_self, &rename_succeeded); + + if (ok) + { + char const *dir_to_remove; + if (copy_into_self) + { + /* In general, when copy returns with copy_into_self set, SOURCE is + the same as, or a parent of DEST. In this case we know it's a + parent. It doesn't make sense to move a directory into itself, and + besides in some situations doing so would give highly nonintuitive + results. Run this `mkdir b; touch a c; mv * b' in an empty + directory. Here's the result of running echo `find b -print`: + b b/a b/b b/b/a b/c. Notice that only file `a' was copied + into b/b. Handle this by giving a diagnostic, removing the + copied-into-self directory, DEST (`b/b' in the example), + and failing. */ + + dir_to_remove = NULL; + ok = false; + } + else if (rename_succeeded) + { + /* No need to remove anything. SOURCE was successfully + renamed to DEST. Or the user declined to rename a file. */ + dir_to_remove = NULL; + } + else + { + /* This may mean SOURCE and DEST referred to different devices. + It may also conceivably mean that even though they referred + to the same device, rename wasn't implemented for that device. + + E.g., (from Joel N. Weber), + [...] there might someday be cases where you can't rename + but you can copy where the device name is the same, especially + on Hurd. Consider an ftpfs with a primitive ftp server that + supports uploading, downloading and deleting, but not renaming. + + Also, note that comparing device numbers is not a reliable + check for `can-rename'. Some systems can be set up so that + files from many different physical devices all have the same + st_dev field. This is a feature of some NFS mounting + configurations. + + We reach this point if SOURCE has been successfully copied + to DEST. Now we have to remove SOURCE. + + This function used to resort to copying only when rename + failed and set errno to EXDEV. */ + + dir_to_remove = source; + } + + if (dir_to_remove != NULL) + { + struct rm_options rm_options; + enum RM_status status; + char const *dir[2]; + + rm_option_init (&rm_options); + rm_options.verbose = x->verbose; + dir[0] = dir_to_remove; + dir[1] = NULL; + + status = rm ((void*) dir, &rm_options); + assert (VALID_STATUS (status)); + if (status == RM_ERROR) + ok = false; + } + } + + return ok; +} + +/* Move file SOURCE onto DEST. Handles the case when DEST is a directory. + Treat DEST as a directory if DEST_IS_DIR. + Return true if successful. */ + +static bool +movefile (char *source, char *dest, bool dest_is_dir, + const struct cp_options *x) +{ + bool ok; + + /* This code was introduced to handle the ambiguity in the semantics + of mv that is induced by the varying semantics of the rename function. + Some systems (e.g., GNU/Linux) have a rename function that honors a + trailing slash, while others (like Solaris 5,6,7) have a rename + function that ignores a trailing slash. I believe the GNU/Linux + rename semantics are POSIX and susv2 compliant. */ + + if (remove_trailing_slashes) + strip_trailing_slashes (source); + + if (dest_is_dir) + { + /* Treat DEST as a directory; build the full filename. */ + char const *src_basename = last_component (source); + char *new_dest = file_name_concat (dest, src_basename, NULL); + strip_trailing_slashes (new_dest); + ok = do_move (source, new_dest, x); + free (new_dest); + } + else + { + ok = do_move (source, dest, x); + } + + return ok; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("\ +Usage: %s [OPTION]... [-T] SOURCE DEST\n\ + or: %s [OPTION]... SOURCE... DIRECTORY\n\ + or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ +"), + program_name, program_name, program_name); + fputs (_("\ +Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\ +\n\ +"), stdout); + fputs (_("\ +Mandatory arguments to long options are mandatory for short options too.\n\ +"), stdout); + fputs (_("\ + --backup[=CONTROL] make a backup of each existing destination file\n\ + -b like --backup but does not accept an argument\n\ + -f, --force do not prompt before overwriting\n\ + -i, --interactive prompt before overwrite\n\ + -n, --no-clobber do not overwrite an existing file\n\ +If you specify more than one of -i, -f, -n, only the final one takes effect.\n\ +"), stdout); + fputs (_("\ + --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ + argument\n\ + -S, --suffix=SUFFIX override the usual backup suffix\n\ +"), stdout); + fputs (_("\ + -t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\ + -T, --no-target-directory treat DEST as a normal file\n\ + -u, --update move only when the SOURCE file is newer\n\ + than the destination file or when the\n\ + destination file is missing\n\ + -v, --verbose explain what is being done\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + fputs (_("\ +\n\ +The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n\ +\n\ +"), stdout); + fputs (_("\ + none, off never make backups (even if --backup is given)\n\ + numbered, t make numbered backups\n\ + existing, nil numbered if numbered backups exist, simple otherwise\n\ + simple, never always make simple backups\n\ +"), stdout); + emit_ancillary_info (); + } + exit (status); +} + +int +main (int argc, char **argv) +{ + int c; + bool ok; + bool make_backups = false; + char *backup_suffix_string; + char *version_control_string = NULL; + struct cp_options x; + char *target_directory = NULL; + bool no_target_directory = false; + int n_files; + char **file; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdin); + + cp_option_init (&x); + + /* Try to disable the ability to unlink a directory. */ + priv_set_remove_linkdir (); + + /* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless + we'll actually use backup_suffix_string. */ + backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX"); + + while ((c = getopt_long (argc, argv, "bfint:uvS:T", long_options, NULL)) + != -1) + { + switch (c) + { + case 'b': + make_backups = true; + if (optarg) + version_control_string = optarg; + break; + case 'f': + x.interactive = I_ALWAYS_YES; + break; + case 'i': + x.interactive = I_ASK_USER; + break; + case 'n': + x.interactive = I_ALWAYS_NO; + break; + case STRIP_TRAILING_SLASHES_OPTION: + remove_trailing_slashes = true; + break; + case 't': + if (target_directory) + error (EXIT_FAILURE, 0, _("multiple target directories specified")); + else + { + struct stat st; + if (stat (optarg, &st) != 0) + error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg)); + if (! S_ISDIR (st.st_mode)) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (optarg)); + } + target_directory = optarg; + break; + case 'T': + no_target_directory = true; + break; + case 'u': + x.update = true; + break; + case 'v': + x.verbose = true; + break; + case 'S': + make_backups = true; + backup_suffix_string = optarg; + break; + case_GETOPT_HELP_CHAR; + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + default: + usage (EXIT_FAILURE); + } + } + + n_files = argc - optind; + file = argv + optind; + + if (n_files <= !target_directory) + { + if (n_files <= 0) + error (0, 0, _("missing file operand")); + else + error (0, 0, _("missing destination file operand after %s"), + quote (file[0])); + usage (EXIT_FAILURE); + } + + if (no_target_directory) + { + if (target_directory) + error (EXIT_FAILURE, 0, + _("cannot combine --target-directory (-t) " + "and --no-target-directory (-T)")); + if (2 < n_files) + { + error (0, 0, _("extra operand %s"), quote (file[2])); + usage (EXIT_FAILURE); + } + } + else if (!target_directory) + { + assert (2 <= n_files); + if (target_directory_operand (file[n_files - 1])) + target_directory = file[--n_files]; + else if (2 < n_files) + error (EXIT_FAILURE, 0, _("target %s is not a directory"), + quote (file[n_files - 1])); + } + + if (make_backups && x.interactive == I_ALWAYS_NO) + { + error (0, 0, + _("options --backup and --no-clobber are mutually exclusive")); + usage (EXIT_FAILURE); + } + + if (backup_suffix_string) + simple_backup_suffix = xstrdup (backup_suffix_string); + + x.backup_type = (make_backups + ? xget_version (_("backup type"), + version_control_string) + : no_backups); + + hash_init (); + + if (target_directory) + { + int i; + + /* Initialize the hash table only if we'll need it. + The problem it is used to detect can arise only if there are + two or more files to move. */ + if (2 <= n_files) + dest_info_init (&x); + + ok = true; + for (i = 0; i < n_files; ++i) + ok &= movefile (file[i], target_directory, true, &x); + } + else + ok = movefile (file[0], file[1], false, &x); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff -urNp coreutils-8.0-orig/src/runcon.c coreutils-8.0/src/runcon.c --- coreutils-8.0-orig/src/runcon.c 2009-10-06 10:55:34.000000000 +0200 +++ coreutils-8.0/src/runcon.c 2009-10-07 10:10:11.000000000 +0200 @@ -86,7 +86,7 @@ Usage: %s CONTEXT COMMAND [args]\n\ or: %s [ -c ] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [args]\n\ "), program_name, program_name); fputs (_("\ -Run a program in a different security context.\n\ +Run a program in a different SELinux security context.\n\ With neither CONTEXT nor COMMAND, print the current security context.\n\ \n\ CONTEXT Complete security context\n\ diff -urNp coreutils-8.0-orig/src/stat.c coreutils-8.0/src/stat.c --- coreutils-8.0-orig/src/stat.c 2009-09-29 16:25:44.000000000 +0200 +++ coreutils-8.0/src/stat.c 2009-10-07 10:10:11.000000000 +0200 @@ -825,7 +825,7 @@ print_it (char const *format, char const /* Stat the file system and print what we find. */ static bool -do_statfs (char const *filename, bool terse, char const *format) +do_statfs (char const *filename, bool terse, bool secure, char const *format) { STRUCT_STATVFS statfsbuf; @@ -844,15 +844,31 @@ do_statfs (char const *filename, bool te } if (format == NULL) + { + if (terse) { - format = (terse - ? "%n %i %l %t %s %S %b %f %a %c %d\n" - : " File: \"%n\"\n" + if (secure) + format = "%n %i %l %t %s %S %b %f %a %c %d %C\n"; + else + format = "%n %i %l %t %s %S %b %f %a %c %d\n"; + } + else + { + if (secure) + format = " File: \"%n\"\n" " ID: %-8i Namelen: %-7l Type: %T\n" "Block size: %-10s Fundamental block size: %S\n" "Blocks: Total: %-10b Free: %-10f Available: %a\n" - "Inodes: Total: %-10c Free: %d\n"); - } + "Inodes: Total: %-10c Free: %d\n" + " S_Context: %C\n"; + else + format = " File: \"%n\"\n" + " ID: %-8i Namelen: %-7l Type: %T\n" + "Block size: %-10s Fundamental block size: %S\n" + "Blocks: Total: %-10b Free: %-10f Available: %a\n" + "Inodes: Total: %-10c Free: %d\n"; + } + } print_it (format, filename, print_statfs, &statfsbuf); return true; @@ -860,7 +876,7 @@ do_statfs (char const *filename, bool te /* stat the file and print what we find */ static bool -do_stat (char const *filename, bool terse, char const *format) +do_stat (char const *filename, bool terse, bool secure, char const *format) { struct stat statbuf; @@ -881,9 +897,12 @@ do_stat (char const *filename, bool ters if (format == NULL) { if (terse) - { - format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n"; - } + { + if (secure) + format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o %C\n"; + else + format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n"; + } else { /* Temporary hack to match original output until conditional @@ -900,12 +919,22 @@ do_stat (char const *filename, bool ters } else { - format = - " File: %N\n" - " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" - "Device: %Dh/%dd\tInode: %-10i Links: %h\n" - "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" - "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + if (secure) + format = + " File: %N\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %-5h" + " Device type: %t,%T\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + " S_Context: %C\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + else + format = + " File: %N\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %h\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; } } } @@ -926,6 +955,7 @@ usage (int status) Display file or file system status.\n\ \n\ -L, --dereference follow links\n\ + -Z, --context print the SELinux security context \n\ -f, --file-system display file system status instead of file status\n\ "), stdout); fputs (_("\ @@ -1010,6 +1040,7 @@ main (int argc, char *argv[]) int i; bool fs = false; bool terse = false; + bool secure = false; char *format = NULL; bool ok = true; @@ -1049,13 +1080,13 @@ main (int argc, char *argv[]) terse = true; break; - case 'Z': /* FIXME: remove in 2010 */ - /* Ignore, for compatibility with distributions - that implemented this before upstream. - But warn of impending removal. */ - error (0, 0, - _("the --context (-Z) option is obsolete and will be removed\n" - "in a future release")); + case 'Z': + if((is_selinux_enabled()>0)) + secure = 1; + else { + error (0, 0, _("Kernel is not SELinux enabled")); + usage (EXIT_FAILURE); + } break; case_GETOPT_HELP_CHAR; @@ -1075,8 +1106,8 @@ main (int argc, char *argv[]) for (i = optind; i < argc; i++) ok &= (fs - ? do_statfs (argv[i], terse, format) - : do_stat (argv[i], terse, format)); + ? do_statfs (argv[i], terse, secure, format) + : do_stat (argv[i], terse, secure, format)); exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); } diff -urNp coreutils-8.0-orig/src/stat.c.orig coreutils-8.0/src/stat.c.orig --- coreutils-8.0-orig/src/stat.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ coreutils-8.0/src/stat.c.orig 2009-09-29 16:25:44.000000000 +0200 @@ -0,0 +1,1082 @@ +/* stat.c -- display file or file system status + Copyright (C) 2001-2009 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 a copy of the GNU General Public License + along with this program. If not, see . + + Written by Michael Meskes. */ + +#include + +/* Keep this conditional in sync with the similar conditional in + ../m4/stat-prog.m4. */ +#if (STAT_STATVFS \ + && (HAVE_STRUCT_STATVFS_F_BASETYPE || HAVE_STRUCT_STATVFS_F_FSTYPENAME \ + || (! HAVE_STRUCT_STATFS_F_FSTYPENAME && HAVE_STRUCT_STATVFS_F_TYPE))) +# define USE_STATVFS 1 +#else +# define USE_STATVFS 0 +#endif + +#include +#include +#include +#include +#include +#if USE_STATVFS +# include +#elif HAVE_SYS_VFS_H +# include +#elif HAVE_SYS_MOUNT_H && HAVE_SYS_PARAM_H +/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs. + It does have statvfs.h, but shouldn't use it, since it doesn't + HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */ +/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */ +# include +# include +# if HAVE_NETINET_IN_H && HAVE_NFS_NFS_CLNT_H && HAVE_NFS_VFS_H +/* Ultrix 4.4 needs these for the declaration of struct statfs. */ +# include +# include +# include +# endif +#elif HAVE_OS_H /* BeOS */ +# include +#endif +#include + +#include "system.h" + +#include "error.h" +#include "filemode.h" +#include "file-type.h" +#include "fs.h" +#include "getopt.h" +#include "quote.h" +#include "quotearg.h" +#include "stat-time.h" +#include "strftime.h" +#include "areadlink.h" + +#define alignof(type) offsetof (struct { char c; type x; }, x) + +#if USE_STATVFS +# define STRUCT_STATVFS struct statvfs +# define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATVFS_F_FSID_IS_INTEGER +# define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATVFS_F_TYPE +# if HAVE_STRUCT_STATVFS_F_NAMEMAX +# define SB_F_NAMEMAX(S) ((S)->f_namemax) +# endif +# define STATFS statvfs +# define STATFS_FRSIZE(S) ((S)->f_frsize) +#else +# define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATFS_F_TYPE +# if HAVE_STRUCT_STATFS_F_NAMELEN +# define SB_F_NAMEMAX(S) ((S)->f_namelen) +# endif +# define STATFS statfs +# if HAVE_OS_H /* BeOS */ +/* BeOS has a statvfs function, but it does not return sensible values + for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and + f_fstypename. Use 'struct fs_info' instead. */ +static int +statfs (char const *filename, struct fs_info *buf) +{ + dev_t device = dev_for_path (filename); + if (device < 0) + { + errno = (device == B_ENTRY_NOT_FOUND ? ENOENT + : device == B_BAD_VALUE ? EINVAL + : device == B_NAME_TOO_LONG ? ENAMETOOLONG + : device == B_NO_MEMORY ? ENOMEM + : device == B_FILE_ERROR ? EIO + : 0); + return -1; + } + /* If successful, buf->dev will be == device. */ + return fs_stat_dev (device, buf); +} +# define f_fsid dev +# define f_blocks total_blocks +# define f_bfree free_blocks +# define f_bavail free_blocks +# define f_bsize io_size +# define f_files total_nodes +# define f_ffree free_nodes +# define STRUCT_STATVFS struct fs_info +# define STRUCT_STATXFS_F_FSID_IS_INTEGER true +# define STATFS_FRSIZE(S) ((S)->block_size) +# else +# define STRUCT_STATVFS struct statfs +# define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATFS_F_FSID_IS_INTEGER +# define STATFS_FRSIZE(S) 0 +# endif +#endif + +#ifdef SB_F_NAMEMAX +# define OUT_NAMEMAX out_uint +#else +/* NetBSD 1.5.2 has neither f_namemax nor f_namelen. */ +# define SB_F_NAMEMAX(S) "*" +# define OUT_NAMEMAX out_string +#endif + +#if HAVE_STRUCT_STATVFS_F_BASETYPE +# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype +#else +# if HAVE_STRUCT_STATVFS_F_FSTYPENAME || HAVE_STRUCT_STATFS_F_FSTYPENAME +# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename +# elif HAVE_OS_H /* BeOS */ +# define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name +# endif +#endif + +/* FIXME: these are used by printf.c, too */ +#define isodigit(c) ('0' <= (c) && (c) <= '7') +#define octtobin(c) ((c) - '0') +#define hextobin(c) ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : \ + (c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0') + +#define PROGRAM_NAME "stat" + +#define AUTHORS proper_name ("Michael Meskes") + +enum +{ + PRINTF_OPTION = CHAR_MAX + 1 +}; + +static struct option const long_options[] = +{ + {"context", no_argument, 0, 'Z'}, + {"dereference", no_argument, NULL, 'L'}, + {"file-system", no_argument, NULL, 'f'}, + {"format", required_argument, NULL, 'c'}, + {"printf", required_argument, NULL, PRINTF_OPTION}, + {"terse", no_argument, NULL, 't'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +/* Whether to follow symbolic links; True for --dereference (-L). */ +static bool follow_links; + +/* Whether to interpret backslash-escape sequences. + True for --printf=FMT, not for --format=FMT (-c). */ +static bool interpret_backslash_escapes; + +/* The trailing delimiter string: + "" for --printf=FMT, "\n" for --format=FMT (-c). */ +static char const *trailing_delim = ""; + +/* Return the type of the specified file system. + Some systems have statfvs.f_basetype[FSTYPSZ] (AIX, HP-UX, and Solaris). + Others have statvfs.f_fstypename[_VFS_NAMELEN] (NetBSD 3.0). + Others have statfs.f_fstypename[MFSNAMELEN] (NetBSD 1.5.2). + Still others have neither and have to get by with f_type (GNU/Linux). + But f_type may only exist in statfs (Cygwin). */ +static char const * +human_fstype (STRUCT_STATVFS const *statfsbuf) +{ +#ifdef STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME + return statfsbuf->STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME; +#else + switch (statfsbuf->f_type) + { +# if defined __linux__ + + /* Compare with what's in libc: + f=/a/libc/sysdeps/unix/sysv/linux/linux_fsinfo.h + sed -n '/ADFS_SUPER_MAGIC/,/SYSFS_MAGIC/p' $f \ + | perl -n -e '/#define (.*?)_(?:SUPER_)MAGIC\s+0x(\S+)/' \ + -e 'and print "case S_MAGIC_$1: /\* 0x" . uc($2) . " *\/\n"' \ + | sort > sym_libc + perl -ne '/^\s+(case S_MAGIC_.*?): \/\* 0x(\S+) \*\//' \ + -e 'and do { $v=uc$2; print "$1: /\* 0x$v *\/\n"}' stat.c \ + | sort > sym_stat + diff -u sym_stat sym_libc + */ + + /* Also sync from the list in "man 2 statfs". */ + + /* IMPORTANT NOTE: Each of the following `case S_MAGIC_...:' + statements must be followed by a hexadecimal constant in + a comment. The S_MAGIC_... name and constant are automatically + combined to produce the #define directives in fs.h. */ + + case S_MAGIC_ADFS: /* 0xADF5 */ + return "adfs"; + case S_MAGIC_AFFS: /* 0xADFF */ + return "affs"; + case S_MAGIC_AUTOFS: /* 0x187 */ + return "autofs"; + case S_MAGIC_BEFS: /* 0x42465331 */ + return "befs"; + case S_MAGIC_BFS: /* 0x1BADFACE */ + return "bfs"; + case S_MAGIC_BINFMT_MISC: /* 0x42494e4d */ + return "binfmt_misc"; + case S_MAGIC_CODA: /* 0x73757245 */ + return "coda"; + case S_MAGIC_COH: /* 0x012FF7B7 */ + return "coh"; + case S_MAGIC_CRAMFS: /* 0x28CD3D45 */ + return "cramfs"; + case S_MAGIC_DEVFS: /* 0x1373 */ + return "devfs"; + case S_MAGIC_DEVPTS: /* 0x1CD1 */ + return "devpts"; + case S_MAGIC_EFS: /* 0x414A53 */ + return "efs"; + case S_MAGIC_EXT: /* 0x137D */ + return "ext"; + case S_MAGIC_EXT2: /* 0xEF53 */ + return "ext2/ext3"; + case S_MAGIC_EXT2_OLD: /* 0xEF51 */ + return "ext2"; + case S_MAGIC_FAT: /* 0x4006 */ + return "fat"; + case S_MAGIC_FUSECTL: /* 0x65735543 */ + return "fusectl"; + case S_MAGIC_HPFS: /* 0xF995E849 */ + return "hpfs"; + case S_MAGIC_HUGETLBFS: /* 0x958458f6 */ + return "hugetlbfs"; + case S_MAGIC_ISOFS: /* 0x9660 */ + return "isofs"; + case S_MAGIC_ISOFS_R_WIN: /* 0x4004 */ + return "isofs"; + case S_MAGIC_ISOFS_WIN: /* 0x4000 */ + return "isofs"; + case S_MAGIC_JFFS2: /* 0x72B6 */ + return "jffs2"; + case S_MAGIC_JFFS: /* 0x07C0 */ + return "jffs"; + case S_MAGIC_JFS: /* 0x3153464A */ + return "jfs"; + case S_MAGIC_LUSTRE: /* 0x0BD00BD0 */ + return "lustre"; + case S_MAGIC_MINIX: /* 0x137F */ + return "minix"; + case S_MAGIC_MINIX_30: /* 0x138F */ + return "minix (30 char.)"; + case S_MAGIC_MINIX_V2: /* 0x2468 */ + return "minix v2"; + case S_MAGIC_MINIX_V2_30: /* 0x2478 */ + return "minix v2 (30 char.)"; + case S_MAGIC_MSDOS: /* 0x4D44 */ + return "msdos"; + case S_MAGIC_NCP: /* 0x564C */ + return "novell"; + case S_MAGIC_NFS: /* 0x6969 */ + return "nfs"; + case S_MAGIC_NFSD: /* 0x6E667364 */ + return "nfsd"; + case S_MAGIC_NTFS: /* 0x5346544E */ + return "ntfs"; + case S_MAGIC_OPENPROM: /* 0x9fa1 */ + return "openprom"; + case S_MAGIC_PROC: /* 0x9FA0 */ + return "proc"; + case S_MAGIC_QNX4: /* 0x002F */ + return "qnx4"; + case S_MAGIC_RAMFS: /* 0x858458F6 */ + return "ramfs"; + case S_MAGIC_REISERFS: /* 0x52654973 */ + return "reiserfs"; + case S_MAGIC_ROMFS: /* 0x7275 */ + return "romfs"; + case S_MAGIC_SMB: /* 0x517B */ + return "smb"; + case S_MAGIC_SQUASHFS: /* 0x73717368 */ + return "squashfs"; + case S_MAGIC_SYSFS: /* 0x62656572 */ + return "sysfs"; + case S_MAGIC_SYSV2: /* 0x012FF7B6 */ + return "sysv2"; + case S_MAGIC_SYSV4: /* 0x012FF7B5 */ + return "sysv4"; + case S_MAGIC_TMPFS: /* 0x1021994 */ + return "tmpfs"; + case S_MAGIC_UDF: /* 0x15013346 */ + return "udf"; + case S_MAGIC_UFS: /* 0x00011954 */ + return "ufs"; + case S_MAGIC_UFS_BYTESWAPPED: /* 0x54190100 */ + return "ufs"; + case S_MAGIC_USBDEVFS: /* 0x9FA2 */ + return "usbdevfs"; + case S_MAGIC_VXFS: /* 0xA501FCF5 */ + return "vxfs"; + case S_MAGIC_XENIX: /* 0x012FF7B4 */ + return "xenix"; + case S_MAGIC_XFS: /* 0x58465342 */ + return "xfs"; + case S_MAGIC_XIAFS: /* 0x012FD16D */ + return "xia"; + +# elif __GNU__ + case FSTYPE_UFS: + return "ufs"; + case FSTYPE_NFS: + return "nfs"; + case FSTYPE_GFS: + return "gfs"; + case FSTYPE_LFS: + return "lfs"; + case FSTYPE_SYSV: + return "sysv"; + case FSTYPE_FTP: + return "ftp"; + case FSTYPE_TAR: + return "tar"; + case FSTYPE_AR: + return "ar"; + case FSTYPE_CPIO: + return "cpio"; + case FSTYPE_MSLOSS: + return "msloss"; + case FSTYPE_CPM: + return "cpm"; + case FSTYPE_HFS: + return "hfs"; + case FSTYPE_DTFS: + return "dtfs"; + case FSTYPE_GRFS: + return "grfs"; + case FSTYPE_TERM: + return "term"; + case FSTYPE_DEV: + return "dev"; + case FSTYPE_PROC: + return "proc"; + case FSTYPE_IFSOCK: + return "ifsock"; + case FSTYPE_AFS: + return "afs"; + case FSTYPE_DFS: + return "dfs"; + case FSTYPE_PROC9: + return "proc9"; + case FSTYPE_SOCKET: + return "socket"; + case FSTYPE_MISC: + return "misc"; + case FSTYPE_EXT2FS: + return "ext2/ext3"; + case FSTYPE_HTTP: + return "http"; + case FSTYPE_MEMFS: + return "memfs"; + case FSTYPE_ISO9660: + return "iso9660"; +# endif + default: + { + unsigned long int type = statfsbuf->f_type; + static char buf[sizeof "UNKNOWN (0x%lx)" - 3 + + (sizeof type * CHAR_BIT + 3) / 4]; + sprintf (buf, "UNKNOWN (0x%lx)", type); + return buf; + } + } +#endif +} + +static char * +human_access (struct stat const *statbuf) +{ + static char modebuf[12]; + filemodestring (statbuf, modebuf); + modebuf[10] = 0; + return modebuf; +} + +static char * +human_time (struct timespec t) +{ + static char str[MAX (INT_BUFSIZE_BOUND (intmax_t), + (INT_STRLEN_BOUND (int) /* YYYY */ + + 1 /* because YYYY might equal INT_MAX + 1900 */ + + sizeof "-MM-DD HH:MM:SS.NNNNNNNNN +ZZZZ"))]; + struct tm const *tm = localtime (&t.tv_sec); + if (tm == NULL) + return timetostr (t.tv_sec, str); + nstrftime (str, sizeof str, "%Y-%m-%d %H:%M:%S.%N %z", tm, 0, t.tv_nsec); + return str; +} + +static void +out_string (char *pformat, size_t prefix_len, char const *arg) +{ + strcpy (pformat + prefix_len, "s"); + printf (pformat, arg); +} +static void +out_int (char *pformat, size_t prefix_len, intmax_t arg) +{ + strcpy (pformat + prefix_len, PRIdMAX); + printf (pformat, arg); +} +static void +out_uint (char *pformat, size_t prefix_len, uintmax_t arg) +{ + strcpy (pformat + prefix_len, PRIuMAX); + printf (pformat, arg); +} +static void +out_uint_o (char *pformat, size_t prefix_len, uintmax_t arg) +{ + strcpy (pformat + prefix_len, PRIoMAX); + printf (pformat, arg); +} +static void +out_uint_x (char *pformat, size_t prefix_len, uintmax_t arg) +{ + strcpy (pformat + prefix_len, PRIxMAX); + printf (pformat, arg); +} + +/* Very specialized function (modifies FORMAT), just so as to avoid + duplicating this code between both print_statfs and print_stat. */ +static void +out_file_context (char const *filename, char *pformat, size_t prefix_len) +{ + char *scontext; + if ((follow_links + ? getfilecon (filename, &scontext) + : lgetfilecon (filename, &scontext)) < 0) + { + error (0, errno, _("failed to get security context of %s"), + quote (filename)); + scontext = NULL; + } + strcpy (pformat + prefix_len, "s"); + printf (pformat, (scontext ? scontext : "?")); + if (scontext) + freecon (scontext); +} + +/* print statfs info */ +static void +print_statfs (char *pformat, size_t prefix_len, char m, char const *filename, + void const *data) +{ + STRUCT_STATVFS const *statfsbuf = data; + + switch (m) + { + case 'n': + out_string (pformat, prefix_len, filename); + break; + + case 'i': + { +#if STRUCT_STATXFS_F_FSID_IS_INTEGER + uintmax_t fsid = statfsbuf->f_fsid; +#else + typedef unsigned int fsid_word; + verify (alignof (STRUCT_STATVFS) % alignof (fsid_word) == 0); + verify (offsetof (STRUCT_STATVFS, f_fsid) % alignof (fsid_word) == 0); + verify (sizeof statfsbuf->f_fsid % alignof (fsid_word) == 0); + fsid_word const *p = (fsid_word *) &statfsbuf->f_fsid; + + /* Assume a little-endian word order, as that is compatible + with glibc's statvfs implementation. */ + uintmax_t fsid = 0; + int words = sizeof statfsbuf->f_fsid / sizeof *p; + int i; + for (i = 0; i < words && i * sizeof *p < sizeof fsid; i++) + { + uintmax_t u = p[words - 1 - i]; + fsid |= u << (i * CHAR_BIT * sizeof *p); + } +#endif + out_uint_x (pformat, prefix_len, fsid); + } + break; + + case 'l': + OUT_NAMEMAX (pformat, prefix_len, SB_F_NAMEMAX (statfsbuf)); + break; + case 't': +#if HAVE_STRUCT_STATXFS_F_TYPE + out_uint_x (pformat, prefix_len, statfsbuf->f_type); +#else + fputc ('?', stdout); +#endif + break; + case 'T': + out_string (pformat, prefix_len, human_fstype (statfsbuf)); + break; + case 'b': + out_int (pformat, prefix_len, statfsbuf->f_blocks); + break; + case 'f': + out_int (pformat, prefix_len, statfsbuf->f_bfree); + break; + case 'a': + out_int (pformat, prefix_len, statfsbuf->f_bavail); + break; + case 's': + out_uint (pformat, prefix_len, statfsbuf->f_bsize); + break; + case 'S': + { + uintmax_t frsize = STATFS_FRSIZE (statfsbuf); + if (! frsize) + frsize = statfsbuf->f_bsize; + out_uint (pformat, prefix_len, frsize); + } + break; + case 'c': + out_uint (pformat, prefix_len, statfsbuf->f_files); + break; + case 'd': + out_int (pformat, prefix_len, statfsbuf->f_ffree); + break; + case 'C': + out_file_context (filename, pformat, prefix_len); + break; + default: + fputc ('?', stdout); + break; + } +} + +/* print stat info */ +static void +print_stat (char *pformat, size_t prefix_len, char m, + char const *filename, void const *data) +{ + struct stat *statbuf = (struct stat *) data; + struct passwd *pw_ent; + struct group *gw_ent; + + switch (m) + { + case 'n': + out_string (pformat, prefix_len, filename); + break; + case 'N': + out_string (pformat, prefix_len, quote (filename)); + if (S_ISLNK (statbuf->st_mode)) + { + char *linkname = areadlink_with_size (filename, statbuf->st_size); + if (linkname == NULL) + { + error (0, errno, _("cannot read symbolic link %s"), + quote (filename)); + return; + } + printf (" -> "); + out_string (pformat, prefix_len, quote (linkname)); + } + break; + case 'd': + out_uint (pformat, prefix_len, statbuf->st_dev); + break; + case 'D': + out_uint_x (pformat, prefix_len, statbuf->st_dev); + break; + case 'i': + out_uint (pformat, prefix_len, statbuf->st_ino); + break; + case 'a': + out_uint_o (pformat, prefix_len, statbuf->st_mode & CHMOD_MODE_BITS); + break; + case 'A': + out_string (pformat, prefix_len, human_access (statbuf)); + break; + case 'f': + out_uint_x (pformat, prefix_len, statbuf->st_mode); + break; + case 'F': + out_string (pformat, prefix_len, file_type (statbuf)); + break; + case 'h': + out_uint (pformat, prefix_len, statbuf->st_nlink); + break; + case 'u': + out_uint (pformat, prefix_len, statbuf->st_uid); + break; + case 'U': + setpwent (); + pw_ent = getpwuid (statbuf->st_uid); + out_string (pformat, prefix_len, + pw_ent ? pw_ent->pw_name : "UNKNOWN"); + break; + case 'g': + out_uint (pformat, prefix_len, statbuf->st_gid); + break; + case 'G': + setgrent (); + gw_ent = getgrgid (statbuf->st_gid); + out_string (pformat, prefix_len, + gw_ent ? gw_ent->gr_name : "UNKNOWN"); + break; + case 't': + out_uint_x (pformat, prefix_len, major (statbuf->st_rdev)); + break; + case 'T': + out_uint_x (pformat, prefix_len, minor (statbuf->st_rdev)); + break; + case 's': + out_uint (pformat, prefix_len, statbuf->st_size); + break; + case 'B': + out_uint (pformat, prefix_len, ST_NBLOCKSIZE); + break; + case 'b': + out_uint (pformat, prefix_len, ST_NBLOCKS (*statbuf)); + break; + case 'o': + out_uint (pformat, prefix_len, statbuf->st_blksize); + break; + case 'x': + out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf))); + break; + case 'X': + if (TYPE_SIGNED (time_t)) + out_int (pformat, prefix_len, statbuf->st_atime); + else + out_uint (pformat, prefix_len, statbuf->st_atime); + break; + case 'y': + out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf))); + break; + case 'Y': + if (TYPE_SIGNED (time_t)) + out_int (pformat, prefix_len, statbuf->st_mtime); + else + out_uint (pformat, prefix_len, statbuf->st_mtime); + break; + case 'z': + out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf))); + break; + case 'Z': + if (TYPE_SIGNED (time_t)) + out_int (pformat, prefix_len, statbuf->st_ctime); + else + out_uint (pformat, prefix_len, statbuf->st_ctime); + break; + case 'C': + out_file_context (filename, pformat, prefix_len); + break; + default: + fputc ('?', stdout); + break; + } +} + +/* Output a single-character \ escape. */ + +static void +print_esc_char (char c) +{ + switch (c) + { + case 'a': /* Alert. */ + c ='\a'; + break; + case 'b': /* Backspace. */ + c ='\b'; + break; + case 'f': /* Form feed. */ + c ='\f'; + break; + case 'n': /* New line. */ + c ='\n'; + break; + case 'r': /* Carriage return. */ + c ='\r'; + break; + case 't': /* Horizontal tab. */ + c ='\t'; + break; + case 'v': /* Vertical tab. */ + c ='\v'; + break; + case '"': + case '\\': + break; + default: + error (0, 0, _("warning: unrecognized escape `\\%c'"), c); + break; + } + putchar (c); +} + +static void +print_it (char const *format, char const *filename, + void (*print_func) (char *, size_t, char, char const *, void const *), + void const *data) +{ + /* Add 2 to accommodate our conversion of the stat `%s' format string + to the longer printf `%llu' one. */ + enum + { + MAX_ADDITIONAL_BYTES = + (MAX (sizeof PRIdMAX, + MAX (sizeof PRIoMAX, MAX (sizeof PRIuMAX, sizeof PRIxMAX))) + - 1) + }; + size_t n_alloc = strlen (format) + MAX_ADDITIONAL_BYTES + 1; + char *dest = xmalloc (n_alloc); + char const *b; + for (b = format; *b; b++) + { + switch (*b) + { + case '%': + { + size_t len = strspn (b + 1, "#-+.I 0123456789"); + char const *fmt_char = b + len + 1; + memcpy (dest, b, len + 1); + + b = fmt_char; + switch (*fmt_char) + { + case '\0': + --b; + /* fall through */ + case '%': + if (0 < len) + { + dest[len + 1] = *fmt_char; + dest[len + 2] = '\0'; + error (EXIT_FAILURE, 0, _("%s: invalid directive"), + quotearg_colon (dest)); + } + putchar ('%'); + break; + default: + print_func (dest, len + 1, *fmt_char, filename, data); + break; + } + break; + } + + case '\\': + if ( ! interpret_backslash_escapes) + { + putchar ('\\'); + break; + } + ++b; + if (isodigit (*b)) + { + int esc_value = octtobin (*b); + int esc_length = 1; /* number of octal digits */ + for (++b; esc_length < 3 && isodigit (*b); + ++esc_length, ++b) + { + esc_value = esc_value * 8 + octtobin (*b); + } + putchar (esc_value); + --b; + } + else if (*b == 'x' && isxdigit (to_uchar (b[1]))) + { + int esc_value = hextobin (b[1]); /* Value of \xhh escape. */ + /* A hexadecimal \xhh escape sequence must have + 1 or 2 hex. digits. */ + ++b; + if (isxdigit (to_uchar (b[1]))) + { + ++b; + esc_value = esc_value * 16 + hextobin (*b); + } + putchar (esc_value); + } + else if (*b == '\0') + { + error (0, 0, _("warning: backslash at end of format")); + putchar ('\\'); + /* Arrange to exit the loop. */ + --b; + } + else + { + print_esc_char (*b); + } + break; + + default: + putchar (*b); + break; + } + } + free (dest); + + fputs (trailing_delim, stdout); +} + +/* Stat the file system and print what we find. */ +static bool +do_statfs (char const *filename, bool terse, char const *format) +{ + STRUCT_STATVFS statfsbuf; + + if (STREQ (filename, "-")) + { + error (0, 0, _("using %s to denote standard input does not work" + " in file system mode"), quote (filename)); + return false; + } + + if (STATFS (filename, &statfsbuf) != 0) + { + error (0, errno, _("cannot read file system information for %s"), + quote (filename)); + return false; + } + + if (format == NULL) + { + format = (terse + ? "%n %i %l %t %s %S %b %f %a %c %d\n" + : " File: \"%n\"\n" + " ID: %-8i Namelen: %-7l Type: %T\n" + "Block size: %-10s Fundamental block size: %S\n" + "Blocks: Total: %-10b Free: %-10f Available: %a\n" + "Inodes: Total: %-10c Free: %d\n"); + } + + print_it (format, filename, print_statfs, &statfsbuf); + return true; +} + +/* stat the file and print what we find */ +static bool +do_stat (char const *filename, bool terse, char const *format) +{ + struct stat statbuf; + + if (STREQ (filename, "-")) + { + if (fstat (STDIN_FILENO, &statbuf) != 0) + { + error (0, errno, _("cannot stat standard input")); + return false; + } + } + else if ((follow_links ? stat : lstat) (filename, &statbuf) != 0) + { + error (0, errno, _("cannot stat %s"), quote (filename)); + return false; + } + + if (format == NULL) + { + if (terse) + { + format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o\n"; + } + else + { + /* Temporary hack to match original output until conditional + implemented. */ + if (S_ISBLK (statbuf.st_mode) || S_ISCHR (statbuf.st_mode)) + { + format = + " File: %N\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %-5h" + " Device type: %t,%T\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } + else + { + format = + " File: %N\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %h\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } + } + } + print_it (format, filename, print_stat, &statbuf); + return true; +} + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + printf (_("Usage: %s [OPTION]... FILE...\n"), program_name); + fputs (_("\ +Display file or file system status.\n\ +\n\ + -L, --dereference follow links\n\ + -f, --file-system display file system status instead of file status\n\ +"), stdout); + fputs (_("\ + -c --format=FORMAT use the specified FORMAT instead of the default;\n\ + output a newline after each use of FORMAT\n\ + --printf=FORMAT like --format, but interpret backslash escapes,\n\ + and do not output a mandatory trailing newline.\n\ + If you want a newline, include \\n in FORMAT.\n\ + -t, --terse print the information in terse form\n\ +"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + + fputs (_("\n\ +The valid format sequences for files (without --file-system):\n\ +\n\ + %a Access rights in octal\n\ + %A Access rights in human readable form\n\ + %b Number of blocks allocated (see %B)\n\ + %B The size in bytes of each block reported by %b\n\ + %C SELinux security context string\n\ +"), stdout); + fputs (_("\ + %d Device number in decimal\n\ + %D Device number in hex\n\ + %f Raw mode in hex\n\ + %F File type\n\ + %g Group ID of owner\n\ + %G Group name of owner\n\ +"), stdout); + fputs (_("\ + %h Number of hard links\n\ + %i Inode number\n\ + %n File name\n\ + %N Quoted file name with dereference if symbolic link\n\ + %o I/O block size\n\ + %s Total size, in bytes\n\ + %t Major device type in hex\n\ + %T Minor device type in hex\n\ +"), stdout); + fputs (_("\ + %u User ID of owner\n\ + %U User name of owner\n\ + %x Time of last access\n\ + %X Time of last access as seconds since Epoch\n\ + %y Time of last modification\n\ + %Y Time of last modification as seconds since Epoch\n\ + %z Time of last change\n\ + %Z Time of last change as seconds since Epoch\n\ +\n\ +"), stdout); + + fputs (_("\ +Valid format sequences for file systems:\n\ +\n\ + %a Free blocks available to non-superuser\n\ + %b Total data blocks in file system\n\ + %c Total file nodes in file system\n\ + %d Free file nodes in file system\n\ + %f Free blocks in file system\n\ + %C SELinux security context string\n\ +"), stdout); + fputs (_("\ + %i File System ID in hex\n\ + %l Maximum length of filenames\n\ + %n File name\n\ + %s Block size (for faster transfers)\n\ + %S Fundamental block size (for block counts)\n\ + %t Type in hex\n\ + %T Type in human readable form\n\ +"), stdout); + printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME); + emit_ancillary_info (); + } + exit (status); +} + +int +main (int argc, char *argv[]) +{ + int c; + int i; + bool fs = false; + bool terse = false; + char *format = NULL; + bool ok = true; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((c = getopt_long (argc, argv, "c:fLtZ", long_options, NULL)) != -1) + { + switch (c) + { + case PRINTF_OPTION: + format = optarg; + interpret_backslash_escapes = true; + trailing_delim = ""; + break; + + case 'c': + format = optarg; + interpret_backslash_escapes = false; + trailing_delim = "\n"; + break; + + case 'L': + follow_links = true; + break; + + case 'f': + fs = true; + break; + + case 't': + terse = true; + break; + + case 'Z': /* FIXME: remove in 2010 */ + /* Ignore, for compatibility with distributions + that implemented this before upstream. + But warn of impending removal. */ + error (0, 0, + _("the --context (-Z) option is obsolete and will be removed\n" + "in a future release")); + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + if (argc == optind) + { + error (0, 0, _("missing operand")); + usage (EXIT_FAILURE); + } + + for (i = optind; i < argc; i++) + ok &= (fs + ? do_statfs (argv[i], terse, format) + : do_stat (argv[i], terse, format)); + + exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff -urNp coreutils-8.0-orig/tests/misc/selinux coreutils-8.0/tests/misc/selinux --- coreutils-8.0-orig/tests/misc/selinux 2009-09-01 13:01:16.000000000 +0200 +++ coreutils-8.0/tests/misc/selinux 2009-10-07 10:10:11.000000000 +0200 @@ -30,7 +30,7 @@ chcon $ctx f d p || # inspect that context with both ls -Z and stat. for i in d f p; do - c=`ls -dogZ $i|cut -d' ' -f3`; test x$c = x$ctx || fail=1 + c=`ls -dogZ $i|cut -d' ' -f5`; test x$c = x$ctx || fail=1 c=`stat --printf %C $i`; test x$c = x$ctx || fail=1 done