(make_path): Reformat 3 if-stmts to test `if (newly_created_dir)' only once.
[gnulib.git] / lib / makepath.c
1 /* makepath.c -- Ensure that a directory path exists.
2    Copyright (C) 1990, 1997, 1998 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 #ifndef S_IWUSR
75 # define S_IWUSR 0200
76 #endif
77
78 #ifndef S_IXUSR
79 # define S_IXUSR 0100
80 #endif
81
82 #define WX_USR (S_IWUSR | S_IXUSR)
83
84 #ifdef __MSDOS__
85 typedef int uid_t;
86 typedef int gid_t;
87 #endif
88
89 #include "save-cwd.h"
90 #include "makepath.h"
91 #include "error.h"
92
93 void strip_trailing_slashes ();
94
95 #define CLEANUP_CWD                                     \
96   do                                                    \
97     {                                                   \
98       /* We're done operating on basename_dir.          \
99          Restore working directory.  */                 \
100       if (do_chdir)                                     \
101         {                                               \
102           int fail = restore_cwd (&cwd, NULL, NULL);    \
103           free_cwd (&cwd);                              \
104           if (fail)                                     \
105             return 1;                                   \
106         }                                               \
107     }                                                   \
108   while (0)
109
110 #define CLEANUP                                         \
111   do                                                    \
112     {                                                   \
113       umask (oldmask);                                  \
114       CLEANUP_CWD;                                      \
115     }                                                   \
116   while (0)
117
118 /* Ensure that the directory ARGPATH exists.
119    Remove any trailing slashes from ARGPATH before calling this function.
120
121    Create any leading directories that don't already exist, with
122    permissions PARENT_MODE.
123    If the last element of ARGPATH does not exist, create it as
124    a new directory with permissions MODE.
125    If OWNER and GROUP are non-negative, use them to set the UID and GID of
126    any created directories.
127    If VERBOSE_FMT_STRING is nonzero, use it as a printf format
128    string for printing a message after successfully making a directory,
129    with the name of the directory that was just made as an argument.
130    If PRESERVE_EXISTING is non-zero and ARGPATH is an existing directory,
131    then do not attempt to set its permissions and ownership.
132
133    Return 0 if ARGPATH exists as a directory with the proper
134    ownership and permissions when done, otherwise 1.  */
135
136 int
137 make_path (const char *argpath,
138            int mode,
139            int parent_mode,
140            uid_t owner,
141            gid_t group,
142            int preserve_existing,
143            const char *verbose_fmt_string)
144 {
145   struct stat stats;
146   int retval = 0;
147
148   if (stat (argpath, &stats))
149     {
150       char *slash;
151       int tmp_mode;             /* Initial perms for leading dirs.  */
152       int re_protect;           /* Should leading dirs be unwritable? */
153       struct ptr_list
154       {
155         char *dirname_end;
156         struct ptr_list *next;
157       };
158       struct ptr_list *p, *leading_dirs = NULL;
159       int do_chdir;             /* Whether to chdir before each mkdir.  */
160       struct saved_cwd cwd;
161       char *basename_dir;
162       char *dirpath;
163
164       /* Temporarily relax umask in case it's overly restrictive.  */
165       int oldmask = umask (0);
166
167       /* Make a copy of ARGPATH that we can scribble NULs on.  */
168       dirpath = (char *) alloca (strlen (argpath) + 1);
169       strcpy (dirpath, argpath);
170       strip_trailing_slashes (dirpath);
171
172       /* If leading directories shouldn't be writable or executable,
173          or should have set[ug]id or sticky bits set and we are setting
174          their owners, we need to fix their permissions after making them.  */
175       if (((parent_mode & WX_USR) != WX_USR)
176           || ((owner != (uid_t) -1 || group != (gid_t) -1)
177               && (parent_mode & 07000) != 0))
178         {
179           tmp_mode = 0700;
180           re_protect = 1;
181         }
182       else
183         {
184           tmp_mode = parent_mode;
185           re_protect = 0;
186         }
187
188       /* If we can record the current working directory, we may be able
189          to do the chdir optimization.  */
190       do_chdir = !save_cwd (&cwd);
191
192       /* If we've saved the cwd and DIRPATH is an absolute pathname,
193          we must chdir to `/' in order to enable the chdir optimization.
194          So if chdir ("/") fails, turn off the optimization.  */
195       if (do_chdir && *dirpath == '/' && chdir ("/") < 0)
196         do_chdir = 0;
197
198       slash = dirpath;
199
200       /* Skip over leading slashes.  */
201       while (*slash == '/')
202         slash++;
203
204       while (1)
205         {
206           int newly_created_dir = 1;
207
208           /* slash points to the leftmost unprocessed component of dirpath.  */
209           basename_dir = slash;
210
211           slash = strchr (slash, '/');
212           if (slash == NULL)
213             break;
214
215           /* If we're *not* doing chdir before each mkdir, then we have to refer
216              to the target using the full (multi-component) directory name.  */
217           if (!do_chdir)
218             basename_dir = dirpath;
219
220           *slash = '\0';
221           if (mkdir (basename_dir, tmp_mode))
222             {
223               if (stat (basename_dir, &stats))
224                 {
225                   error (0, errno, "cannot create directory `%s'", dirpath);
226                   CLEANUP;
227                   return 1;
228                 }
229               else if (!S_ISDIR (stats.st_mode))
230                 {
231                   error (0, 0, "`%s' exists but is not a directory", dirpath);
232                   CLEANUP;
233                   return 1;
234                 }
235               else
236                 {
237                   /* DIRPATH already exists and is a directory. */
238                   newly_created_dir = 0;
239                 }
240             }
241
242           if (newly_created_dir)
243             {
244               if (verbose_fmt_string)
245                 fprintf (stderr, verbose_fmt_string, dirpath);
246
247               if ((owner != (uid_t) -1 || group != (gid_t) -1)
248                   && chown (basename_dir, owner, group)
249 #if defined(AFS) && defined (EPERM)
250                   && errno != EPERM
251 #endif
252                   )
253                 {
254                   error (0, errno, "%s", dirpath);
255                   CLEANUP;
256                   return 1;
257                 }
258
259               if (re_protect)
260                 {
261                   struct ptr_list *new = (struct ptr_list *)
262                     alloca (sizeof (struct ptr_list));
263                   new->dirname_end = slash;
264                   new->next = leading_dirs;
265                   leading_dirs = new;
266                 }
267             }
268
269           /* If we were able to save the initial working directory,
270              then we can use chdir to change into each directory before
271              creating an entry in that directory.  This avoids making
272              stat and mkdir process O(n^2) file name components.  */
273           if (do_chdir && chdir (basename_dir) < 0)
274             {
275               error (0, errno, "cannot chdir to directory, %s", dirpath);
276               CLEANUP;
277               return 1;
278             }
279
280           *slash++ = '/';
281
282           /* Avoid unnecessary calls to `stat' when given
283              pathnames containing multiple adjacent slashes.  */
284           while (*slash == '/')
285             slash++;
286         }
287
288       if (!do_chdir)
289         basename_dir = dirpath;
290
291       /* We're done making leading directories.
292          Create the final component of the path.  */
293
294       /* The path could end in "/." or contain "/..", so test
295          if we really have to create the directory.  */
296
297       if (stat (basename_dir, &stats) && mkdir (basename_dir, mode))
298         {
299           error (0, errno, "cannot create directory `%s'", dirpath);
300           CLEANUP;
301           return 1;
302         }
303
304       /* Done creating directories.  Restore original umask.  */
305       umask (oldmask);
306
307       if (verbose_fmt_string != NULL)
308         error (0, 0, verbose_fmt_string, dirpath);
309
310       if (owner != (uid_t) -1 && group != (gid_t) -1)
311         {
312           if (chown (basename_dir, owner, group)
313 #ifdef AFS
314               && errno != EPERM
315 #endif
316               )
317             {
318               error (0, errno, "cannot chown %s", dirpath);
319               retval = 1;
320             }
321           /* chown may have turned off some permission bits we wanted.  */
322           if ((mode & 07000) != 0 && chmod (basename_dir, mode))
323             {
324               error (0, errno, "cannot chmod %s", dirpath);
325               retval = 1;
326             }
327         }
328
329       CLEANUP_CWD;
330
331       /* If the mode for leading directories didn't include owner "wx"
332          privileges, we have to reset their protections to the correct
333          value.  */
334       for (p = leading_dirs; p != NULL; p = p->next)
335         {
336           *(p->dirname_end) = '\0';
337           if (chmod (dirpath, parent_mode))
338             {
339               error (0, errno, "%s", dirpath);
340               retval = 1;
341             }
342         }
343     }
344   else
345     {
346       /* We get here if the entire path already exists.  */
347
348       const char *dirpath = argpath;
349
350       if (!S_ISDIR (stats.st_mode))
351         {
352           error (0, 0, "`%s' exists but is not a directory", dirpath);
353           return 1;
354         }
355
356       if (!preserve_existing)
357         {
358           /* chown must precede chmod because on some systems,
359              chown clears the set[ug]id bits for non-superusers,
360              resulting in incorrect permissions.
361              On System V, users can give away files with chown and then not
362              be able to chmod them.  So don't give files away.  */
363
364           if ((owner != (uid_t) -1 || group != (gid_t) -1)
365               && chown (dirpath, owner, group)
366 #ifdef AFS
367               && errno != EPERM
368 #endif
369               )
370             {
371               error (0, errno, "%s", dirpath);
372               retval = 1;
373             }
374           if (chmod (dirpath, mode))
375             {
376               error (0, errno, "%s", dirpath);
377               retval = 1;
378             }
379         }
380     }
381
382   return retval;
383 }