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