Rocking the blogosphere
Apple Online Store

Patch for GNU touch to add -p option, a la mkdir

The other day I realized that often when I’m using touch, I’d like it to have the ability to create ancestor directories that don’t exist, a la mkdir -p. I quickly hacked together a shell script to do what I want.

Then I thought that this might be a generally useful extension to touch, so I thought I’d take a stab at adding it directly to the touch source code in GNU coreutils. Here’s my patch, which I sent to the mailing list:

--- coreutils-6.7.orig/src/touch.c      2006-10-22 09:54:15.000000000 -0700
+++ coreutils-6.7/src/touch.c   2006-12-28 15:35:28.000000000 -0800
@@ -34,6 +34,8 @@
 #include "safe-read.h"
 #include "stat-time.h"
 #include "utimens.h"
+#include "savewd.h"
+#include "mkdir-p.h"

 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "touch"
@@ -112,6 +114,62 @@
     error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date));
 }

+/* 
+ */
+
+/* Options passed to subsidiary functions.  */
+struct mkdir_options
+{
+  /* Function to make an ancestor, or NULL if ancestors should not be
+     made.  */
+  int (*make_ancestor_function) (char const *, char const *, void *);
+
+  /* Mode for ancestor directory.  */
+  mode_t ancestor_mode;
+
+  /* Mode for directory itself.  */
+  mode_t mode;
+
+  /* File mode bits affected by MODE.  */
+  mode_t mode_bits;
+
+  /* If not null, format to use when reporting newly made directories.  */
+  char const *created_directory_format;
+};
+
+static struct mkdir_options global_mkdir_options;
+
+static void
+announce_mkdir (char const *dir, void *options)
+{
+  struct mkdir_options const *o = options;
+  if (o->created_directory_format)
+    error (0, 0, o->created_directory_format, quote (dir));
+}
+
+/* Make ancestor directory DIR, whose last component is COMPONENT,
+   with options OPTIONS.  Assume the working directory is COMPONENT's
+   parent.  Return 0 if successful and the resulting directory is
+   readable, 1 if successful but the resulting directory is not
+   readable, -1 (setting errno) otherwise.  */
+static int
+make_ancestor (char const *dir, char const *component, void *options)
+{
+  struct mkdir_options const *o = options;
+  int r = mkdir (component, o->ancestor_mode);
+  if (r == 0)
+    {
+      r = ! (o->ancestor_mode & S_IRUSR);
+      announce_mkdir (dir, options);
+    }
+  return r;
+}
+
+/*  */
+
 /* Update the time of file FILE according to the options given.
    Return true if successful.  */

@@ -129,6 +187,18 @@
     fd = STDOUT_FILENO;
   else if (! no_create)
     {
+      {
+        char *dir = dir_name (file);
+        struct savewd wd;
+        savewd_init (&wd);
+        void *make_ancestor_function = global_mkdir_options.make_ancestor_function;
+        mode_t mode = global_mkdir_options.mode;
+        mode_t mode_bits = global_mkdir_options.mode_bits;
+        make_dir_parents (dir, &wd, make_ancestor_function, &global_mkdir_options,
+                          mode, announce_mkdir,
+                          mode_bits, (uid_t) -1, (gid_t) -1, true);
+      }
+
       /* Try to open FILE, creating it if necessary.  */
       fd = fd_reopen (STDIN_FILENO, file,
                      O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
@@ -246,6 +316,7 @@
   -m                     change only the modification time\n\
 "), stdout);
       fputs (_("\
+  -p, --parents          no error if existing, make parent directories as needed\n\
   -r, --reference=FILE   use this file's times instead of current time\n\
   -t STAMP               use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
   --time=WORD            change the specified time:\n\
@@ -272,6 +343,10 @@
   bool date_set = false;
   bool ok = true;
   char const *flex_date = NULL;
+  global_mkdir_options.make_ancestor_function = NULL;
+  global_mkdir_options.mode = S_IRWXUGO;
+  global_mkdir_options.mode_bits = 0;
+  global_mkdir_options.created_directory_format = NULL;

   initialize_main (&argc, &argv);
   program_name = argv[0];
@@ -284,7 +359,7 @@
   change_times = 0;
   no_create = use_ref = false;

-  while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
+  while ((c = getopt_long (argc, argv, "acd:fmpr:t:", longopts, NULL)) != -1)
     {
       switch (c)
        {
@@ -307,6 +382,12 @@
          change_times |= CH_MTIME;
          break;

+        case 'p':
+          global_mkdir_options.make_ancestor_function = make_ancestor;
+          mode_t umask_value = umask (0);
+          global_mkdir_options.ancestor_mode = (S_IRWXUGO & ~umask_value) | (S_IWUSR | S_IXUSR);
+          break;
+
        case 'r':
          use_ref = true;
          ref_file = optarg;

and I also have this patch in Subversion here.

This feels fairly hacky to me, as there’s a fair amount of code duplication from mkdir.c - my hope is that the coreutils maintainers can take this and clean it up, perhaps by moving some of the common functionality into the lib directory.

Del.icio.us Digg Reddit Technorati

Possibly related posts

No comments yet. Be the first.

Leave a reply

Apple Online Store
Apple Online Store