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