Rewrite using save-cwd.c and chdir to remove quadratic component of complexity.
[gnulib.git] / lib / makepath.c
1 /* makepath.c -- Ensure that a directory path exists.
2    Copyright (C) 1990, 1997 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> and Jim Meyering.  */
19
20 #if HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #if __GNUC__
25 # define alloca __builtin_alloca
26 #else
27 # if HAVE_ALLOCA_H
28 #  include <alloca.h>
29 # else
30 #  ifdef _AIX
31  #  pragma alloca
32 #  else
33 char *alloca ();
34 #  endif
35 # endif
36 #endif
37
38 #include <stdio.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #if HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif
44
45 #if STAT_MACROS_BROKEN
46 # undef S_ISDIR
47 #endif
48
49 #if !defined(S_ISDIR) && defined(S_IFDIR)
50 # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
51 #endif
52
53 #if STDC_HEADERS
54 # include <stdlib.h>
55 #endif
56
57 #if HAVE_ERRNO_H
58 # include <errno.h>
59 #endif
60
61 #ifndef errno
62 extern int errno;
63 #endif
64
65 #if HAVE_STRING_H
66 # include <string.h>
67 #else
68 # include <strings.h>
69 # ifndef strchr
70 #  define strchr index
71 # endif
72 #endif
73
74 #ifdef __MSDOS__
75 typedef int uid_t;
76 typedef int gid_t;
77 #endif
78
79 #include "save-cwd.h"
80 #include "makepath.h"
81 #include "error.h"
82
83 void strip_trailing_slashes ();
84
85 #define CLEANUP_CWD                                     \
86   do                                                    \
87     {                                                   \
88       /* We're done operating on basename_dir.          \
89          Restore working directory.  */                 \
90       if (saved_cwd)                                    \
91         {                                               \
92           int fail = restore_cwd (&cwd, NULL, NULL);    \
93           free_cwd (&cwd);                              \
94           if (fail)                                     \
95             return 1;                                   \
96         }                                               \
97     }                                                   \
98   while (0)
99
100 #define CLEANUP                                         \
101   do                                                    \
102     {                                                   \
103       umask (oldmask);                                  \
104       CLEANUP_CWD;                                      \
105     }                                                   \
106   while (0)
107
108 /* Ensure that the directory ARGPATH exists.
109    Remove any trailing slashes from ARGPATH before calling this function.
110
111    Create any leading directories that don't already exist, with
112    permissions PARENT_MODE.
113    If the last element of ARGPATH does not exist, create it as
114    a new directory with permissions MODE.
115    If OWNER and GROUP are non-negative, use them to set the UID and GID of
116    any created directories.
117    If VERBOSE_FMT_STRING is nonzero, use it as a printf format
118    string for printing a message after successfully making a directory,
119    with the name of the directory that was just made as an argument.
120    If PRESERVE_EXISTING is non-zero and ARGPATH is an existing directory,
121    then do not attempt to set its permissions and ownership.
122
123    Return 0 if ARGPATH exists as a directory with the proper
124    ownership and permissions when done, otherwise 1.  */
125
126 #if __STDC__
127 int
128 make_path (const char *argpath,
129            int mode,
130            int parent_mode,
131            uid_t owner,
132            gid_t group,
133            int preserve_existing,
134            const char *verbose_fmt_string)
135 #else
136 int
137 make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
138            verbose_fmt_string)
139      const char *argpath;
140      int mode;
141      int parent_mode;
142      uid_t owner;
143      gid_t group;
144      int preserve_existing;
145      const char *verbose_fmt_string;
146 #endif
147 {
148   struct stat stats;
149   int retval = 0;
150
151   if (stat (argpath, &stats))
152     {
153       char *slash;
154       int tmp_mode;             /* Initial perms for leading dirs.  */
155       int re_protect;           /* Should leading dirs be unwritable? */
156       struct ptr_list
157       {
158         char *dirname_end;
159         struct ptr_list *next;
160       };
161       struct ptr_list *p, *leading_dirs = NULL;
162       int saved_cwd = 0;
163       struct saved_cwd cwd;
164       char *basename_dir;
165       int first_subdir = 1;
166       char *dirpath;
167
168       /* Temporarily relax umask in case it's overly restrictive.  */
169       int oldmask = umask (0);
170
171       /* Make a copy of ARGPATH that we can scribble NULs on.  */
172       dirpath = (char *) alloca (strlen (argpath) + 1);
173       strcpy (dirpath, argpath);
174       strip_trailing_slashes (dirpath);
175
176       /* If leading directories shouldn't be writable or executable,
177          or should have set[ug]id or sticky bits set and we are setting
178          their owners, we need to fix their permissions after making them.  */
179       if (((parent_mode & 0300) != 0300)
180           || (owner != (uid_t) -1 && group != (gid_t) -1
181               && (parent_mode & 07000) != 0))
182         {
183           tmp_mode = 0700;
184           re_protect = 1;
185         }
186       else
187         {
188           tmp_mode = parent_mode;
189           re_protect = 0;
190         }
191
192       slash = dirpath;
193
194       /* Skip over leading slashes.  */
195       while (*slash == '/')
196         slash++;
197
198       while (1)
199         {
200           /* slash points to the leftmost unprocessed component of dirpath.  */
201           basename_dir = slash;
202
203           slash = strchr (slash, '/');
204           if (slash == NULL)
205             break;
206
207           if (first_subdir)
208             {
209               first_subdir = 0;
210               saved_cwd = !save_cwd (&cwd);
211             }
212
213           if (!saved_cwd)
214             basename_dir = dirpath;
215
216           *slash = '\0';
217           if (stat (basename_dir, &stats))
218             {
219               if (mkdir (basename_dir, tmp_mode))
220                 {
221                   error (0, errno, "cannot create directory `%s'", dirpath);
222                   CLEANUP;
223                   return 1;
224                 }
225               else
226                 {
227                   if (verbose_fmt_string != NULL)
228                     error (0, 0, verbose_fmt_string, dirpath);
229
230                   if (owner != (uid_t) -1 && group != (gid_t) -1
231                       && chown (basename_dir, owner, group)
232 #if defined(AFS) && defined (EPERM)
233                       && errno != EPERM
234 #endif
235                       )
236                     {
237                       error (0, errno, "%s", dirpath);
238                       CLEANUP;
239                       return 1;
240                     }
241
242                   if (re_protect)
243                     {
244                       struct ptr_list *new = (struct ptr_list *)
245                         alloca (sizeof (struct ptr_list));
246                       new->dirname_end = slash;
247                       new->next = leading_dirs;
248                       leading_dirs = new;
249                     }
250                 }
251             }
252           else if (!S_ISDIR (stats.st_mode))
253             {
254               error (0, 0, "`%s' exists but is not a directory", dirpath);
255               CLEANUP;
256               return 1;
257             }
258
259           if (saved_cwd && chdir (basename_dir) < 0)
260             {
261               error (0, errno, "cannot chdir to directory, %s", dirpath);
262               CLEANUP;
263               return 1;
264             }
265
266           *slash++ = '/';
267
268           /* Avoid unnecessary calls to `stat' when given
269              pathnames containing multiple adjacent slashes.  */
270           while (*slash == '/')
271             slash++;
272         }
273
274       if (!saved_cwd)
275         basename_dir = dirpath;
276
277       /* We're done making leading directories.
278          Create the final component of the path.  */
279
280       /* The path could end in "/." or contain "/..", so test
281          if we really have to create the directory.  */
282
283       if (stat (basename_dir, &stats) && mkdir (basename_dir, mode))
284         {
285           error (0, errno, "cannot create directory `%s'", dirpath);
286           CLEANUP;
287           return 1;
288         }
289
290       /* Done creating directories.  Restore original umask.  */
291       umask (oldmask);
292
293       if (verbose_fmt_string != NULL)
294         error (0, 0, verbose_fmt_string, dirpath);
295
296       if (owner != (uid_t) -1 && group != (gid_t) -1)
297         {
298           if (chown (basename_dir, owner, group)
299 #ifdef AFS
300               && errno != EPERM
301 #endif
302               )
303             {
304               error (0, errno, "cannot chown %s", dirpath);
305               retval = 1;
306             }
307           /* chown may have turned off some permission bits we wanted.  */
308           if ((mode & 07000) != 0 && chmod (basename_dir, mode))
309             {
310               error (0, errno, "cannot chmod %s", dirpath);
311               retval = 1;
312             }
313         }
314
315       CLEANUP_CWD;
316
317       /* If the mode for leading directories didn't include owner "wx"
318          privileges, we have to reset their protections to the correct
319          value.  */
320       for (p = leading_dirs; p != NULL; p = p->next)
321         {
322           *(p->dirname_end) = '\0';
323           if (chmod (dirpath, parent_mode))
324             {
325               error (0, errno, "%s", dirpath);
326               retval = 1;
327             }
328         }
329     }
330   else
331     {
332       /* We get here if the entire path already exists.  */
333
334       const char *dirpath = argpath;
335
336       if (!S_ISDIR (stats.st_mode))
337         {
338           error (0, 0, "`%s' exists but is not a directory", dirpath);
339           return 1;
340         }
341
342       if (!preserve_existing)
343         {
344           /* chown must precede chmod because on some systems,
345              chown clears the set[ug]id bits for non-superusers,
346              resulting in incorrect permissions.
347              On System V, users can give away files with chown and then not
348              be able to chmod them.  So don't give files away.  */
349
350           if (owner != (uid_t) -1 && group != (gid_t) -1
351               && chown (dirpath, owner, group)
352 #ifdef AFS
353               && errno != EPERM
354 #endif
355               )
356             {
357               error (0, errno, "%s", dirpath);
358               retval = 1;
359             }
360           if (chmod (dirpath, mode))
361             {
362               error (0, errno, "%s", dirpath);
363               retval = 1;
364             }
365         }
366     }
367
368   return retval;
369 }