]> git.saurik.com Git - apple/libc.git/blame - util/mkpath_np.c
Libc-997.90.3.tar.gz
[apple/libc.git] / util / mkpath_np.c
CommitLineData
ad3c9f2a
A
1/*
2 * Copyright (c) 2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <unistd.h>
25#include <stdlib.h>
26#include <string.h>
27#include <errno.h>
28#include <sys/stat.h>
29
30/* This extended version of mkpath_np is provided to help NSFileManager
31 * maintain binary compatibility. If firstdir is not NULL, *firstdir will be
32 * set to the path of the first created directory, and it is the caller's
33 * responsibility to free the returned string. This SPI is subject to removal
34 * once NSFileManager no longer has a need for it, and use in new code is
35 * highly discouraged.
36 *
37 * See: <rdar://problem/9888987>
38 */
39
40int
41_mkpath_np(const char *path, mode_t omode, const char ** firstdir)
42{
6465356a 43 char *apath = NULL, *opath = NULL, *s, *sn, *sl;
ad3c9f2a
A
44 unsigned int depth = 0;
45 mode_t chmod_mode = 0;
46 int retval = 0;
47 int old_errno = errno;
48 struct stat sbuf;
49
50 /* Try the trivial case first. */
51 if (0 == mkdir(path, omode)) {
52 if (firstdir) {
53 *firstdir = strdup(path);
54 }
55 goto mkpath_exit;
56 }
57
58 /* Anything other than an ENOENT, EEXIST, or EISDIR indicates an
59 * error that we need to send back to the caller. ENOENT indicates
60 * that we need to try a lower level.
61 */
62 switch (errno) {
63 case ENOENT:
64 break;
65 case EEXIST:
065eae9f
A
66 if (stat(path, &sbuf) == 0) {
67 if (S_ISDIR(sbuf.st_mode)) {
68 retval = EEXIST;
69 } else {
70 retval = ENOTDIR;
71 }
72 } else {
73 retval = EIO;
ad3c9f2a
A
74 }
75 goto mkpath_exit;
76 case EISDIR: /* <rdar://problem/10288022> */
77 retval = EEXIST;
78 goto mkpath_exit;
79 default:
80 retval = errno;
81 goto mkpath_exit;
82 }
83
84 apath = strdup(path);
85 if (apath == NULL) {
86 retval = ENOMEM;
87 goto mkpath_exit;
88 }
89
6465356a
A
90 sl = s = apath + strlen(apath) - 1;
91 do {
92 sn = s;
93 /* Strip off trailing /., see <rdar://problem/14351794> */
94 if (s - 1 > apath && *s == '.' && *(s - 1) == '/')
95 s -= 2;
96 /* Strip off trailing /, see <rdar://problem/11592386> */
97 if (s > apath && *s == '/')
98 s--;
99 } while (s < sn);
100 if (s < sl) {
101 s[1] = '\0';
102 path = opath = strdup(apath);
103 if (opath == NULL) {
104 retval = ENOMEM;
105 goto mkpath_exit;
106 }
107 }
108
4c63d215
A
109 /* Retry the trivial case after having stripped of trailing /. <rdar://problem/14351794> */
110 if (0 == mkdir(path, omode)) {
111 if (firstdir) {
112 *firstdir = strdup(path);
113 }
114 goto mkpath_exit;
115 }
116
ad3c9f2a
A
117 while (1) {
118 /* Increase our depth and try making that directory */
6465356a 119 s = strrchr(apath, '/');
ad3c9f2a
A
120 if (!s) {
121 /* We should never hit this under normal circumstances,
122 * but it can occur due to really unfortunate timing
123 */
124 retval = ENOENT;
125 goto mkpath_exit;
126 }
127 *s = '\0';
128 depth++;
129
130 if (0 == mkdir(apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
131 /* Found our starting point */
132
133 /* POSIX 1003.2:
134 * For each dir operand that does not name an existing
135 * directory, effects equivalent to those cased by the
136 * following command shall occcur:
137 *
138 * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
139 * mkdir [-m mode] dir
140 */
141
142 struct stat dirstat;
143 if (-1 == stat(apath, &dirstat)) {
144 /* Really unfortunate timing ... */
145 retval = ENOENT;
146 goto mkpath_exit;
147 }
148
149 if ((dirstat.st_mode & (S_IWUSR | S_IXUSR)) != (S_IWUSR | S_IXUSR)) {
150 chmod_mode = dirstat.st_mode | S_IWUSR | S_IXUSR;
151 if (-1 == chmod(apath, chmod_mode)) {
152 /* Really unfortunate timing ... */
153 retval = ENOENT;
154 goto mkpath_exit;
155 }
156 }
157
158 if (firstdir) {
159 *firstdir = strdup(apath);
160 }
161 break;
162 } else if (errno == EEXIST) {
163 /* Some other process won the race in creating this directory
164 * before we did. We will use this as our starting point.
165 * See: <rdar://problem/10279893>
166 */
167 if (stat(apath, &sbuf) == 0 &&
168 S_ISDIR(sbuf.st_mode)) {
169
170 if (firstdir) {
171 *firstdir = strdup(apath);
172 }
173 break;
174 }
175
176 retval = ENOTDIR;
177 goto mkpath_exit;
178 } else if (errno != ENOENT) {
179 retval = errno;
180 goto mkpath_exit;
181 }
182 }
183
184 while (depth > 1) {
185 /* Decrease our depth and make that directory */
6465356a 186 s = strrchr(apath, '\0');
ad3c9f2a
A
187 *s = '/';
188 depth--;
189
190 if (-1 == mkdir(apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
191 /* This handles "." and ".." added to the new section of path */
192 if (errno == EEXIST)
193 continue;
194 retval = errno;
195 goto mkpath_exit;
196 }
197
198 if (chmod_mode) {
199 if (-1 == chmod(apath, chmod_mode)) {
200 /* Really unfortunate timing ... */
201 retval = ENOENT;
202 goto mkpath_exit;
203 }
204 }
205 }
206
207 if (-1 == mkdir(path, omode)) {
208 retval = errno;
209 if (errno == EEXIST &&
210 stat(path, &sbuf) == 0 &&
211 !S_ISDIR(sbuf.st_mode)) {
212 retval = ENOTDIR;
213 }
214 }
215
216mkpath_exit:
217 free(apath);
6465356a 218 free(opath);
ad3c9f2a
A
219
220 errno = old_errno;
221 return retval;
222}
223
224int mkpath_np(const char *path, mode_t omode) {
225 return _mkpath_np(path, omode, NULL);
226}