bug#55937: [PATCH] touch: create parent directories if needed
Thanks for taking the time to review my patch. I agree with you that this change is not strictly required. It's more of a nice to have / ergonomic improvement to the existing touch interface. There are several StackOverflow posts that ask for this very feature: https://unix.stackexchange.com/q/63098 & https://unix.stackexchange.com/q/305844 Below is the help text for touch: > Update the access and modification times of each FILE to the current time. > A FILE argument that does not exist is created empty, unless -c or -h is supplied. I was surprised to learn there was no existing flag that would allow directory creation when a file and its directories do not exist. Currently if directories do not exist, the command fails. I would go as far to argue that creating a file also implies creating any required directories, since directories must exist before we can create said file. I also agree with David Hilton's recommendation that we should not change the default behavior of touch, like in my first patch, but rather add an opt-in flag for this behavior. Thank you for reading my reply and I look forward to your future feedback. On Tue, Jun 14, 2022 at 10:37 PM Paul Eggert wrote: > On 6/14/22 19:20, Alan Rosenthal wrote: > > `touch -p a/b/c/d/e` will now be the same as running: > > `mkdir -p a/b/c/d && touch a/b/c/d/e`. > > I don't see how this useful enough to merit a change, since one can > achieve the effect of the proposed "touch -p" with the already-existing > "mkdir -p" followed by plain "touch". mkdir -p already exists and should > work everywhere that's POSIX-compatible. We don't need -p for other > commands that create files (e.g., cp, mv, ln); what's special about > 'touch'? > >
bug#55937: [PATCH] touch: create parent directories if needed
On 6/14/22 19:20, Alan Rosenthal wrote: `touch -p a/b/c/d/e` will now be the same as running: `mkdir -p a/b/c/d && touch a/b/c/d/e`. I don't see how this useful enough to merit a change, since one can achieve the effect of the proposed "touch -p" with the already-existing "mkdir -p" followed by plain "touch". mkdir -p already exists and should work everywhere that's POSIX-compatible. We don't need -p for other commands that create files (e.g., cp, mv, ln); what's special about 'touch'?
bug#55937: [PATCH] touch: create parent directories if needed
`touch -p a/b/c/d/e` will now be the same as running: `mkdir -p a/b/c/d && touch a/b/c/d/e`. Added an option -p/--create-dirs to create any required directories. Default behavior remains the same. --- src/touch.c | 40 +++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/touch.c b/src/touch.c index 21c247d0b..543f92b41 100644 --- a/src/touch.c +++ b/src/touch.c @@ -28,10 +28,12 @@ #include "die.h" #include "error.h" #include "fd-reopen.h" +#include "mkancesdirs.h" #include "parse-datetime.h" #include "posixtm.h" #include "posixver.h" #include "quote.h" +#include "savewd.h" #include "stat-time.h" #include "utimens.h" @@ -55,6 +57,9 @@ static int change_times; /* (-c) If true, don't create if not already there. */ static bool no_create; +/* (-p) If true, create directories if not already there. */ +static bool create_dirs; + /* (-r) If true, use times from a reference file. */ static bool use_ref; @@ -88,6 +93,7 @@ static struct option const longopts[] = {"date", required_argument, NULL, 'd'}, {"reference", required_argument, NULL, 'r'}, {"no-dereference", no_argument, NULL, 'h'}, + {"create-dirs", no_argument, NULL, 'p'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -116,6 +122,14 @@ get_reldate (struct timespec *result, die (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date)); } +/* Create directory, called by mkancesdirs(). */ + +static int +make_dir(char const * file, char const * component, void * arg) +{ + return mkdir(component, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); +} + /* Update the time of file FILE according to the options given. Return true if successful. */ @@ -130,6 +144,25 @@ touch (char const *file) fd = STDOUT_FILENO; else if (! (no_create || no_dereference)) { + if (create_dirs) +{ + struct savewd wd; + savewd_init(); + ptrdiff_t ret = mkancesdirs((char*) file, , make_dir, NULL); + if (ret == -1) +{ + error (0, open_errno, _("cannot mkdir %s"), quoteaf (file)); + return false; +} + int r = savewd_restore(, 0); + if (r < 0) +{ + error (0, open_errno, _("cannot mkdir %s"), quoteaf (file)); + return false; +} + savewd_finish(); +} + /* Try to open FILE, creating it if necessary. */ fd = fd_reopen (STDIN_FILENO, file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, MODE_RW_UGO); @@ -240,6 +273,7 @@ change the times of the file associated with standard output.\n\ -m change only the modification time\n\ "), stdout); fputs (_("\ + -p, --create-dirs create any required parent directories\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=WORDchange the specified time:\n\ @@ -276,7 +310,7 @@ main (int argc, char **argv) change_times = 0; no_create = use_ref = false; - while ((c = getopt_long (argc, argv, "acd:fhmr:t:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "acd:fhmpr:t:", longopts, NULL)) != -1) { switch (c) { @@ -303,6 +337,10 @@ main (int argc, char **argv) change_times |= CH_MTIME; break; +case 'p': + create_dirs = true; + break; + case 'r': use_ref = true; ref_file = optarg; -- 2.20.1 On Mon, Jun 13, 2022 at 7:52 AM Alan Rosenthal wrote: > `touch a/b/c/d/e` will now be the same as running `mkdir -p a/b/c/d && > touch a/b/c/d/e`. > Added an option --no-create-dirs to not create any directories. > --- > src/touch.c | 40 +++- > 1 file changed, 39 insertions(+), 1 deletion(-) > > diff --git a/src/touch.c b/src/touch.c > index 21c247d0b..557530f79 100644 > --- a/src/touch.c > +++ b/src/touch.c > @@ -28,10 +28,12 @@ > #include "die.h" > #include "error.h" > #include "fd-reopen.h" > +#include "mkancesdirs.h" > #include "parse-datetime.h" > #include "posixtm.h" > #include "posixver.h" > #include "quote.h" > +#include "savewd.h" > #include "stat-time.h" > #include "utimens.h" > > @@ -55,6 +57,9 @@ static int change_times; > /* (-c) If true, don't create if not already there. */ > static bool no_create; > > +/* (-c) If true, don't create directories if not already there. */ > +static bool no_create_dirs; > + > /* (-r) If true, use times from a reference file. */ > static bool use_ref; > > @@ -88,6 +93,7 @@ static struct option const longopts[] = >{"date", required_argument, NULL, 'd'}, >{"reference", required_argument, NULL, 'r'}, >{"no-dereference", no_argument, NULL, 'h'}, > + {"no-create-dirs", no_argument, NULL, 'i'}, >
bug#55937: [PATCH] touch: create parent directories if needed
I don't like this as the default behavior. If this feature is added, it should be optional, and enabled with -p, like mkdir. David
bug#55937: [PATCH] touch: create parent directories if needed
`touch a/b/c/d/e` will now be the same as running `mkdir -p a/b/c/d && touch a/b/c/d/e`. Added an option --no-create-dirs to not create any directories. --- src/touch.c | 40 +++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/touch.c b/src/touch.c index 21c247d0b..557530f79 100644 --- a/src/touch.c +++ b/src/touch.c @@ -28,10 +28,12 @@ #include "die.h" #include "error.h" #include "fd-reopen.h" +#include "mkancesdirs.h" #include "parse-datetime.h" #include "posixtm.h" #include "posixver.h" #include "quote.h" +#include "savewd.h" #include "stat-time.h" #include "utimens.h" @@ -55,6 +57,9 @@ static int change_times; /* (-c) If true, don't create if not already there. */ static bool no_create; +/* (-c) If true, don't create directories if not already there. */ +static bool no_create_dirs; + /* (-r) If true, use times from a reference file. */ static bool use_ref; @@ -88,6 +93,7 @@ static struct option const longopts[] = {"date", required_argument, NULL, 'd'}, {"reference", required_argument, NULL, 'r'}, {"no-dereference", no_argument, NULL, 'h'}, + {"no-create-dirs", no_argument, NULL, 'i'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -116,6 +122,14 @@ get_reldate (struct timespec *result, die (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date)); } +/* Create directory, called by mkancesdirs(). */ + +static int +make_dir(char const * file, char const * component, void * arg) +{ + return mkdir(component, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); +} + /* Update the time of file FILE according to the options given. Return true if successful. */ @@ -130,6 +144,25 @@ touch (char const *file) fd = STDOUT_FILENO; else if (! (no_create || no_dereference)) { + if (! no_create_dirs) +{ + struct savewd wd; + savewd_init(); + ptrdiff_t ret = mkancesdirs((char*) file, , make_dir, NULL); + if (ret == -1) +{ + error (0, open_errno, _("cannot mkdir %s"), quoteaf (file)); + return false; +} + int r = savewd_restore(, 0); + if (r < 0) +{ + error (0, open_errno, _("cannot mkdir %s"), quoteaf (file)); + return false; +} + savewd_finish(); +} + /* Try to open FILE, creating it if necessary. */ fd = fd_reopen (STDIN_FILENO, file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, MODE_RW_UGO); @@ -234,6 +267,7 @@ change the times of the file associated with standard output.\n\ -f (ignored)\n\ "), stdout); fputs (_("\ + -i, --no-create-dirs do not create any required parent directories\n\ -h, --no-dereference affect each symbolic link instead of any referenced\n\ file (useful only on systems that can change the\n\ timestamps of a symlink)\n\ @@ -276,7 +310,7 @@ main (int argc, char **argv) change_times = 0; no_create = use_ref = false; - while ((c = getopt_long (argc, argv, "acd:fhmr:t:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "acd:fhimr:t:", longopts, NULL)) != -1) { switch (c) { @@ -299,6 +333,10 @@ main (int argc, char **argv) no_dereference = true; break; +case 'i': + no_create_dirs = true; + break; + case 'm': change_times |= CH_MTIME; break; -- 2.20.1 On Sun, Jun 12, 2022 at 10:05 PM Alan Rosenthal wrote: > `touch a/b/c/d/e` will now be the same as running `mkdir -p a/b/c/d && > touch a/b/c/d/e`. > Added an option --no-create-dirs to not create any directories. > --- > src/touch.c | 39 ++- > 1 file changed, 38 insertions(+), 1 deletion(-) > > diff --git a/src/touch.c b/src/touch.c > index 21c247d0b..9034e8797 100644 > --- a/src/touch.c > +++ b/src/touch.c > @@ -28,10 +28,12 @@ > #include "die.h" > #include "error.h" > #include "fd-reopen.h" > +#include "mkancesdirs.h" > #include "parse-datetime.h" > #include "posixtm.h" > #include "posixver.h" > #include "quote.h" > +#include "savewd.h" > #include "stat-time.h" > #include "utimens.h" > > @@ -55,6 +57,9 @@ static int change_times; > /* (-c) If true, don't create if not already there. */ > static bool no_create; > > +/* (-c) If true, don't create directories if not already there. */ > +static bool no_create_dirs; > + > /* (-r) If true, use times from a reference file. */ > static bool use_ref; > > @@ -88,6 +93,7 @@ static struct option const longopts[] = >{"date", required_argument, NULL, 'd'}, >{"reference", required_argument, NULL, 'r'}, >{"no-dereference", no_argument, NULL, 'h'}, > + {"no_create_dirs", no_argument, NULL, 'i'}, >{GETOPT_HELP_OPTION_DECL}, >{GETOPT_VERSION_OPTION_DECL}, >
bug#55937: [PATCH] touch: create parent directories if needed
`touch a/b/c/d/e` will now be the same as running `mkdir -p a/b/c/d && touch a/b/c/d/e`. Added an option --no-create-dirs to not create any directories. --- src/touch.c | 39 ++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/touch.c b/src/touch.c index 21c247d0b..9034e8797 100644 --- a/src/touch.c +++ b/src/touch.c @@ -28,10 +28,12 @@ #include "die.h" #include "error.h" #include "fd-reopen.h" +#include "mkancesdirs.h" #include "parse-datetime.h" #include "posixtm.h" #include "posixver.h" #include "quote.h" +#include "savewd.h" #include "stat-time.h" #include "utimens.h" @@ -55,6 +57,9 @@ static int change_times; /* (-c) If true, don't create if not already there. */ static bool no_create; +/* (-c) If true, don't create directories if not already there. */ +static bool no_create_dirs; + /* (-r) If true, use times from a reference file. */ static bool use_ref; @@ -88,6 +93,7 @@ static struct option const longopts[] = {"date", required_argument, NULL, 'd'}, {"reference", required_argument, NULL, 'r'}, {"no-dereference", no_argument, NULL, 'h'}, + {"no_create_dirs", no_argument, NULL, 'i'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -116,6 +122,14 @@ get_reldate (struct timespec *result, die (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date)); } +/* Create directory, called by mkancesdirs(). */ + +static int +make_dir(char const * file, char const * component, void * arg) +{ + return mkdir(component, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); +} + /* Update the time of file FILE according to the options given. Return true if successful. */ @@ -130,6 +144,25 @@ touch (char const *file) fd = STDOUT_FILENO; else if (! (no_create || no_dereference)) { + if (! no_create_dirs) +{ + struct savewd wd; + savewd_init(); + ptrdiff_t ret = mkancesdirs((char*) file, , make_dir, NULL); + if (ret == -1) +{ + error (0, open_errno, _("cannot mkdir %s"), quoteaf (file)); + return false; +} + int r = savewd_restore(, 0); + if (r < 0) +{ + error (0, open_errno, _("cannot mkdir %s"), quoteaf (file)); + return false; +} + savewd_finish(); +} + /* Try to open FILE, creating it if necessary. */ fd = fd_reopen (STDIN_FILENO, file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, MODE_RW_UGO); @@ -276,7 +309,7 @@ main (int argc, char **argv) change_times = 0; no_create = use_ref = false; - while ((c = getopt_long (argc, argv, "acd:fhmr:t:", longopts, NULL)) != -1) + while ((c = getopt_long (argc, argv, "acd:fhimr:t:", longopts, NULL)) != -1) { switch (c) { @@ -299,6 +332,10 @@ main (int argc, char **argv) no_dereference = true; break; +case 'i': + no_create_dirs = true; + break; + case 'm': change_times |= CH_MTIME; break; -- 2.20.1