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