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