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