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.