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