X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmkdir-p.c;h=b074c0aa784169bb0428733b448d13745b9baa42;hb=8de557e31178699dd6e839850056f0653cdfba89;hp=433d871aed16767a506df02dcc2ff18a6d451b25;hpb=359c0a71713e606f440d40f55a43c77349df2aaa;p=gnulib.git diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c index 433d871ae..b074c0aa7 100644 --- a/lib/mkdir-p.c +++ b/lib/mkdir-p.c @@ -19,7 +19,7 @@ /* Written by David MacKenzie and Jim Meyering. */ -#if HAVE_CONFIG_H +#ifdef HAVE_CONFIG_H # include #endif @@ -30,9 +30,7 @@ #include #include #include -#if HAVE_UNISTD_H -# include -#endif +#include #include #include @@ -47,89 +45,11 @@ #include "quote.h" #include "stat-macros.h" -#define WX_USR (S_IWUSR | S_IXUSR) - -#define CLEANUP_CWD \ - do \ - { \ - /* We're done operating on basename_dir. \ - Restore working directory. */ \ - if (do_chdir) \ - { \ - if (restore_cwd (&cwd) != 0) \ - { \ - int _saved_errno = errno; \ - error (0, errno, \ - _("failed to return to initial working directory")); \ - free_cwd (&cwd); \ - errno = _saved_errno; \ - return 1; \ - } \ - free_cwd (&cwd); \ - } \ - } \ - while (0) - -#define CLEANUP \ - do \ - { \ - umask (oldmask); \ - CLEANUP_CWD; \ - } \ - while (0) - -/* Attempt to create directory DIR (aka FULLDIR) with the specified MODE. - If CREATED_DIR_P is non-NULL, set *CREATED_DIR_P if this - function creates DIR and clear it otherwise. Give a diagnostic and - return false if DIR cannot be created or cannot be determined to - exist already. Use FULLDIR in any diagnostic, not DIR. - Note that if DIR already exists, this function returns true - (indicating success) and clears *CREATED_DIR_P. */ - -bool -make_dir (char const *dir, char const *fulldir, mode_t mode, - bool *created_dir_p) -{ - bool ok = true; - bool created_dir; - - created_dir = (mkdir (dir, mode) == 0); - - if (!created_dir) - { - struct stat stats; - int saved_errno = errno; - - /* The mkdir and stat calls below may appear to be reversed. - They are not. It is important to call mkdir first and then to - call stat (to distinguish the three cases) only if mkdir fails. - The alternative to this approach is to `stat' each directory, - then to call mkdir if it doesn't exist. But if some other process - were to create the directory between the stat & mkdir, the mkdir - would fail with EEXIST. */ - - if (stat (dir, &stats)) - { - error (0, saved_errno, _("cannot create directory %s"), - quote (fulldir)); - ok = false; - } - else if (!S_ISDIR (stats.st_mode)) - { - error (0, 0, _("%s exists but is not a directory"), quote (fulldir)); - ok = false; - } - else - { - /* DIR (aka FULLDIR) already exists and is a directory. */ - } - } - - if (created_dir_p) - *created_dir_p = created_dir; +#ifndef ENOSYS +# define ENOSYS EEXIST +#endif - return ok; -} +#define WX_USR (S_IWUSR | S_IXUSR) /* Ensure that the directory ARG exists. @@ -145,8 +65,14 @@ make_dir (char const *dir, char const *fulldir, mode_t mode, If PRESERVE_EXISTING is true and ARG is an existing directory, then do not attempt to set its permissions and ownership. - Return true iff ARG exists as a directory with the proper - ownership and permissions when done. */ + Set *CWD_ERRNO to a (nonzero) error number if this + function has changed the current working directory and is unable to + restore it to its initial state. Do not change + *CWD_ERRNO otherwise. + + Return true iff ARG exists as a directory with the proper ownership + and permissions when done. Note that this function returns true + even when it fails to return to the initial working directory. */ bool make_dir_parents (char const *arg, @@ -155,24 +81,45 @@ make_dir_parents (char const *arg, uid_t owner, gid_t group, bool preserve_existing, - char const *verbose_fmt_string) + char const *verbose_fmt_string, + int *cwd_errno) { struct stat stats; bool retval = true; + bool do_chdir = false; /* Whether to chdir before each mkdir. */ + struct saved_cwd cwd; + bool cwd_problem = false; + char const *fixup_permissions_dir = NULL; + char const *full_dir = arg; + + struct ptr_list + { + char *dirname_end; + struct ptr_list *next; + }; + struct ptr_list *leading_dirs = NULL; + + if (stat (arg, &stats) == 0) + { + if (! S_ISDIR (stats.st_mode)) + { + error (0, 0, _("%s exists but is not a directory"), quote (arg)); + return false; + } - if (stat (arg, &stats) != 0) + if (!preserve_existing) + fixup_permissions_dir = arg; + } + else if (errno != ENOENT || !*arg) + { + error (0, errno, "%s", quote (arg)); + return false; + } + else { char *slash; mode_t tmp_mode; /* Initial perms for leading dirs. */ bool re_protect; /* Should leading dirs be unwritable? */ - struct ptr_list - { - char *dirname_end; - struct ptr_list *next; - }; - struct ptr_list *p, *leading_dirs = NULL; - bool do_chdir; /* Whether to chdir before each mkdir. */ - struct saved_cwd cwd; char *basename_dir; char *dir; @@ -183,6 +130,7 @@ make_dir_parents (char const *arg, dir = (char *) alloca (strlen (arg) + 1); strcpy (dir, arg); strip_trailing_slashes (dir); + full_dir = dir; /* If leading directories shouldn't be writable or executable, or should have set[ug]id or sticky bits set and we are setting @@ -213,7 +161,10 @@ make_dir_parents (char const *arg, file name starts with exactly two slashes. */ char const *root = "//" + (dir[1] != '/' || dir[2] == '/'); if (chdir (root) != 0) - do_chdir = false; + { + free_cwd (&cwd); + do_chdir = false; + } } slash = dir; @@ -222,10 +173,8 @@ make_dir_parents (char const *arg, while (*slash == '/') slash++; - while (1) + while (true) { - bool newly_created_dir; - /* slash points to the leftmost unprocessed component of dir. */ basename_dir = slash; @@ -239,13 +188,7 @@ make_dir_parents (char const *arg, basename_dir = dir; *slash = '\0'; - if (! make_dir (basename_dir, dir, tmp_mode, &newly_created_dir)) - { - CLEANUP; - return false; - } - - if (newly_created_dir) + if (mkdir (basename_dir, tmp_mode) == 0) { if (verbose_fmt_string) error (0, 0, verbose_fmt_string, quote (dir)); @@ -259,35 +202,50 @@ make_dir_parents (char const *arg, { error (0, errno, _("cannot change owner and/or group of %s"), quote (dir)); - CLEANUP; - return false; + retval = false; + break; } if (re_protect) { struct ptr_list *new = (struct ptr_list *) - alloca (sizeof (struct ptr_list)); + alloca (sizeof *new); new->dirname_end = slash; new->next = leading_dirs; leading_dirs = new; } } + else if (errno == EEXIST || errno == ENOSYS) + { + /* A file is already there. Perhaps it is a directory. + If not, it will be diagnosed later. + + The ENOSYS is for Solaris 8 NFS clients, which can + fail with errno == ENOSYS if mkdir is invoked on an + NFS mount point. */ + } + else + { + error (0, errno, _("cannot create directory %s"), quote (dir)); + retval = false; + break; + } /* If we were able to save the initial working directory, then we can use chdir to change into each directory before creating an entry in that directory. This avoids making - stat and mkdir process O(n^2) file name components. */ + mkdir process O(n^2) file name components. */ if (do_chdir && chdir (basename_dir) < 0) { error (0, errno, _("cannot chdir to directory %s"), quote (dir)); - CLEANUP; - return false; + retval = false; + break; } *slash++ = '/'; - /* Avoid unnecessary calls to `stat' when given + /* Avoid unnecessary calls to mkdir when given file names containing multiple adjacent slashes. */ while (*slash == '/') slash++; @@ -301,26 +259,40 @@ make_dir_parents (char const *arg, /* We're done making leading directories. Create the final component of the file name. */ - - if (! make_dir (basename_dir, dir, mode, NULL)) + if (retval) { - CLEANUP; - return false; + if (mkdir (basename_dir, mode) != 0) + { + error (0, errno, _("cannot create directory %s"), quote (dir)); + retval = false; + } + else + { + if (verbose_fmt_string) + error (0, 0, verbose_fmt_string, quote (dir)); + fixup_permissions_dir = basename_dir; + } } + } - if (verbose_fmt_string != NULL) - error (0, 0, verbose_fmt_string, quote (dir)); + if (fixup_permissions_dir) + { + /* chown must precede chmod because on some systems, + chown clears the set[ug]id bits for non-superusers, + resulting in incorrect permissions. + On System V, users can give away files with chown and then not + be able to chmod them. So don't give files away. */ if (owner != (uid_t) -1 || group != (gid_t) -1) { - if (chown (basename_dir, owner, group) + if (chown (fixup_permissions_dir, owner, group) != 0 #ifdef AFS && errno != EPERM #endif ) { error (0, errno, _("cannot change owner and/or group of %s"), - quote (dir)); + quote (full_dir)); retval = false; } } @@ -330,67 +302,35 @@ make_dir_parents (char const *arg, required to honor only the file permission bits. In particular, it need not honor the `special' bits, so if MODE includes any special bits, set them here. */ - if ((mode & ~S_IRWXUGO) - && chmod (basename_dir, mode)) + if ((mode & ~S_IRWXUGO) && chmod (fixup_permissions_dir, mode) != 0) { error (0, errno, _("cannot change permissions of %s"), - quote (dir)); + quote (full_dir)); retval = false; } - - CLEANUP_CWD; - - /* If the mode for leading directories didn't include owner "wx" - privileges, we have to reset their protections to the correct - value. */ - for (p = leading_dirs; p != NULL; p = p->next) - { - *(p->dirname_end) = '\0'; - if (chmod (dir, parent_mode) != 0) - { - error (0, errno, _("cannot change permissions of %s"), - quote (dir)); - retval = false; - } - } } - else - { - /* We get here if the file already exists. */ - - char const *dir = arg; - if (!S_ISDIR (stats.st_mode)) + if (do_chdir) + { + if (restore_cwd (&cwd) != 0) { - error (0, 0, _("%s exists but is not a directory"), quote (dir)); - return false; + *cwd_errno = errno; + cwd_problem = true; } + free_cwd (&cwd); + } - if (!preserve_existing) + /* If the mode for leading directories didn't include owner "wx" + privileges, reset their protections to the correct value. */ + for (; leading_dirs != NULL; leading_dirs = leading_dirs->next) + { + leading_dirs->dirname_end[0] = '\0'; + if ((cwd_problem && *full_dir != '/') + || chmod (full_dir, parent_mode) != 0) { - /* chown must precede chmod because on some systems, - chown clears the set[ug]id bits for non-superusers, - resulting in incorrect permissions. - On System V, users can give away files with chown and then not - be able to chmod them. So don't give files away. */ - - if ((owner != (uid_t) -1 || group != (gid_t) -1) - && chown (dir, owner, group) -#ifdef AFS - && errno != EPERM -#endif - ) - { - error (0, errno, _("cannot change owner and/or group of %s"), - quote (dir)); - retval = false; - } - if (chmod (dir, mode) != 0) - { - error (0, errno, _("cannot change permissions of %s"), - quote (dir)); - retval = false; - } + error (0, (cwd_problem ? 0 : errno), + _("cannot change permissions of %s"), quote (full_dir)); + retval = false; } }