/* 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;
}
