coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Chmod: -L option


From: Jakub Martisko
Subject: Re: Chmod: -L option
Date: Mon, 28 Nov 2016 09:51:18 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.6.0

I've modified the patch form the link mentioned in the
original post and added few tests. Any comments are welcomed.

JM

On 23.11.2016 08:41, Jakub Martisko wrote:
> Hello, according to the man page: "In contrast, chmod
> ignores symbolic links encountered during recursive
> directory traversals". Is there any reason for this? I've
> done a bit of mailing list archaeology and found this [1]
> thread with patch. I might try to rewrite the patch for
> current version and add some tests if there is no reason
> that would discourage from having this functionality.
> 
> JM
> 
> 
> [1]
> http://lists.gnu.org/archive/html/bug-coreutils/2011-12/msg00089.html
> 
>From dca59f52c45d54601ecb8862a3755ddb1bea900c Mon Sep 17 00:00:00 2001
From: Jakub Martisko <address@hidden>
Date: Mon, 28 Nov 2016 09:13:39 +0100
Subject: [PATCH] chmod: add support for -L -P -H -h --dereference options

* src/chmod.c support for options mentioned above.
Achieved by usage of FTS_ bitflags. Patch based on:
lists.gnu.org/archive/html/bug-coreutils/2011-12/msg00089.html
* tests/chmod/symlinks.sh add test cases for the new
options.
* tests/local.mk add the new tests.
* man/chmod.x new options mentioned.
---
 man/chmod.x             |   4 +-
 src/chmod.c             | 108 +++++++++++++++++++++++++++++++++++++++++++-----
 tests/chmod/symlinks.sh |  93 +++++++++++++++++++++++++++++++++++++++++
 tests/local.mk          |   1 +
 4 files changed, 195 insertions(+), 11 deletions(-)
 create mode 100644 tests/chmod/symlinks.sh

diff --git a/man/chmod.x b/man/chmod.x
index 5761d36..de0f773 100644
--- a/man/chmod.x
+++ b/man/chmod.x
@@ -70,7 +70,9 @@ changes the permissions of the pointed-to file.
 In contrast,
 .B chmod
 ignores symbolic links encountered during recursive directory
-traversals.
+traversals. Options that modify this behavior are described in the
+.B OPTIONS
+section.
 .SH "SETUID AND SETGID BITS"
 .B chmod
 clears the set-group-ID bit of a
diff --git a/src/chmod.c b/src/chmod.c
index 89e2232..5cd0970 100644
--- a/src/chmod.c
+++ b/src/chmod.c
@@ -68,6 +68,10 @@ static mode_t umask_value;
 /* If true, change the modes of directories recursively. */
 static bool recurse;

+/* 1 if --dereference, 0 if --no-dereference, -1 if neither has been
+   specified.  */
+static int dereference = -1;
+
 /* If true, force silence (suppress most of error messages). */
 static bool force_silent;

@@ -87,7 +91,8 @@ static struct dev_ino *root_dev_ino;
    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
 enum
 {
-  NO_PRESERVE_ROOT = CHAR_MAX + 1,
+  DEREFERENCE_OPTION = CHAR_MAX + 1,
+  NO_PRESERVE_ROOT,
   PRESERVE_ROOT,
   REFERENCE_FILE_OPTION
 };
