4d647b5ef3160a5bcdd1cf8c80bdd7b51a1b3f34
[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       /* The path could end in "/." or contain "/..", so test
210          if we really have to create the directory.  */
211
212       if (stat (dirpath, &stats) && mkdir (dirpath, mode))
213         {
214           error (0, errno, "cannot make directory `%s'", dirpath);
215           umask (oldmask);
216           return 1;
217         }
218       if (verbose_fmt_string != NULL)
219         error (0, 0, verbose_fmt_string, dirpath);
220
221       if (owner != (uid_t) -1 && group != (gid_t) -1)
222         {
223           if (chown (dirpath, owner, group)
224 #ifdef AFS
225               && errno != EPERM
226 #endif
227               )
228             {
229               error (0, errno, "%s", dirpath);
230               retval = 1;
231             }
232           /* chown may have turned off some permission bits we wanted.  */
233           if ((mode & 07000) != 0 && chmod (dirpath, mode))
234             {
235               error (0, errno, "%s", dirpath);
236               retval = 1;
237             }
238         }
239
240       /* If the mode for leading directories didn't include owner "wx"
241          privileges, we have to reset their protections to the correct
242          value.  */
243       for (p = leading_dirs; p != NULL; p = p->next)
244         {
245           *(p->dirname_end) = '\0';
246           if (chmod (dirpath, parent_mode))
247             {
248               error (0, errno, "%s", dirpath);
249               retval = 1;
250             }
251         }
252     }
253   else
254     {
255       /* We get here if the entire path already exists.  */
256
257       if (!S_ISDIR (stats.st_mode))
258         {
259           error (0, 0, "`%s' exists but is not a directory", dirpath);
260           umask (oldmask);
261           return 1;
262         }
263
264       /* chown must precede chmod because on some systems,
265          chown clears the set[ug]id bits for non-superusers,
266          resulting in incorrect permissions.
267          On System V, users can give away files with chown and then not
268          be able to chmod them.  So don't give files away.  */
269
270       if (owner != (uid_t) -1 && group != (gid_t) -1
271           && chown (dirpath, owner, group)
272 #ifdef AFS
273           && errno != EPERM
274 #endif
275           )
276         {
277           error (0, errno, "%s", dirpath);
278           retval = 1;
279         }
280       if (chmod (dirpath, mode))
281         {
282           error (0, errno, "%s", dirpath);
283           retval = 1;
284         }
285     }
286
287   umask (oldmask);
288   return retval;
289 }