1/* mkdir-p.c -- Ensure that a directory and its parents exist.
2
3   Copyright (C) 1990, 1997-2000, 2002-2007, 2009-2010 Free Software
4   Foundation, Inc.
5
6   This program is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 3 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19/* Written by Paul Eggert, David MacKenzie, and Jim Meyering.  */
20
21#include <config.h>
22
23#include "mkdir-p.h"
24
25#include <errno.h>
26#include <sys/stat.h>
27#include <unistd.h>
28
29#include "gettext.h"
30#define _(msgid) gettext (msgid)
31
32#include "dirchownmod.h"
33#include "dirname.h"
34#include "error.h"
35#include "quote.h"
36#include "mkancesdirs.h"
37#include "savewd.h"
38
39#ifndef HAVE_FCHMOD
40# define HAVE_FCHMOD false
41#endif
42
43/* Ensure that the directory DIR exists.
44
45   WD is the working directory, as in savewd.c.
46
47   If MAKE_ANCESTOR is not null, create any ancestor directories that
48   don't already exist, by invoking MAKE_ANCESTOR (DIR, ANCESTOR, OPTIONS).
49   This function should return zero if successful, -1 (setting errno)
50   otherwise.  In this case, DIR may be modified by storing '\0' bytes
51   into it, to access the ancestor directories, and this modification
52   is retained on return if the ancestor directories could not be
53   created.
54
55   Create DIR as a new directory with using mkdir with permissions
56   MODE.  It is also OK if MAKE_ANCESTOR is not null and a
57   directory DIR already exists.
58
59   Call ANNOUNCE (DIR, OPTIONS) just after successfully making DIR,
60   even if some of the following actions fail.
61
62   Set DIR's owner to OWNER and group to GROUP, but leave the owner
63   alone if OWNER is (uid_t) -1, and similarly for GROUP.
64
65   Set DIR's mode bits to MODE, except preserve any of the bits that
66   correspond to zero bits in MODE_BITS.  In other words, MODE_BITS is
67   a mask that specifies which of DIR's mode bits should be set or
68   cleared.  MODE should be a subset of MODE_BITS, which in turn
69   should be a subset of CHMOD_MODE_BITS.  Changing the mode in this
70   way is necessary if DIR already existed or if MODE and MODE_BITS
71   specify non-permissions bits like S_ISUID.
72
73   However, if PRESERVE_EXISTING is true and DIR already exists,
74   do not attempt to set DIR's ownership and file mode bits.
75
76   This implementation assumes the current umask is zero.
77
78   Return true if DIR exists as a directory with the proper ownership
79   and file mode bits when done, or if a child process has been
80   dispatched to do the real work (though the child process may not
81   have finished yet -- it is the caller's responsibility to handle
82   this).  Report a diagnostic and return false on failure, storing
83   '\0' into *DIR if an ancestor directory had problems.  */
84
85bool
86make_dir_parents (char *dir,
87                  struct savewd *wd,
88                  int (*make_ancestor) (char const *, char const *, void *),
89                  void *options,
90                  mode_t mode,
91                  void (*announce) (char const *, void *),
92                  mode_t mode_bits,
93                  uid_t owner,
94                  gid_t group,
95                  bool preserve_existing)
96{
97  int mkdir_errno = (IS_ABSOLUTE_FILE_NAME (dir) ? 0 : savewd_errno (wd));
98
99  if (mkdir_errno == 0)
100    {
101      ptrdiff_t prefix_len = 0;
102      int savewd_chdir_options = (HAVE_FCHMOD ? SAVEWD_CHDIR_SKIP_READABLE : 0);
103
104      if (make_ancestor)
105        {
106          prefix_len = mkancesdirs (dir, wd, make_ancestor, options);
107          if (prefix_len < 0)
108            {
109              if (prefix_len < -1)
110                return true;
111              mkdir_errno = errno;
112            }
113        }
114
115      if (0 <= prefix_len)
116        {
117          /* If the ownership might change, or if the directory will be
118             writeable to other users and its special mode bits may
119             change after the directory is created, create it with
120             more restrictive permissions at first, so unauthorized
121             users cannot nip in before the directory is ready.  */
122          bool keep_owner = owner == (uid_t) -1 && group == (gid_t) -1;
123          bool keep_special_mode_bits =
124            ((mode_bits & (S_ISUID | S_ISGID)) | (mode & S_ISVTX)) == 0;
125          mode_t mkdir_mode = mode;
126          if (! keep_owner)
127            mkdir_mode &= ~ (S_IRWXG | S_IRWXO);
128          else if (! keep_special_mode_bits)
129            mkdir_mode &= ~ (S_IWGRP | S_IWOTH);
130
131          if (mkdir (dir + prefix_len, mkdir_mode) == 0)
132            {
133              announce (dir, options);
134              preserve_existing = keep_owner & keep_special_mode_bits;
135              savewd_chdir_options |=
136                (SAVEWD_CHDIR_NOFOLLOW
137                 | (mode & S_IRUSR ? SAVEWD_CHDIR_READABLE : 0));
138            }
139          else
140            {
141              mkdir_errno = errno;
142              mkdir_mode = -1;
143            }
144
145          if (preserve_existing)
146            {
147              struct stat st;
148              if (mkdir_errno == 0
149                  || (mkdir_errno != ENOENT && make_ancestor
150                      && stat (dir + prefix_len, &st) == 0
151                      && S_ISDIR (st.st_mode)))
152                return true;
153            }
154          else
155            {
156              int open_result[2];
157              int chdir_result =
158                savewd_chdir (wd, dir + prefix_len,
159                              savewd_chdir_options, open_result);
160              if (chdir_result < -1)
161                return true;
162              else
163                {
164                  bool chdir_ok = (chdir_result == 0);
165                  int chdir_errno = errno;
166                  int fd = open_result[0];
167                  bool chdir_failed_unexpectedly =
168                    (mkdir_errno == 0
169                     && ((! chdir_ok && (mode & S_IXUSR))
170                         || (fd < 0 && (mode & S_IRUSR))));
171
172                  if (chdir_failed_unexpectedly)
173                    {
174                      /* No need to save errno here; it's irrelevant.  */
175                      if (0 <= fd)
176                        close (fd);
177                    }
178                  else
179                    {
180                      char const *subdir = (chdir_ok ? "." : dir + prefix_len);
181                      if (dirchownmod (fd, subdir, mkdir_mode, owner, group,
182                                       mode, mode_bits)
183                          == 0)
184                        return true;
185                    }
186
187                  if (mkdir_errno == 0
188                      || (mkdir_errno != ENOENT && make_ancestor
189                          && errno != ENOTDIR))
190                    {
191                      error (0,
192                             (! chdir_failed_unexpectedly ? errno
193                              : ! chdir_ok && (mode & S_IXUSR) ? chdir_errno
194                              : open_result[1]),
195                             _(keep_owner
196                               ? "cannot change permissions of %s"
197                               : "cannot change owner and permissions of %s"),
198                             quote (dir));
199                      return false;
200                    }
201                }
202            }
203        }
204    }
205
206  error (0, mkdir_errno, _("cannot create directory %s"), quote (dir));
207  return false;
208}
209