(make_path): Print message IFF the directory was
[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 #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 do_chdir;             /* Whether to chdir before each mkdir.  */
163       struct saved_cwd cwd;
164       char *basename_dir;
165       char *dirpath;
166
167       /* Temporarily relax umask in case it's overly restrictive.  */
168       int oldmask = umask (0);
169
170       /* Make a copy of ARGPATH that we can scribble NULs on.  */
171       dirpath = (char *) alloca (strlen (argpath) + 1);
172       strcpy (dirpath, argpath);
173       strip_trailing_slashes (dirpath);
174
175       /* If leading directories shouldn't be writable or executable,
176          or should have set[ug]id or sticky bits set and we are setting
177          their owners, we need to fix their permissions after making them.  */
178       if (((parent_mode & 0300) != 0300)
179           || (owner != (uid_t) -1 && group != (gid_t) -1
180               && (parent_mode & 07000) != 0))
181         {
182           tmp_mode = 0700;
183           re_protect = 1;
184         }
185       else
186         {
187           tmp_mode = parent_mode;
188           re_protect = 0;
189         }
190
191       /* If we can record the current working directory, we may be able
192          to do the chdir optimization.  */
193       do_chdir = !save_cwd (&cwd);
194
195       /* If we've saved the cwd and DIRPATH is an absolute pathname,
196          we must chdir to `/' in order to enable the chdir optimization.
197          So if chdir ("/") fails, turn off the optimization.  */
198       if (do_chdir && *dirpath == '/' && chdir ("/") < 0)
199         do_chdir = 0;
200
201       slash = dirpath;
202
203       /* Skip over leading slashes.  */
204       while (*slash == '/')
205         slash++;
206
207       while (1)
208         {
209           int newly_created_dir = 1;
210
211           /* slash points to the leftmost unprocessed component of dirpath.  */
212           basename_dir = slash;
213
214           slash = strchr (slash, '/');
215           if (slash == NULL)
216             break;
217
218           /* If we're *not* doing chdir before each mkdir, then we have to refer
219              to the target using the full (multi-component) directory name.  */
220           if (!do_chdir)
221             basename_dir = dirpath;
222
223           *slash = '\0';
224           if (mkdir (basename_dir, tmp_mode))
225             {
226               if (stat (basename_dir, &stats))
227                 {
228                   error (0, errno, "cannot create directory `%s'", dirpath);
229                   CLEANUP;
230                   return 1;
231                 }
232               else if (!S_ISDIR (stats.st_mode))
233                 {
234                   error (0, 0, "`%s' exists but is not a directory", dirpath);
235                   CLEANUP;
236                   return 1;
237                 }
238               else
239                 {
240                   /* DIRPATH already exists and is a directory. */
241                   newly_created_dir = 0;
242                 }
243             }
244
245           if (newly_created_dir && verbose_fmt_string != NULL)
246             error (0, 0, verbose_fmt_string, dirpath);
247
248           if (owner != (uid_t) -1 && group != (gid_t) -1
249               && chown (basename_dir, owner, group)
250 #if defined(AFS) && defined (EPERM)
251               && errno != EPERM
252 #endif
253               )
254             {
255               error (0, errno, "%s", dirpath);
256               CLEANUP;
257               return 1;
258             }
259
260           if (re_protect)
261             {
262               struct ptr_list *new = (struct ptr_list *)
263                 alloca (sizeof (struct ptr_list));
264               new->dirname_end = slash;
265               new->next = leading_dirs;
266               leading_dirs = new;
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 }