(root)/
coreutils-9.4/
lib/
mkancesdirs.c
       1  /* Make a file's ancestor directories.
       2  
       3     Copyright (C) 2006, 2009-2023 Free Software Foundation, Inc.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation, either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  /* Written by Paul Eggert.  */
      19  
      20  #include <config.h>
      21  
      22  #include "mkancesdirs.h"
      23  
      24  #include <sys/types.h>
      25  #include <sys/stat.h>
      26  #include <fcntl.h>
      27  
      28  #include <errno.h>
      29  #include <unistd.h>
      30  
      31  #include "filename.h"
      32  #include "savewd.h"
      33  
      34  /* Ensure that the ancestor directories of FILE exist, using an
      35     algorithm that should work even if two processes execute this
      36     function in parallel.  Modify FILE as necessary to access the
      37     ancestor directories, but restore FILE to an equivalent value
      38     if successful.
      39  
      40     WD points to the working directory, using the conventions of
      41     savewd.
      42  
      43     Create any ancestor directories that don't already exist, by
      44     invoking MAKE_DIR (FILE, COMPONENT, MAKE_DIR_ARG).  This function
      45     should return 0 if successful, -1 (setting errno) otherwise.  If
      46     COMPONENT is relative, it is relative to the temporary working
      47     directory, which may differ from *WD.
      48  
      49     Ordinarily MAKE_DIR is executed with the working directory changed
      50     to reflect the already-made prefix, and mkancesdirs returns with
      51     the working directory changed a prefix of FILE.  However, if the
      52     initial working directory cannot be saved in a file descriptor,
      53     MAKE_DIR is invoked in a subprocess and this function returns in
      54     both the parent and child process, so the caller should not assume
      55     any changed state survives other than the EXITMAX component of WD,
      56     and the caller should take care that the parent does not attempt to
      57     do the work that the child is doing.
      58  
      59     If successful and if this process can go ahead and create FILE,
      60     return the length of the prefix of FILE that has already been made.
      61     If successful so far but a child process is doing the actual work,
      62     return -2.  If unsuccessful, return -1 and set errno.  */
      63  
      64  ptrdiff_t
      65  mkancesdirs (char *file, struct savewd *wd,
      66               int (*make_dir) (char const *, char const *, void *),
      67               void *make_dir_arg)
      68  {
      69    /* Address of the previous directory separator that follows an
      70       ordinary byte in a file name in the left-to-right scan, or NULL
      71       if no such separator precedes the current location P.  */
      72    char *sep = NULL;
      73  
      74    /* Address of the leftmost file name component that has not yet
      75       been processed.  */
      76    char *component = file;
      77  
      78    char *p = file + FILE_SYSTEM_PREFIX_LEN (file);
      79    char c;
      80    bool made_dir = false;
      81  
      82    /* Scan forward through FILE, creating and chdiring into directories
      83       along the way.  Try MAKE_DIR before chdir, so that the procedure
      84       works even when two or more processes are executing it in
      85       parallel.  Isolate each file name component by having COMPONENT
      86       point to its start and SEP point just after its end.  */
      87  
      88    while ((c = *p++))
      89      if (ISSLASH (*p))
      90        {
      91          if (! ISSLASH (c))
      92            sep = p;
      93        }
      94      else if (ISSLASH (c) && *p && sep)
      95        {
      96          /* Don't bother to make or test for "." since it does not
      97             affect the algorithm.  */
      98          if (! (sep - component == 1 && component[0] == '.'))
      99            {
     100              int make_dir_errno = 0;
     101              int savewd_chdir_options = 0;
     102              int chdir_result;
     103  
     104              /* Temporarily modify FILE to isolate this file name
     105                 component.  */
     106              *sep = '\0';
     107  
     108              /* Invoke MAKE_DIR on this component, except don't bother
     109                 with ".." since it must exist if its "parent" does.  */
     110              if (sep - component == 2
     111                  && component[0] == '.' && component[1] == '.')
     112                made_dir = false;
     113              else if (make_dir (file, component, make_dir_arg) < 0)
     114                make_dir_errno = errno;
     115              else
     116                made_dir = true;
     117  
     118              if (made_dir)
     119                savewd_chdir_options |= SAVEWD_CHDIR_NOFOLLOW;
     120  
     121              chdir_result =
     122                savewd_chdir (wd, component, savewd_chdir_options, NULL);
     123  
     124              /* Undo the temporary modification to FILE, unless there
     125                 was a failure.  */
     126              if (chdir_result != -1)
     127                *sep = '/';
     128  
     129              if (chdir_result != 0)
     130                {
     131                  if (make_dir_errno != 0 && errno == ENOENT)
     132                    errno = make_dir_errno;
     133                  return chdir_result;
     134                }
     135            }
     136  
     137          component = p;
     138        }
     139  
     140    return component - file;
     141  }