Add my comment from ChangeLog entry for gkm's change.
[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           /* The mkdir and stat calls below appear to be reversed.
221              They are not.  It is important to call mkdir first and then to
222              call stat (to distinguish the three cases) only if mkdir fails.
223              The alternative to this approach is to `stat' each directory,
224              then to call mkdir if it doesn't exist.  But if some other process
225              were to create the directory between the stat & mkdir, the mkdir
226              would fail with EEXIST.  */
227
228           *slash = '\0';
229           if (mkdir (basename_dir, tmp_mode))
230             {
231               if (stat (basename_dir, &stats))
232                 {
233                   error (0, errno, "cannot create directory `%s'", dirpath);
234                   CLEANUP;
235                   return 1;
236                 }
237               else if (!S_ISDIR (stats.st_mode))
238                 {
239                   error (0, 0, "`%s' exists but is not a directory", dirpath);
240                   CLEANUP;
241                   return 1;
242                 }
243               else
244                 {
245                   /* DIRPATH already exists and is a directory. */
246                   newly_created_dir = 0;
247                 }
248             }
249
250           if (newly_created_dir)
251             {
252               if (verbose_fmt_string)
253                 fprintf (stderr, verbose_fmt_string, dirpath);
254
255               if ((owner != (uid_t) -1 || group != (gid_t) -1)
256                   && chown (basename_dir, owner, group)
257 #if defined(AFS) && defined (EPERM)
258                   && errno != EPERM
259 #endif
260                   )
261                 {
262                   error (0, errno, "%s", dirpath);
263                   CLEANUP;
264                   return 1;
265                 }
266
267               if (re_protect)
268                 {
269                   struct ptr_list *new = (struct ptr_list *)
270                     alloca (sizeof (struct ptr_list));
271                   new->dirname_end = slash;
272                   new->next = leading_dirs;
273                   leading_dirs = new;
274                 }
275             }
276
277           /* If we were able to save the initial working directory,
278              then we can use chdir to change into each directory before
279              creating an entry in that directory.  This avoids making
280              stat and mkdir process O(n^2) file name components.  */
281           if (do_chdir && chdir (basename_dir) < 0)
282             {
283               error (0, errno, "cannot chdir to directory, %s", dirpath);
284               CLEANUP;
285               return 1;
286             }
287
288           *slash++ = '/';
289
290           /* Avoid unnecessary calls to `stat' when given
291              pathnames containing multiple adjacent slashes.  */
292           while (*slash == '/')
293             slash++;
294         }
295
296       if (!do_chdir)
297         basename_dir = dirpath;
298
299       /* We're done making leading directories.
300          Create the final component of the path.  */
301
302       /* The path could end in "/." or contain "/..", so test
303          if we really have to create the directory.  */
304
305       if (stat (basename_dir, &stats) && mkdir (basename_dir, mode))
306         {
307           error (0, errno, "cannot create directory `%s'", dirpath);
308           CLEANUP;
309           return 1;
310         }
311
312       /* Done creating directories.  Restore original umask.  */
313       umask (oldmask);
314
315       if (verbose_fmt_string != NULL)
316         error (0, 0, verbose_fmt_string, dirpath);
317
318       if (owner != (uid_t) -1 && group != (gid_t) -1)
319         {
320           if (chown (basename_dir, owner, group)
321 #ifdef AFS
322               && errno != EPERM
323 #endif
324               )
325             {
326               error (0, errno, "cannot chown %s", dirpath);
327               retval = 1;
328             }
329           /* chown may have turned off some permission bits we wanted.  */
330           if ((mode & 07000) != 0 && chmod (basename_dir, mode))
331             {
332               error (0, errno, "cannot chmod %s", dirpath);
333               retval = 1;
334             }
335         }
336
337       CLEANUP_CWD;
338
339       /* If the mode for leading directories didn't include owner "wx"
340          privileges, we have to reset their protections to the correct
341          value.  */
342       for (p = leading_dirs; p != NULL; p = p->next)
343         {
344           *(p->dirname_end) = '\0';
345           if (chmod (dirpath, parent_mode))
346             {
347               error (0, errno, "%s", dirpath);
348               retval = 1;
349             }
350         }
351     }
352   else
353     {
354       /* We get here if the entire path already exists.  */
355
356       const char *dirpath = argpath;
357
358       if (!S_ISDIR (stats.st_mode))
359         {
360           error (0, 0, "`%s' exists but is not a directory", dirpath);
361           return 1;
362         }
363
364       if (!preserve_existing)
365         {
366           /* chown must precede chmod because on some systems,
367              chown clears the set[ug]id bits for non-superusers,
368              resulting in incorrect permissions.
369              On System V, users can give away files with chown and then not
370              be able to chmod them.  So don't give files away.  */
371
372           if ((owner != (uid_t) -1 || group != (gid_t) -1)
373               && chown (dirpath, owner, group)
374 #ifdef AFS
375               && errno != EPERM
376 #endif
377               )
378             {
379               error (0, errno, "%s", dirpath);
380               retval = 1;
381             }
382           if (chmod (dirpath, mode))
383             {
384               error (0, errno, "%s", dirpath);
385               retval = 1;
386             }
387         }
388     }
389
390   return retval;
391 }