142 lines
4.9 KiB
C
142 lines
4.9 KiB
C
/* Make a file's ancestor directories.
|
|
|
|
Copyright (C) 2006, 2009-2020 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 "dirname.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;
|
|
}
|