(make_path) [!__STDC__]: Remove K&R-style definition.
[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 (do_chdir)                                     \
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 int
127 make_path (const char *argpath,
128            int mode,
129            int parent_mode,
130            uid_t owner,
131            gid_t group,
132            int preserve_existing,
133            const char *verbose_fmt_string)
134 {
135   struct stat stats;
136   int retval = 0;
137
138   if (stat (argpath, &stats))
139     {
140       char *slash;
141       int tmp_mode;             /* Initial perms for leading dirs.  */
142       int re_protect;           /* Should leading dirs be unwritable? */
143       struct ptr_list
144       {
145         char *dirname_end;
146         struct ptr_list *next;
147       };
148       struct ptr_list *p, *leading_dirs = NULL;
149       int do_chdir;             /* Whether to chdir before each mkdir.  */
150       struct saved_cwd cwd;
151       char *basename_dir;
152       char *dirpath;
153
154       /* Temporarily relax umask in case it's overly restrictive.  */
155       int oldmask = umask (0);
156
157       /* Make a copy of ARGPATH that we can scribble NULs on.  */
158       dirpath = (char *) alloca (strlen (argpath) + 1);
159       strcpy (dirpath, argpath);
160       strip_trailing_slashes (dirpath);
161
162       /* If leading directories shouldn't be writable or executable,
163          or should have set[ug]id or sticky bits set and we are setting
164          their owners, we need to fix their permissions after making them.  */
165       if (((parent_mode & 0300) != 0300)
166           || (owner != (uid_t) -1 && group != (gid_t) -1
167               && (parent_mode & 07000) != 0))
168         {
169           tmp_mode = 0700;
170           re_protect = 1;
171         }
172       else
173         {
174           tmp_mode = parent_mode;
175           re_protect = 0;
176         }
177
178       /* If we can record the current working directory, we may be able
179          to do the chdir optimization.  */
180       do_chdir = !save_cwd (&cwd);
181
182       /* If we've saved the cwd and DIRPATH is an absolute pathname,
183          we must chdir to `/' in order to enable the chdir optimization.
184          So if chdir ("/") fails, turn off the optimization.  */
185       if (do_chdir && *dirpath == '/' && chdir ("/") < 0)
186         do_chdir = 0;
187
188       slash = dirpath;
189
190       /* Skip over leading slashes.  */
191       while (*slash == '/')
192         slash++;
193
194       while (1)
195         {
196           int newly_created_dir = 1;
197
198           /* slash points to the leftmost unprocessed component of dirpath.  */
199           basename_dir = slash;
200
201           slash = strchr (slash, '/');
202           if (slash == NULL)
203             break;
204
205           /* If we're *not* doing chdir before each mkdir, then we have to refer
206              to the target using the full (multi-component) directory name.  */
207           if (!do_chdir)
208             basename_dir = dirpath;
209
210           *slash = '\0';
211           if (mkdir (basename_dir, tmp_mode))
212             {
213               if (stat (basename_dir, &stats))
214                 {
215                   error (0, errno, "cannot create directory `%s'", dirpath);
216                   CLEANUP;
217                   return 1;
218                 }
219               else if (!S_ISDIR (stats.st_mode))
220                 {
221                   error (0, 0, "`%s' exists but is not a directory", dirpath);
222                   CLEANUP;
223                   return 1;
224                 }
225               else
226                 {
227                   /* DIRPATH already exists and is a directory. */
228                   newly_created_dir = 0;
229                 }
230             }
231
232           if (newly_created_dir && verbose_fmt_string != NULL)
233             fprintf (stderr, verbose_fmt_string, dirpath);
234
235           if (owner != (uid_t) -1 && group != (gid_t) -1
236               && chown (basename_dir, owner, group)
237 #if defined(AFS) && defined (EPERM)
238               && errno != EPERM
239 #endif
240               )
241             {
242               error (0, errno, "%s", dirpath);
243               CLEANUP;
244               return 1;
245             }
246
247           if (re_protect)
248             {
249               struct ptr_list *new = (struct ptr_list *)
250                 alloca (sizeof (struct ptr_list));
251               new->dirname_end = slash;
252               new->next = leading_dirs;
253               leading_dirs = new;
254             }
255
256           /* If we were able to save the initial working directory,
257              then we can use chdir to change into each directory before
258              creating an entry in that directory.  This avoids making
259              stat and mkdir process O(n^2) file name components.  */
260           if (do_chdir && chdir (basename_dir) < 0)
261             {
262               error (0, errno, "cannot chdir to directory, %s", dirpath);
263               CLEANUP;
264               return 1;
265             }
266
267           *slash++ = '/';
268
269           /* Avoid unnecessary calls to `stat' when given
270              pathnames containing multiple adjacent slashes.  */
271           while (*slash == '/')
272             slash++;
273         }
274
275       if (!do_chdir)
276         basename_dir = dirpath;
277
278       /* We're done making leading directories.
279          Create the final component of the path.  */
280
281       /* The path could end in "/." or contain "/..", so test
282          if we really have to create the directory.  */
283
284       if (stat (basename_dir, &stats) && mkdir (basename_dir, mode))
285         {
286           error (0, errno, "cannot create directory `%s'", dirpath);
287           CLEANUP;
288           return 1;
289         }
290
291       /* Done creating directories.  Restore original umask.  */
292       umask (oldmask);
293
294       if (verbose_fmt_string != NULL)
295         error (0, 0, verbose_fmt_string, dirpath);
296
297       if (owner != (uid_t) -1 && group != (gid_t) -1)
298         {
299           if (chown (basename_dir, owner, group)
300 #ifdef AFS
301               && errno != EPERM
302 #endif
303               )
304             {
305               error (0, errno, "cannot chown %s", dirpath);
306               retval = 1;
307             }
308           /* chown may have turned off some permission bits we wanted.  */
309           if ((mode & 07000) != 0 && chmod (basename_dir, mode))
310             {
311               error (0, errno, "cannot chmod %s", dirpath);
312               retval = 1;
313             }
314         }
315
316       CLEANUP_CWD;
317
318       /* If the mode for leading directories didn't include owner "wx"
319          privileges, we have to reset their protections to the correct
320          value.  */
321       for (p = leading_dirs; p != NULL; p = p->next)
322         {
323           *(p->dirname_end) = '\0';
324           if (chmod (dirpath, parent_mode))
325             {
326               error (0, errno, "%s", dirpath);
327               retval = 1;
328             }
329         }
330     }
331   else
332     {
333       /* We get here if the entire path already exists.  */
334
335       const char *dirpath = argpath;
336
337       if (!S_ISDIR (stats.st_mode))
338         {
339           error (0, 0, "`%s' exists but is not a directory", dirpath);
340           return 1;
341         }
342
343       if (!preserve_existing)
344         {
345           /* chown must precede chmod because on some systems,
346              chown clears the set[ug]id bits for non-superusers,
347              resulting in incorrect permissions.
348              On System V, users can give away files with chown and then not
349              be able to chmod them.  So don't give files away.  */
350
351           if (owner != (uid_t) -1 && group != (gid_t) -1
352               && chown (dirpath, owner, group)
353 #ifdef AFS
354               && errno != EPERM
355 #endif
356               )
357             {
358               error (0, errno, "%s", dirpath);
359               retval = 1;
360             }
361           if (chmod (dirpath, mode))
362             {
363               error (0, errno, "%s", dirpath);
364               retval = 1;
365             }
366         }
367     }
368
369   return retval;
370 }