e109865c55f44b3e7610497bf4d20c16b7c4899c
[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 #include "safe-stat.h"
90 void error ();
91
92 /* Ensure that the directory ARGPATH exists.
93    Remove any trailing slashes from ARGPATH before calling this function.
94
95    Make any leading directories that don't already exist, with
96    permissions PARENT_MODE.
97    If the last element of ARGPATH does not exist, create it as
98    a new directory with permissions MODE.
99    If OWNER and GROUP are non-negative, make them the UID and GID of
100    created directories.
101    If VERBOSE_FMT_STRING is nonzero, use it as a printf format
102    string for printing a message after successfully making a directory,
103    with the name of the directory that was just made as an argument.
104
105    Return 0 if ARGPATH exists as a directory with the proper
106    ownership and permissions when done, otherwise 1.  */
107
108 int
109 make_path (argpath, mode, parent_mode, owner, group, verbose_fmt_string)
110      char *argpath;
111      int mode;
112      int parent_mode;
113      uid_t owner;
114      gid_t group;
115      char *verbose_fmt_string;
116 {
117   char *dirpath;                /* A copy we can scribble NULs on.  */
118   struct stat stats;
119   int retval = 0;
120   int oldmask = umask (0);
121
122   dirpath = (char *) alloca (strlen (argpath) + 1);
123   strcpy (dirpath, argpath);
124
125   if (SAFE_STAT (dirpath, &stats))
126     {
127       char *slash;
128       int tmp_mode;             /* Initial perms for leading dirs.  */
129       int re_protect;           /* Should leading dirs be unwritable? */
130       struct ptr_list
131       {
132         char *dirname_end;
133         struct ptr_list *next;
134       };
135       struct ptr_list *p, *leading_dirs = NULL;
136
137       /* If leading directories shouldn't be writable or executable,
138          or should have set[ug]id or sticky bits set and we are setting
139          their owners, we need to fix their permissions after making them.  */
140       if (((parent_mode & 0300) != 0300)
141           || (owner != (uid_t) -1 && group != (gid_t) -1
142               && (parent_mode & 07000) != 0))
143         {
144           tmp_mode = 0700;
145           re_protect = 1;
146         }
147       else
148         {
149           tmp_mode = parent_mode;
150           re_protect = 0;
151         }
152
153       slash = dirpath;
154       while (*slash == '/')
155         slash++;
156       while ((slash = index (slash, '/')))
157         {
158           *slash = '\0';
159           if (SAFE_STAT (dirpath, &stats))
160             {
161               if (mkdir (dirpath, tmp_mode))
162                 {
163                   error (0, errno, "cannot make directory `%s'", dirpath);
164                   umask (oldmask);
165                   return 1;
166                 }
167               else
168                 {
169                   if (verbose_fmt_string != NULL)
170                     error (0, 0, verbose_fmt_string, dirpath);
171
172                   if (owner != (uid_t) -1 && group != (gid_t) -1
173                       && chown (dirpath, owner, group)
174 #if defined(AFS) && defined (EPERM)
175                       && errno != EPERM
176 #endif
177                       )
178                     {
179                       error (0, errno, "%s", dirpath);
180                       retval = 1;
181                     }
182                   if (re_protect)
183                     {
184                       struct ptr_list *new = (struct ptr_list *)
185                         alloca (sizeof (struct ptr_list));
186                       new->dirname_end = slash;
187                       new->next = leading_dirs;
188                       leading_dirs = new;
189                     }
190                 }
191             }
192           else if (!S_ISDIR (stats.st_mode))
193             {
194               error (0, 0, "`%s' exists but is not a directory", dirpath);
195               umask (oldmask);
196               return 1;
197             }
198
199           *slash++ = '/';
200
201           /* Avoid unnecessary calls to `stat' when given
202              pathnames containing multiple adjacent slashes.  */
203           while (*slash == '/')
204             slash++;
205         }
206
207       /* We're done making leading directories.
208          Make the final component of the path.  */
209
210       /* The path could end in "/." or contain "/..", so test
211          if we really have to create the directory.  */
212
213       if (SAFE_STAT (dirpath, &stats) && mkdir (dirpath, mode))
214         {
215           error (0, errno, "cannot make directory `%s'", dirpath);
216           umask (oldmask);
217           return 1;
218         }
219       if (verbose_fmt_string != NULL)
220         error (0, 0, verbose_fmt_string, dirpath);
221
222       if (owner != (uid_t) -1 && group != (gid_t) -1)
223         {
224           if (chown (dirpath, owner, group)
225 #ifdef AFS
226               && errno != EPERM
227 #endif
228               )
229             {
230               error (0, errno, "%s", dirpath);
231               retval = 1;
232             }
233           /* chown may have turned off some permission bits we wanted.  */
234           if ((mode & 07000) != 0 && chmod (dirpath, mode))
235             {
236               error (0, errno, "%s", dirpath);
237               retval = 1;
238             }
239         }
240
241       /* If the mode for leading directories didn't include owner "wx"
242          privileges, we have to reset their protections to the correct
243          value.  */
244       for (p = leading_dirs; p != NULL; p = p->next)
245         {
246           *(p->dirname_end) = '\0';
247           if (chmod (dirpath, parent_mode))
248             {
249               error (0, errno, "%s", dirpath);
250               retval = 1;
251             }
252         }
253     }
254   else
255     {
256       /* We get here if the entire path already exists.  */
257
258       if (!S_ISDIR (stats.st_mode))
259         {
260           error (0, 0, "`%s' exists but is not a directory", dirpath);
261           umask (oldmask);
262           return 1;
263         }
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   umask (oldmask);
289   return retval;
290 }