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