libitize
[gnulib.git] / lib / makepath.c
1 /* makepath.c -- Ensure that a directory path exists.
2    Copyright (C) 1990 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 "makepath.h"
80
81 void error ();
82
83 /* Ensure that the directory ARGPATH exists.
84    Remove any trailing slashes from ARGPATH before calling this function.
85
86    Create any leading directories that don't already exist, with
87    permissions PARENT_MODE.
88    If the last element of ARGPATH does not exist, create it as
89    a new directory with permissions MODE.
90    If OWNER and GROUP are non-negative, use them to set the UID and GID of
91    any created directories.
92    If VERBOSE_FMT_STRING is nonzero, use it as a printf format
93    string for printing a message after successfully making a directory,
94    with the name of the directory that was just made as an argument.
95    If PRESERVE_EXISTING is non-zero and ARGPATH is an existing directory,
96    then do not attempt to set its permissions and ownership.
97
98    Return 0 if ARGPATH exists as a directory with the proper
99    ownership and permissions when done, otherwise 1.  */
100
101 #if __STDC__
102 int
103 make_path (const char *argpath,
104            int mode,
105            int parent_mode,
106            uid_t owner,
107            gid_t group,
108            int preserve_existing,
109            const char *verbose_fmt_string)
110 #else
111 int
112 make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
113            verbose_fmt_string)
114      const char *argpath;
115      int mode;
116      int parent_mode;
117      uid_t owner;
118      gid_t group;
119      int preserve_existing;
120      const char *verbose_fmt_string;
121 #endif
122 {
123   char *dirpath;                /* A copy we can scribble NULs on.  */
124   struct stat stats;
125   int retval = 0;
126   int oldmask = umask (0);
127
128   /* FIXME: move this alloca and strcpy into the if-block.
129      Set dirpath to argpath in the else-block.  */
130   dirpath = (char *) alloca (strlen (argpath) + 1);
131   strcpy (dirpath, argpath);
132
133   if (stat (dirpath, &stats))
134     {
135       char *slash;
136       int tmp_mode;             /* Initial perms for leading dirs.  */
137       int re_protect;           /* Should leading dirs be unwritable? */
138       struct ptr_list
139       {
140         char *dirname_end;
141         struct ptr_list *next;
142       };
143       struct ptr_list *p, *leading_dirs = NULL;
144
145       /* If leading directories shouldn't be writable or executable,
146          or should have set[ug]id or sticky bits set and we are setting
147          their owners, we need to fix their permissions after making them.  */
148       if (((parent_mode & 0300) != 0300)
149           || (owner != (uid_t) -1 && group != (gid_t) -1
150               && (parent_mode & 07000) != 0))
151         {
152           tmp_mode = 0700;
153           re_protect = 1;
154         }
155       else
156         {
157           tmp_mode = parent_mode;
158           re_protect = 0;
159         }
160
161       slash = dirpath;
162       while (*slash == '/')
163         slash++;
164       while ((slash = strchr (slash, '/')))
165         {
166           *slash = '\0';
167           if (stat (dirpath, &stats))
168             {
169               if (mkdir (dirpath, tmp_mode))
170                 {
171                   error (0, errno, "cannot create directory `%s'", dirpath);
172                   umask (oldmask);
173                   return 1;
174                 }
175               else
176                 {
177                   if (verbose_fmt_string != NULL)
178                     error (0, 0, verbose_fmt_string, dirpath);
179
180                   if (owner != (uid_t) -1 && group != (gid_t) -1
181                       && chown (dirpath, owner, group)
182 #if defined(AFS) && defined (EPERM)
183                       && errno != EPERM
184 #endif
185                       )
186                     {
187                       error (0, errno, "%s", dirpath);
188                       retval = 1;
189                     }
190                   if (re_protect)
191                     {
192                       struct ptr_list *new = (struct ptr_list *)
193                         alloca (sizeof (struct ptr_list));
194                       new->dirname_end = slash;
195                       new->next = leading_dirs;
196                       leading_dirs = new;
197                     }
198                 }
199             }
200           else if (!S_ISDIR (stats.st_mode))
201             {
202               error (0, 0, "`%s' exists but is not a directory", dirpath);
203               umask (oldmask);
204               return 1;
205             }
206
207           *slash++ = '/';
208
209           /* Avoid unnecessary calls to `stat' when given
210              pathnames containing multiple adjacent slashes.  */
211           while (*slash == '/')
212             slash++;
213         }
214
215       /* We're done making leading directories.
216          Create the final component of the path.  */
217
218       /* The path could end in "/." or contain "/..", so test
219          if we really have to create the directory.  */
220
221       if (stat (dirpath, &stats) && mkdir (dirpath, mode))
222         {
223           error (0, errno, "cannot create directory `%s'", dirpath);
224           umask (oldmask);
225           return 1;
226         }
227       if (verbose_fmt_string != NULL)
228         error (0, 0, verbose_fmt_string, dirpath);
229
230       if (owner != (uid_t) -1 && group != (gid_t) -1)
231         {
232           if (chown (dirpath, owner, group)
233 #ifdef AFS
234               && errno != EPERM
235 #endif
236               )
237             {
238               error (0, errno, "%s", dirpath);
239               retval = 1;
240             }
241           /* chown may have turned off some permission bits we wanted.  */
242           if ((mode & 07000) != 0 && chmod (dirpath, mode))
243             {
244               error (0, errno, "%s", dirpath);
245               retval = 1;
246             }
247         }
248
249       /* If the mode for leading directories didn't include owner "wx"
250          privileges, we have to reset their protections to the correct
251          value.  */
252       for (p = leading_dirs; p != NULL; p = p->next)
253         {
254           *(p->dirname_end) = '\0';
255           if (chmod (dirpath, parent_mode))
256             {
257               error (0, errno, "%s", dirpath);
258               retval = 1;
259             }
260         }
261     }
262   else
263     {
264       /* We get here if the entire path already exists.  */
265
266       if (!S_ISDIR (stats.st_mode))
267         {
268           error (0, 0, "`%s' exists but is not a directory", dirpath);
269           umask (oldmask);
270           return 1;
271         }
272
273       if (!preserve_existing)
274         {
275           /* chown must precede chmod because on some systems,
276              chown clears the set[ug]id bits for non-superusers,
277              resulting in incorrect permissions.
278              On System V, users can give away files with chown and then not
279              be able to chmod them.  So don't give files away.  */
280
281           if (owner != (uid_t) -1 && group != (gid_t) -1
282               && chown (dirpath, owner, group)
283 #ifdef AFS
284               && errno != EPERM
285 #endif
286               )
287             {
288               error (0, errno, "%s", dirpath);
289               retval = 1;
290             }
291           if (chmod (dirpath, mode))
292             {
293               error (0, errno, "%s", dirpath);
294               retval = 1;
295             }
296         }
297     }
298
299   umask (oldmask);
300   return retval;
301 }