diff options
Diffstat (limited to 'lib/mkancesdirs.c')
-rw-r--r-- | lib/mkancesdirs.c | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/lib/mkancesdirs.c b/lib/mkancesdirs.c new file mode 100644 index 0000000..b66a9d2 --- /dev/null +++ b/lib/mkancesdirs.c @@ -0,0 +1,141 @@ +/* Make a file's ancestor directories. + + Copyright (C) 2006, 2009-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Paul Eggert. */ + +#include <config.h> + +#include "mkancesdirs.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <errno.h> +#include <unistd.h> + +#include "filename.h" +#include "savewd.h" + +/* Ensure that the ancestor directories of FILE exist, using an + algorithm that should work even if two processes execute this + function in parallel. Modify FILE as necessary to access the + ancestor directories, but restore FILE to an equivalent value + if successful. + + WD points to the working directory, using the conventions of + savewd. + + Create any ancestor directories that don't already exist, by + invoking MAKE_DIR (FILE, COMPONENT, MAKE_DIR_ARG). This function + should return 0 if successful, -1 (setting errno) otherwise. If + COMPONENT is relative, it is relative to the temporary working + directory, which may differ from *WD. + + Ordinarily MAKE_DIR is executed with the working directory changed + to reflect the already-made prefix, and mkancesdirs returns with + the working directory changed a prefix of FILE. However, if the + initial working directory cannot be saved in a file descriptor, + MAKE_DIR is invoked in a subprocess and this function returns in + both the parent and child process, so the caller should not assume + any changed state survives other than the EXITMAX component of WD, + and the caller should take care that the parent does not attempt to + do the work that the child is doing. + + If successful and if this process can go ahead and create FILE, + return the length of the prefix of FILE that has already been made. + If successful so far but a child process is doing the actual work, + return -2. If unsuccessful, return -1 and set errno. */ + +ptrdiff_t +mkancesdirs (char *file, struct savewd *wd, + int (*make_dir) (char const *, char const *, void *), + void *make_dir_arg) +{ + /* Address of the previous directory separator that follows an + ordinary byte in a file name in the left-to-right scan, or NULL + if no such separator precedes the current location P. */ + char *sep = NULL; + + /* Address of the leftmost file name component that has not yet + been processed. */ + char *component = file; + + char *p = file + FILE_SYSTEM_PREFIX_LEN (file); + char c; + bool made_dir = false; + + /* Scan forward through FILE, creating and chdiring into directories + along the way. Try MAKE_DIR before chdir, so that the procedure + works even when two or more processes are executing it in + parallel. Isolate each file name component by having COMPONENT + point to its start and SEP point just after its end. */ + + while ((c = *p++)) + if (ISSLASH (*p)) + { + if (! ISSLASH (c)) + sep = p; + } + else if (ISSLASH (c) && *p && sep) + { + /* Don't bother to make or test for "." since it does not + affect the algorithm. */ + if (! (sep - component == 1 && component[0] == '.')) + { + int make_dir_errno = 0; + int savewd_chdir_options = 0; + int chdir_result; + + /* Temporarily modify FILE to isolate this file name + component. */ + *sep = '\0'; + + /* Invoke MAKE_DIR on this component, except don't bother + with ".." since it must exist if its "parent" does. */ + if (sep - component == 2 + && component[0] == '.' && component[1] == '.') + made_dir = false; + else if (make_dir (file, component, make_dir_arg) < 0) + make_dir_errno = errno; + else + made_dir = true; + + if (made_dir) + savewd_chdir_options |= SAVEWD_CHDIR_NOFOLLOW; + + chdir_result = + savewd_chdir (wd, component, savewd_chdir_options, NULL); + + /* Undo the temporary modification to FILE, unless there + was a failure. */ + if (chdir_result != -1) + *sep = '/'; + + if (chdir_result != 0) + { + if (make_dir_errno != 0 && errno == ENOENT) + errno = make_dir_errno; + return chdir_result; + } + } + + component = p; + } + + return component - file; +} |