12543 lines
404 KiB
Diff
12543 lines
404 KiB
Diff
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 <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+/* Extracted from cp.c and librarified by Jim Meyering. */
|
|
+
|
|
+#include <config.h>
|
|
+#include <stdio.h>
|
|
+#include <assert.h>
|
|
+#include <sys/types.h>
|
|
+#include <selinux/selinux.h>
|
|
+
|
|
+#if HAVE_HURD_H
|
|
+# include <hurd.h>
|
|
+#endif
|
|
+#if HAVE_PRIV_H
|
|
+# include <priv.h>
|
|
+#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 <attr/error_context.h>
|
|
+# include <attr/libattr.h>
|
|
+# include <stdarg.h>
|
|
+# include "verror.h"
|
|
+#endif
|
|
+
|
|
+#if HAVE_SYS_IOCTL_H
|
|
+# include <sys/ioctl.h>
|
|
+#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.*\<E[A-Z]*\>.*\<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 <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+/* Extracted from cp.c and librarified by Jim Meyering. */
|
|
+
|
|
+#ifndef COPY_H
|
|
+# define COPY_H
|
|
+
|
|
+# include <stdbool.h>
|
|
+# 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 <http://www.gnu.org/licenses/>.
|
|
+
|
|
+ Written by Torbjorn Granlund, David MacKenzie, and Jim Meyering. */
|
|
+
|
|
+#include <config.h>
|
|
+#include <stdio.h>
|
|
+#include <sys/types.h>
|
|
+#include <getopt.h>
|
|
+#include <selinux/selinux.h>
|
|
+
|
|
+#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 <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+#include <config.h>
|
|
+#include <stdio.h>
|
|
+#include <sys/types.h>
|
|
+#include <getopt.h>
|
|
+
|
|
+#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 <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+/* Written by David MacKenzie <djm@gnu.ai.mit.edu> */
|
|
+
|
|
+#include <config.h>
|
|
+#include <stdio.h>
|
|
+#include <getopt.h>
|
|
+#include <sys/types.h>
|
|
+#include <signal.h>
|
|
+#include <pwd.h>
|
|
+#include <grp.h>
|
|
+#include <selinux/selinux.h>
|
|
+
|
|
+#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 <sys/wait.h>
|
|
+#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, "<<none>>"))
|
|
+ {
|
|
+ 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 <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+/* 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 <Peter.Anvin@linux.org> and Dennis
|
|
+ Flaherty <dennisf@denix.elk.miles.com> based on original patches by
|
|
+ Greg Lee <lee@uhunix.uhcc.hawaii.edu>. */
|
|
+
|
|
+#include <config.h>
|
|
+#include <sys/types.h>
|
|
+
|
|
+#if HAVE_TERMIOS_H
|
|
+# include <termios.h>
|
|
+#endif
|
|
+#if HAVE_STROPTS_H
|
|
+# include <stropts.h>
|
|
+#endif
|
|
+#if HAVE_SYS_IOCTL_H
|
|
+# include <sys/ioctl.h>
|
|
+#endif
|
|
+
|
|
+#ifdef WINSIZE_IN_PTEM
|
|
+# include <sys/stream.h>
|
|
+# include <sys/ptem.h>
|
|
+#endif
|
|
+
|
|
+#include <stdio.h>
|
|
+#include <assert.h>
|
|
+#include <setjmp.h>
|
|
+#include <grp.h>
|
|
+#include <pwd.h>
|
|
+#include <getopt.h>
|
|
+#include <signal.h>
|
|
+#include <selinux/selinux.h>
|
|
+#include <wchar.h>
|
|
+
|
|
+#if HAVE_LANGINFO_CODESET
|
|
+# include <langinfo.h>
|
|
+#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 <fnmatch.h>
|
|
+
|
|
+#ifdef HAVE_CAP
|
|
+# include <sys/capability.h>
|
|
+#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\
|
|
+ FORMAT1<newline>FORMAT2, 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 <http://www.gnu.org/licenses/>. */
|
|
+
|
|
+/* Written by Mike Parker, David MacKenzie, and Jim Meyering */
|
|
+
|
|
+#include <config.h>
|
|
+#include <stdio.h>
|
|
+#include <getopt.h>
|
|
+#include <sys/types.h>
|
|
+#include <assert.h>
|
|
+#include <selinux/selinux.h>
|
|
+
|
|
+#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 <http://www.gnu.org/licenses/>.
|
|
+
|
|
+ Written by Michael Meskes. */
|
|
+
|
|
+#include <config.h>
|
|
+
|
|
+/* 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 <stddef.h>
|
|
+#include <stdio.h>
|
|
+#include <sys/types.h>
|
|
+#include <pwd.h>
|
|
+#include <grp.h>
|
|
+#if USE_STATVFS
|
|
+# include <sys/statvfs.h>
|
|
+#elif HAVE_SYS_VFS_H
|
|
+# include <sys/vfs.h>
|
|
+#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 <sys/param.h>
|
|
+# include <sys/mount.h>
|
|
+# 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 <netinet/in.h>
|
|
+# include <nfs/nfs_clnt.h>
|
|
+# include <nfs/vfs.h>
|
|
+# endif
|
|
+#elif HAVE_OS_H /* BeOS */
|
|
+# include <fs_info.h>
|
|
+#endif
|
|
+#include <selinux/selinux.h>
|
|
+
|
|
+#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
|
|
|