@@ -95,7 +100,9 @@ enum
 static struct option const long_options[] =
 {
   {"changes", no_argument, NULL, 'c'},
+  {"dereference", no_argument, NULL, DEREFERENCE_OPTION},
   {"recursive", no_argument, NULL, 'R'},
+  {"no-dereference", no_argument, NULL, 'h'},
   {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
   {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
   {"quiet", no_argument, NULL, 'f'},
@@ -190,11 +197,13 @@ 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;
+  struct stat stat_buf;
   mode_t old_mode IF_LINT ( = 0);
   mode_t new_mode IF_LINT ( = 0);
   bool ok = true;
   bool chmod_succeeded = false;

+
   switch (ent->fts_info)
     {
     case FTS_DP:
@@ -234,10 +243,29 @@ process_file (FTS *fts, FTSENT *ent)
       break;

     case FTS_SLNONE:
-      if (! force_silent)
-        error (0, 0, _("cannot operate on dangling symlink %s"),
-               quoteaf (file_full_name));
-      ok = false;
+     if (dereference)
+       {
+        if (! force_silent)
+          error (0, 0, _("cannot operate on dangling symlink %s"),
+                 quoteaf (file_full_name));
+        ok = false;
+
+       }
+      break;
+
+    case FTS_SL:
+      if (dereference == 1)
+        {
+          if (fstatat (fts->fts_cwd_fd, file, &stat_buf, 0) != 0)
+            {
+              if (! force_silent)
+                error (0, errno, _("cannot dereference %s"),
+                       quote (file_full_name));
+              ok = false;
+            }
+
+          file_stats = &stat_buf;
+        }
       break;

     case FTS_DC:               /* directory that causes cycles */
@@ -270,7 +298,8 @@ process_file (FTS *fts, FTSENT *ent)

       if (! S_ISLNK (old_mode))
         {
-          if (chmodat (fts->fts_cwd_fd, file, new_mode) == 0)
+          if (fchmodat (fts->fts_cwd_fd, file, new_mode,
+                    dereference ? 0 : AT_SYMLINK_NOFOLLOW) == 0)
             chmod_succeeded = true;
           else
             {
@@ -388,7 +417,15 @@ With --reference, change the mode of each FILE to that of 
RFILE.\n\
   -v, --verbose          output a diagnostic for every file processed\n\
 "), stdout);
       fputs (_("\
-      --no-preserve-root  do not treat '/' specially (the default)\n\
+      --dereference      affect the referent of each symbolic link (this is\n\
+                         the default), rather than the symbolic link itself\n\
+  -h, --no-dereference   affect each symbolic link instead of any referenced\n\
+                         file (useful only on systems that can change the\n\
+                         ownership of a symlink)\n\
+  "), stdout);
+
+      fputs (_("\
+    --no-preserve-root  do not treat '/' specially (the default)\n\
       --preserve-root    fail to operate recursively on '/'\n\
 "), stdout);
       fputs (_("\
@@ -396,6 +433,20 @@ With --reference, change the mode of each FILE to that of 
RFILE.\n\
 "), stdout);
       fputs (_("\
   -R, --recursive        change files and directories recursively\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 (this is the\n\
+                         default)\n\
+  -L                     traverse every symbolic link to a directory\n\
+                         encountered\n\
+  -P                     do not traverse any symbolic links\n\
+\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -421,6 +472,8 @@ main (int argc, char **argv)
   bool preserve_root = false;
   char const *reference_file = NULL;
   int c;
+  int bit_flags = FTS_COMFOLLOW | FTS_PHYSICAL;
+

   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -433,13 +486,35 @@ main (int argc, char **argv)
   recurse = force_silent = diagnose_surprises = false;

   while ((c = getopt_long (argc, argv,
-                           ("Rcfvr::w::x::X::s::t::u::g::o::a::,::+::=::"
+                           ("HLPRcfhvr::w::x::X::s::t::u::g::o::a::,::+::=::"
                             "0::1::2::3::4::5::6::7::"),
                            long_options, NULL))
          != -1)
     {
       switch (c)
         {
+
+        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 'r':
         case 'w':
         case 'x':
@@ -509,6 +584,18 @@ main (int argc, char **argv)
         }
     }

+  if (recurse)
+    {
+      if (bit_flags == FTS_PHYSICAL)
+        {
+          if (dereference == 1)
+            error (EXIT_FAILURE, 0,
+                   _("-R --dereference requires either -H or -L"));
+          dereference = 0;
+        }
+    }
+
+
   if (reference_file)
     {
       if (mode)
@@ -563,8 +650,9 @@ main (int argc, char **argv)
       root_dev_ino = NULL;
     }

-  ok = process_files (argv + optind,
-                      FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
+  bit_flags |= FTS_DEFER_STAT;
+
+  ok = process_files (argv + optind, bit_flags);

   return ok ? EXIT_SUCCESS : EXIT_FAILURE;
 }
diff --git a/tests/chmod/symlinks.sh b/tests/chmod/symlinks.sh
new file mode 100644
index 0000000..354f4ae
--- /dev/null
+++ b/tests/chmod/symlinks.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+# Verify that chmod works correctly with odd option combinations.
+
+# Copyright (C) 2004-2016 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/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ chmod
+
+mkdir -p ./a/b
+mkdir -p ./a/c
+
+touch ./a/b/file
+touch ./a/c/file
+
+#dangling link
+ln -s ./a/foo ./a/link
+
+#link to file
+ln -s ../b/file ./a/c/link
+#link to folder
+ln -s ./a/b/ ./a/folderlink
+
+#set initial modes
+chmod 777 ./a/b ./a/c ./a/b/file ./a/c/file
+#try using -R (with default -H)
+chmod 755 -R ./a/c
+ls -l ./a/b | grep -c "rwxr-xr-x">b_out
+ls -lR ./a/c | grep -c "rwxr-xr-x">c_out
+
+echo "0">b_exp
+echo "1">c_exp
+
+compare b_exp b_out || fail=1
+compare c_exp c_out || fail=1
+
+#reset
+chmod 777 ./a/b ./a/c ./a/b/file ./a/c/file
+# set ./a/c ./a/c/file and ./a/b/file (through symlink) to 755
+chmod 755 -LR ./a/c
+
+echo "$( ls -l ./a/b ) $( ls -lR ./a/c |grep file)  $( ls -l ./a )"|grep -o 
"rwxr-xr-x"|wc -l > out
+echo "3">exp
+
+compare exp out || fail=1
+
+
+#reset
+chmod 777 ./a/b ./a/c ./a/b/file ./a/c/file
+# set /a/file through symlink (should try to chmod the link itself)
+chmod 755 -RP ./a/c/
+ls -l ./a/b | grep -c "rwxr-xr-x">out
+echo "0">exp
+
+compare exp out || fail=1
+
+#reset
+chmod 777 ./a/b ./a/c ./a/b/file ./a/c/file
+# set /a/b/file through symlink
+chmod 755 --dereference ./a/c/link
+ls -l ./a/b | grep -c "rwxr-xr-x">out
+echo "1">exp
+
+compare exp out || fail=1
+
+#reset
+chmod 777 ./a/b ./a/c ./a/b/file ./a/c/file
+# set /a/b/file through symlink (should try to chmod the link itself)
+chmod 755 --no-dereference ./a/c/link 2>./err
+ls -l ./a/b | grep -c "rwxr-xr-x">out
+echo "0">exp
+
+compare exp out || fail=1
+
+if [ $(uname) == "Linux" ]
+then
+echo "chmod: changing permissions of './a/c/link': Operation not supported" 
>exp
+compare exp err || fail=1
+fi
+
+Exit $fail
diff --git a/tests/local.mk b/tests/local.mk
index 3335002..e1b607e 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -433,6 +433,7 @@ all_tests =                                 \
   tests/chmod/thru-dangling.sh                 \
   tests/chmod/umask-x.sh                       \
   tests/chmod/usage.sh                         \
+  tests/chmod/symlinks.sh                              \
   tests/chown/deref.sh                         \
   tests/chown/preserve-root.sh                 \
   tests/chown/separator.sh                     \
--
2.5.5

reply via email to

[Prev in Thread] Current Thread [Next in Thread]