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