Subject: Re: bug#8736: chmod -p --parents

From: Jérémy Compostella <[email-masked]>
Date: Thu, 23 Feb 2012 00:40:37 +0100

> Hi,
> 
> On Thu, May 26, 2011 at 10:13:05PM +0200, francky.leyn <at> telenet.be wrote:
> > 
> > In the past, I was an advocate of the -p --parents option for 
> > mkdir. By now this is realised. Now I'm doing the same for chmod. 
> > [...]
> > I want to be able to execute the following: 
> > 
> > chmod a+rx -p ~/dir1/dir2/dir3/file.ext 
> 
> I like the proposal.
> > It should be more refined however: the dirs 
> > should be rx, but the file only r. 
> 
> You can use a capital X to automatically handle the directory case. But
> please take a look at the docs for the _exact_ meaning of X.
I made a first implementation of this chmod --parents feature (see the
attached patch). I do not support the second part since 'X' is already
in used for the "Conditional Executability" feature. I have no idea how
to support it.

I don't have written the documentation for now. I would like to have
your opinion about this patch first as I maybe missed some corner case
or the code should be written another way or even something else ...

I really hope you will find time to look at it.

Cheers,

Jérémy
---
Sent from my Emacs

>From 5de3629e2f181779460947ea8693561692425d75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Compostella?= <[email-masked]>
Date: Thu, 23 Feb 2012 00:11:41 +0100
Subject: [PATCH] chmod: add --parents option

* chmod.c (main): Handle --parents option.
---
 src/chmod.c |   51 ++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/src/chmod.c b/src/chmod.c
index a134e3f..4378459 100644
--- a/src/chmod.c
+++ b/src/chmod.c
@@ -96,6 +96,7 @@ static struct option const long_options[] =
 {
   {"changes", no_argument, NULL, 'c'},
   {"recursive", no_argument, NULL, 'R'},
+  {"parents", no_argument, NULL, 'p'},
   {"no-preserve-root", no_argument, NULL, NO_PRESERVE_ROOT},
   {"preserve-root", no_argument, NULL, PRESERVE_ROOT},
   {"quiet", no_argument, NULL, 'f'},
@@ -416,6 +417,7 @@ main (int argc, char **argv)
   size_t mode_alloc = 0;
   bool ok;
   bool preserve_root = false;
+  bool parents = false;
   char const *reference_file = NULL;
   int c;
 
@@ -430,7 +432,7 @@ 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::,::+::=::",
+                           "Rpcfvr::w::x::X::s::t::u::g::o::a::,::+::=::",
                            long_options, NULL))
          != -1)
     {
@@ -484,6 +486,9 @@ main (int argc, char **argv)
         case REFERENCE_FILE_OPTION:
           reference_file = optarg;
           break;
+        case 'p':
+          parents = true;
+          break;
         case 'R':
           recurse = true;
           break;
@@ -544,6 +549,12 @@ main (int argc, char **argv)
       umask_value = umask (0);
     }
 
+  if (recurse && parents)
+    {
+      error (0, 0, _("--recurse and --parents are mutually exclusive"));
+      usage (EXIT_FAILURE);
+    }
+
   if (recurse && preserve_root)
     {
       static struct dev_ino dev_ino_buf;
@@ -557,8 +568,42 @@ main (int argc, char **argv)
       root_dev_ino = NULL;
     }
 
-  ok = process_files (argv + optind,
-                      FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
+  if (! parents)
+    ok = process_files (argv + optind,
+                        FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
+  else
+  {
+    char **first = argv + optind;
+    char **end = argv + argc;
+    while (end != first)
+      {
+        ok = process_files (first,
+                            FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT);
+        if (! ok)
+          break;
+
+        char **current = end - 1;
+        do
+          {
+            char *last = last_component (*current);
+            if (last != *current) {
+              /* Skiping slashes preceding the last component */
+              while (ISSLASH (*--last) && last != *current)
+                ;
+              if (! ISSLASH (*last))
+                *++last = '\0';
+            }
+            if (last == *current)
+              {
+                /* Remove CURRENT by shifting ARGV to left from there */
+                end = current - 1;
+                while (*end && end++)
+                  *end = *(end + 1);
+              }
+          }
+        while (current-- != first);
+      }
+  }
 
   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }
-- 
1.7.2.5