X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmkdir-p.c;h=dd42b6ca556470421510186587b93ff5a3f2445c;hb=21fc432fb0f8fa8c373cd37c81fb6c7271615522;hp=efaa8d26594fb683a84711ea3ae4e43e55857396;hpb=222b0486b7db1b09293e05512873d633440efcb3;p=gnulib.git diff --git a/lib/mkdir-p.c b/lib/mkdir-p.c index efaa8d265..dd42b6ca5 100644 --- a/lib/mkdir-p.c +++ b/lib/mkdir-p.c @@ -1,6 +1,6 @@ /* mkdir-p.c -- Ensure that a directory and its parents exist. - Copyright (C) 1990, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005 + Copyright (C) 1990, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify @@ -30,9 +30,7 @@ #include #include #include -#if HAVE_UNISTD_H -# include -#endif +#include #include #include @@ -41,96 +39,15 @@ #include "gettext.h" #define _(msgid) gettext (msgid) -#include "save-cwd.h" +#include "chdir-safer.h" #include "dirname.h" #include "error.h" +#include "lchmod.h" +#include "lchown.h" #include "quote.h" +#include "save-cwd.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; - - return ok; -} - /* Ensure that the directory ARG exists. Create any leading directories that don't already exist, with @@ -145,8 +62,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 +78,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; @@ -180,14 +124,15 @@ make_dir_parents (char const *arg, mode_t oldmask = umask (0); /* Make a copy of ARG that we can scribble NULs on. */ - dir = (char *) alloca (strlen (arg) + 1); + dir = alloca (strlen (arg) + 1); strcpy (dir, arg); strip_trailing_slashes (dir); + full_dir = dir; - /* If leading directories shouldn't be writable or executable, + /* If leading directories shouldn't be readable, writable or executable, or should have set[ug]id or sticky bits set and we are setting their owners, we need to fix their permissions after making them. */ - if (((parent_mode & WX_USR) != WX_USR) + if (((parent_mode & S_IRWXU) != S_IRWXU) || ((owner != (uid_t) -1 || group != (gid_t) -1) && (parent_mode & (S_ISUID | S_ISGID | S_ISVTX)) != 0)) { @@ -213,7 +158,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,9 +170,10 @@ make_dir_parents (char const *arg, while (*slash == '/') slash++; - while (1) + while (true) { - bool newly_created_dir; + bool dir_known_to_exist; + int mkdir_errno; /* slash points to the leftmost unprocessed component of dir. */ basename_dir = slash; @@ -239,19 +188,16 @@ 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; - } + dir_known_to_exist = (mkdir (basename_dir, tmp_mode) == 0); + mkdir_errno = errno; - if (newly_created_dir) + if (dir_known_to_exist) { if (verbose_fmt_string) error (0, 0, verbose_fmt_string, quote (dir)); if ((owner != (uid_t) -1 || group != (gid_t) -1) - && chown (basename_dir, owner, group) + && lchown (basename_dir, owner, group) #if defined AFS && defined EPERM && errno != EPERM #endif @@ -259,14 +205,13 @@ 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 *new); + struct ptr_list *new = alloca (sizeof *new); new->dirname_end = slash; new->next = leading_dirs; leading_dirs = new; @@ -276,18 +221,41 @@ make_dir_parents (char const *arg, /* 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. */ - if (do_chdir && chdir (basename_dir) < 0) + mkdir process O(n^2) file name components. */ + if (do_chdir) + { + /* If we know that basename_dir is a directory (because we've + just created it), then ensure that when we change to it, + that final component is not a symlink. Otherwise, we must + accept the possibility that basename_dir is a preexisting + symlink-to-directory and chdir through the symlink. */ + if ((dir_known_to_exist + ? chdir_no_follow (basename_dir) + : chdir (basename_dir)) == 0) + dir_known_to_exist = true; + else if (dir_known_to_exist) + { + error (0, errno, _("cannot chdir to directory %s"), + quote (dir)); + retval = false; + break; + } + } + else if (!dir_known_to_exist) + dir_known_to_exist = (stat (basename_dir, &stats) == 0 + && S_ISDIR (stats.st_mode)); + + if (!dir_known_to_exist) { - error (0, errno, _("cannot chdir to directory %s"), + error (0, mkdir_errno, _("cannot create 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 +269,49 @@ 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; + bool dir_known_to_exist = (mkdir (basename_dir, mode) == 0); + int mkdir_errno = errno; + struct stat sbuf; + + if ( ! dir_known_to_exist) + dir_known_to_exist = (stat (basename_dir, &sbuf) == 0 + && S_ISDIR (sbuf.st_mode)); + + if ( ! dir_known_to_exist) + { + error (0, mkdir_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 (lchown (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 +321,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) && lchmod (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 != '/') + || lchmod (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; } }