merge with 1.12
[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
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu> and
19    Jim Meyering <meyering@cs.utexas.edu>.  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #ifdef __GNUC__
26 #define alloca __builtin_alloca
27 #else
28 #ifdef HAVE_ALLOCA_H
29 #include <alloca.h>
30 #else
31 #ifdef _AIX
32  #pragma alloca
33 #else
34 char *alloca ();
35 #endif
36 #endif
37 #endif
38
39 #include <stdio.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45
46 #ifdef STAT_MACROS_BROKEN
47 #undef S_ISDIR
48 #endif /* STAT_MACROS_BROKEN.  */
49
50 #if !defined(S_ISDIR) && defined(S_IFDIR)
51 #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
52 #endif
53
54 #ifdef STDC_HEADERS
55 #include <stdlib.h>
56 #endif
57
58 #ifdef HAVE_ERRNO_H
59 #include <errno.h>
60 #endif
61
62 #ifndef STDC_HEADERS
63 extern int errno;
64 #endif
65
66 #if defined(STDC_HEADERS) || defined(HAVE_STRING_H)
67 #include <string.h>
68 #ifndef index
69 #define index strchr
70 #endif
71 #else
72 #include <strings.h>
73 #endif
74
75 #ifdef __MSDOS__
76 typedef int uid_t;
77 typedef int gid_t;
78 #endif
79
80 #include "makepath.h"
81 #include "safe-stat.h"
82 void error ();
83
84 /* Ensure that the directory ARGPATH exists.
85    Remove any trailing slashes from ARGPATH before calling this function.
86
87    Create any leading directories that don't already exist, with
88    permissions PARENT_MODE.
89    If the last element of ARGPATH does not exist, create it as
90    a new directory with permissions MODE.
91    If OWNER and GROUP are non-negative, use them to set the UID and GID of
92    any created directories.
93    If VERBOSE_FMT_STRING is nonzero, use it as a printf format
94    string for printing a message after successfully making a directory,
95    with the name of the directory that was just made as an argument.
96    If PRESERVE_EXISTING is non-zero and ARGPATH is an existing directory,
97    then do not attempt to set its permissions and ownership.
98
99    Return 0 if ARGPATH exists as a directory with the proper
100    ownership and permissions when done, otherwise 1.  */
101
102 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
103 int
104 make_path (const char *argpath,
105            int mode,
106            int parent_mode,
107            uid_t owner,
108            gid_t group,
109            int preserve_existing,
110            const char *verbose_fmt_string)
111 #else
112 int
113 make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
114            verbose_fmt_string)
115      const char *argpath;
116      int mode;
117      int parent_mode;
118      uid_t owner;
119      gid_t group;
120      int preserve_existing;
121      const char *verbose_fmt_string;
122 #endif
123 {
124   char *dirpath;                /* A copy we can scribble NULs on.  */
125   struct stat stats;
126   int retval = 0;
127   int oldmask = umask (0);
128
129   /* FIXME: move this alloca and strcpy into the if-block.
130      Set dirpath to argpath in the else-block.  */
131   dirpath = (char *) alloca (strlen (argpath) + 1);
132   strcpy (dirpath, argpath);
133
134   if (SAFE_STAT (dirpath, &stats))
135     {
136       char *slash;
137       int tmp_mode;             /* Initial perms for leading dirs.  */
138       int re_protect;           /* Should leading dirs be unwritable? */
139       struct ptr_list
140       {
141         char *dirname_end;
142         struct ptr_list *next;
143       };
144       struct ptr_list *p, *leading_dirs = NULL;
145
146       /* If leading directories shouldn't be writable or executable,
147          or should have set[ug]id or sticky bits set and we are setting
148          their owners, we need to fix their permissions after making them.  */
149       if (((parent_mode & 0300) != 0300)
150           || (owner != (uid_t) -1 && group != (gid_t) -1
151               && (parent_mode & 07000) != 0))
152         {
153           tmp_mode = 0700;
154           re_protect = 1;
155         }
156       else
157         {
158           tmp_mode = parent_mode;
159           re_protect = 0;
160         }
161
162       slash = dirpath;
163       while (*slash == '/')
164         slash++;
165       while ((slash = index (slash, '/')))
166         {
167           *slash = '\0';
168           if (SAFE_STAT (dirpath, &stats))
169             {
170               if (mkdir (dirpath, tmp_mode))
171                 {
172                   error (0, errno, "cannot create directory `%s'", dirpath);
173                   umask (oldmask);
174                   return 1;
175                 }
176               else
177                 {
178                   if (verbose_fmt_string != NULL)
179                     error (0, 0, verbose_fmt_string, dirpath);
180
181                   if (owner != (uid_t) -1 && group != (gid_t) -1
182                       && chown (dirpath, owner, group)
183 #if defined(AFS) && defined (EPERM)
184                       && errno != EPERM
185 #endif
186                       )
187                     {
188                       error (0, errno, "%s", dirpath);
189                       retval = 1;
190                     }
191                   if (re_protect)
192                     {
193                       struct ptr_list *new = (struct ptr_list *)
194                         alloca (sizeof (struct ptr_list));
195                       new->dirname_end = slash;
196                       new->next = leading_dirs;
197                       leading_dirs = new;
198                     }
199                 }
200             }
201           else if (!S_ISDIR (stats.st_mode))
202             {
203               error (0, 0, "`%s' exists but is not a directory", dirpath);
204               umask (oldmask);
205               return 1;
206             }
207
208           *slash++ = '/';
209
210           /* Avoid unnecessary calls to `stat' when given
211              pathnames containing multiple adjacent slashes.  */
212           while (*slash == '/')
213             slash++;
214         }
215
216       /* We're done making leading directories.
217          Create the final component of the path.  */
218
219       /* The path could end in "/." or contain "/..", so test
220          if we really have to create the directory.  */
221
222       if (SAFE_STAT (dirpath, &stats) && mkdir (dirpath, mode))
223         {
224           error (0, errno, "cannot create directory `%s'", dirpath);
225           umask (oldmask);
226           return 1;
227         }
228       if (verbose_fmt_string != NULL)
229         error (0, 0, verbose_fmt_string, dirpath);
230
231       if (owner != (uid_t) -1 && group != (gid_t) -1)
232         {
233           if (chown (dirpath, owner, group)
234 #ifdef AFS
235               && errno != EPERM
236 #endif
237               )
238             {
239               error (0, errno, "%s", dirpath);
240               retval = 1;
241             }
242           /* chown may have turned off some permission bits we wanted.  */
243           if ((mode & 07000) != 0 && chmod (dirpath, mode))
244             {
245               error (0, errno, "%s", dirpath);
246               retval = 1;
247             }
248         }
249
250       /* If the mode for leading directories didn't include owner "wx"
251          privileges, we have to reset their protections to the correct
252          value.  */
253       for (p = leading_dirs; p != NULL; p = p->next)
254         {
255           *(p->dirname_end) = '\0';
256           if (chmod (dirpath, parent_mode))
257             {
258               error (0, errno, "%s", dirpath);
259               retval = 1;
260             }
261         }
262     }
263   else
264     {
265       /* We get here if the entire path already exists.  */
266
267       if (!S_ISDIR (stats.st_mode))
268         {
269           error (0, 0, "`%s' exists but is not a directory", dirpath);
270           umask (oldmask);
271           return 1;
272         }
273
274       if (!preserve_existing)
275         {
276           /* chown must precede chmod because on some systems,
277              chown clears the set[ug]id bits for non-superusers,
278              resulting in incorrect permissions.
279              On System V, users can give away files with chown and then not
280              be able to chmod them.  So don't give files away.  */
281
282           if (owner != (uid_t) -1 && group != (gid_t) -1
283               && chown (dirpath, owner, group)
284 #ifdef AFS
285               && errno != EPERM
286 #endif
287               )
288             {
289               error (0, errno, "%s", dirpath);
290               retval = 1;
291             }
292           if (chmod (dirpath, mode))
293             {
294               error (0, errno, "%s", dirpath);
295               retval = 1;
296             }
297         }
298     }
299
300   umask (oldmask);
301   return retval;
302 }