9066874cd9197feb1f692045f66d72f5e76bfacb
[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 int
103 make_path (argpath, mode, parent_mode, owner, group, preserve_existing,
104            verbose_fmt_string)
105      const char *argpath;
106      int mode;
107      int parent_mode;
108      uid_t owner;
109      gid_t group;
110      int preserve_existing;
111      const char *verbose_fmt_string;
112 {
113   char *dirpath;                /* A copy we can scribble NULs on.  */
114   struct stat stats;
115   int retval = 0;
116   int oldmask = umask (0);
117
118   /* FIXME: move this alloca and strcpy into the if-block.
119      Set dirpath to argpath in the else-block.  */
120   dirpath = (char *) alloca (strlen (argpath) + 1);
121   strcpy (dirpath, argpath);
122
123   if (SAFE_STAT (dirpath, &stats))
124     {
125       char *slash;
126       int tmp_mode;             /* Initial perms for leading dirs.  */
127       int re_protect;           /* Should leading dirs be unwritable? */
128       struct ptr_list
129       {
130         char *dirname_end;
131         struct ptr_list *next;
132       };
133       struct ptr_list *p, *leading_dirs = NULL;
134
135       /* If leading directories shouldn't be writable or executable,
136          or should have set[ug]id or sticky bits set and we are setting
137          their owners, we need to fix their permissions after making them.  */
138       if (((parent_mode & 0300) != 0300)
139           || (owner != (uid_t) -1 && group != (gid_t) -1
140               && (parent_mode & 07000) != 0))
141         {
142           tmp_mode = 0700;
143           re_protect = 1;
144         }
145       else
146         {
147           tmp_mode = parent_mode;
148           re_protect = 0;
149         }
150
151       slash = dirpath;
152       while (*slash == '/')
153         slash++;
154       while ((slash = index (slash, '/')))
155         {
156           *slash = '\0';
157           if (SAFE_STAT (dirpath, &stats))
158             {
159               if (mkdir (dirpath, tmp_mode))
160                 {
161                   error (0, errno, "cannot create directory `%s'", dirpath);
162                   umask (oldmask);
163                   return 1;
164                 }
165               else
166                 {
167                   if (verbose_fmt_string != NULL)
168                     error (0, 0, verbose_fmt_string, dirpath);
169
170                   if (owner != (uid_t) -1 && group != (gid_t) -1
171                       && chown (dirpath, owner, group)
172 #if defined(AFS) && defined (EPERM)
173                       && errno != EPERM
174 #endif
175                       )
176                     {
177                       error (0, errno, "%s", dirpath);
178                       retval = 1;
179                     }
180                   if (re_protect)
181                     {
182                       struct ptr_list *new = (struct ptr_list *)
183                         alloca (sizeof (struct ptr_list));
184                       new->dirname_end = slash;
185                       new->next = leading_dirs;
186                       leading_dirs = new;
187                     }
188                 }
189             }
190           else if (!S_ISDIR (stats.st_mode))
191             {
192               error (0, 0, "`%s' exists but is not a directory", dirpath);
193               umask (oldmask);
194               return 1;
195             }
196
197           *slash++ = '/';
198
199           /* Avoid unnecessary calls to `stat' when given
200              pathnames containing multiple adjacent slashes.  */
201           while (*slash == '/')
202             slash++;
203         }
204
205       /* We're done making leading directories.
206          Create the final component of the path.  */
207
208       /* The path could end in "/." or contain "/..", so test
209          if we really have to create the directory.  */
210
211       if (SAFE_STAT (dirpath, &stats) && mkdir (dirpath, mode))
212         {
213           error (0, errno, "cannot create directory `%s'", dirpath);
214           umask (oldmask);
215           return 1;
216         }
217       if (verbose_fmt_string != NULL)
218         error (0, 0, verbose_fmt_string, dirpath);
219
220       if (owner != (uid_t) -1 && group != (gid_t) -1)
221         {
222           if (chown (dirpath, owner, group)
223 #ifdef AFS
224               && errno != EPERM
225 #endif
226               )
227             {
228               error (0, errno, "%s", dirpath);
229               retval = 1;
230             }
231           /* chown may have turned off some permission bits we wanted.  */
232           if ((mode & 07000) != 0 && chmod (dirpath, mode))
233             {
234               error (0, errno, "%s", dirpath);
235               retval = 1;
236             }
237         }
238
239       /* If the mode for leading directories didn't include owner "wx"
240          privileges, we have to reset their protections to the correct
241          value.  */
242       for (p = leading_dirs; p != NULL; p = p->next)
243         {
244           *(p->dirname_end) = '\0';
245           if (chmod (dirpath, parent_mode))
246             {
247               error (0, errno, "%s", dirpath);
248               retval = 1;
249             }
250         }
251     }
252   else
253     {
254       /* We get here if the entire path already exists.  */
255
256       if (!S_ISDIR (stats.st_mode))
257         {
258           error (0, 0, "`%s' exists but is not a directory", dirpath);
259           umask (oldmask);
260           return 1;
261         }
262
263       if (!preserve_existing)
264         {
265           /* chown must precede chmod because on some systems,
266              chown clears the set[ug]id bits for non-superusers,
267              resulting in incorrect permissions.
268              On System V, users can give away files with chown and then not
269              be able to chmod them.  So don't give files away.  */
270
271           if (owner != (uid_t) -1 && group != (gid_t) -1
272               && chown (dirpath, owner, group)
273 #ifdef AFS
274               && errno != EPERM
275 #endif
276               )
277             {
278               error (0, errno, "%s", dirpath);
279               retval = 1;
280             }
281           if (chmod (dirpath, mode))
282             {
283               error (0, errno, "%s", dirpath);
284               retval = 1;
285             }
286         }
287     }
288
289   umask (oldmask);
290   return retval;
291 }