]> git.saurik.com Git - apple/libc.git/blame - util/mkpath_np.c
Libc-1244.1.7.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>
974e3884 29#include <fcntl.h>
ad3c9f2a 30
974e3884
A
31static int
32_mkpath(int dfd, const char *path, mode_t omode, const char ** firstdir)
ad3c9f2a 33{
6465356a 34 char *apath = NULL, *opath = NULL, *s, *sn, *sl;
ad3c9f2a
A
35 unsigned int depth = 0;
36 mode_t chmod_mode = 0;
37 int retval = 0;
38 int old_errno = errno;
39 struct stat sbuf;
40
41 /* Try the trivial case first. */
974e3884 42 if (0 == mkdirat(dfd, path, omode)) {
ad3c9f2a
A
43 if (firstdir) {
44 *firstdir = strdup(path);
45 }
46 goto mkpath_exit;
47 }
48
49 /* Anything other than an ENOENT, EEXIST, or EISDIR indicates an
50 * error that we need to send back to the caller. ENOENT indicates
51 * that we need to try a lower level.
52 */
53 switch (errno) {
54 case ENOENT:
55 break;
56 case EEXIST:
974e3884 57 if (fstatat(dfd, path, &sbuf, 0) == 0) {
065eae9f
A
58 if (S_ISDIR(sbuf.st_mode)) {
59 retval = EEXIST;
60 } else {
61 retval = ENOTDIR;
62 }
63 } else {
64 retval = EIO;
ad3c9f2a
A
65 }
66 goto mkpath_exit;
67 case EISDIR: /* <rdar://problem/10288022> */
68 retval = EEXIST;
69 goto mkpath_exit;
70 default:
71 retval = errno;
72 goto mkpath_exit;
73 }
74
75 apath = strdup(path);
76 if (apath == NULL) {
77 retval = ENOMEM;
78 goto mkpath_exit;
79 }
80
6465356a
A
81 sl = s = apath + strlen(apath) - 1;
82 do {
83 sn = s;
84 /* Strip off trailing /., see <rdar://problem/14351794> */
85 if (s - 1 > apath && *s == '.' && *(s - 1) == '/')
86 s -= 2;
87 /* Strip off trailing /, see <rdar://problem/11592386> */
88 if (s > apath && *s == '/')
89 s--;
90 } while (s < sn);
91 if (s < sl) {
92 s[1] = '\0';
93 path = opath = strdup(apath);
94 if (opath == NULL) {
95 retval = ENOMEM;
96 goto mkpath_exit;
97 }
98 }
99
4c63d215 100 /* Retry the trivial case after having stripped of trailing /. <rdar://problem/14351794> */
974e3884 101 if (0 == mkdirat(dfd, path, omode)) {
4c63d215
A
102 if (firstdir) {
103 *firstdir = strdup(path);
104 }
105 goto mkpath_exit;
106 }
107
ad3c9f2a
A
108 while (1) {
109 /* Increase our depth and try making that directory */
6465356a 110 s = strrchr(apath, '/');
ad3c9f2a
A
111 if (!s) {
112 /* We should never hit this under normal circumstances,
113 * but it can occur due to really unfortunate timing
114 */
115 retval = ENOENT;
116 goto mkpath_exit;
117 }
118 *s = '\0';
119 depth++;
120
974e3884 121 if (0 == mkdirat(dfd, apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
ad3c9f2a
A
122 /* Found our starting point */
123
124 /* POSIX 1003.2:
125 * For each dir operand that does not name an existing
126 * directory, effects equivalent to those cased by the
127 * following command shall occcur:
128 *
129 * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
130 * mkdir [-m mode] dir
131 */
132
133 struct stat dirstat;
974e3884 134 if (-1 == fstatat(dfd, apath, &dirstat, 0)) {
ad3c9f2a
A
135 /* Really unfortunate timing ... */
136 retval = ENOENT;
137 goto mkpath_exit;
138 }
139
140 if ((dirstat.st_mode & (S_IWUSR | S_IXUSR)) != (S_IWUSR | S_IXUSR)) {
141 chmod_mode = dirstat.st_mode | S_IWUSR | S_IXUSR;
974e3884 142 if (-1 == fchmodat(dfd, apath, chmod_mode, 0)) {
ad3c9f2a
A
143 /* Really unfortunate timing ... */
144 retval = ENOENT;
145 goto mkpath_exit;
146 }
147 }
148
149 if (firstdir) {
150 *firstdir = strdup(apath);
151 }
152 break;
153 } else if (errno == EEXIST) {
154 /* Some other process won the race in creating this directory
155 * before we did. We will use this as our starting point.
156 * See: <rdar://problem/10279893>
157 */
974e3884 158 if (fstatat(dfd, apath, &sbuf, 0) == 0 &&
ad3c9f2a
A
159 S_ISDIR(sbuf.st_mode)) {
160
161 if (firstdir) {
162 *firstdir = strdup(apath);
163 }
164 break;
165 }
166
167 retval = ENOTDIR;
168 goto mkpath_exit;
169 } else if (errno != ENOENT) {
170 retval = errno;
171 goto mkpath_exit;
172 }
173 }
174
175 while (depth > 1) {
176 /* Decrease our depth and make that directory */
6465356a 177 s = strrchr(apath, '\0');
ad3c9f2a
A
178 *s = '/';
179 depth--;
180
974e3884 181 if (-1 == mkdirat(dfd, apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
ad3c9f2a
A
182 /* This handles "." and ".." added to the new section of path */
183 if (errno == EEXIST)
184 continue;
185 retval = errno;
186 goto mkpath_exit;
187 }
188
189 if (chmod_mode) {
974e3884 190 if (-1 == fchmodat(dfd, apath, chmod_mode, 0)) {
ad3c9f2a
A
191 /* Really unfortunate timing ... */
192 retval = ENOENT;
193 goto mkpath_exit;
194 }
195 }
196 }
197
974e3884 198 if (-1 == mkdirat(dfd, path, omode)) {
ad3c9f2a
A
199 retval = errno;
200 if (errno == EEXIST &&
974e3884 201 fstatat(dfd, path, &sbuf, 0) == 0 &&
ad3c9f2a
A
202 !S_ISDIR(sbuf.st_mode)) {
203 retval = ENOTDIR;
204 }
205 }
206
207mkpath_exit:
208 free(apath);
6465356a 209 free(opath);
ad3c9f2a
A
210
211 errno = old_errno;
212 return retval;
213}
214
974e3884
A
215/* This extended version of mkpath_np is provided to help NSFileManager
216 * maintain binary compatibility. If firstdir is not NULL, *firstdir will be
217 * set to the path of the first created directory, and it is the caller's
218 * responsibility to free the returned string. This SPI is subject to removal
219 * once NSFileManager no longer has a need for it, and use in new code is
220 * highly discouraged.
221 *
222 * See: <rdar://problem/9888987>
223 */
224
225int
226_mkpath_np(const char *path, mode_t omode, const char ** firstdir) {
227 return _mkpath(AT_FDCWD, path, omode, firstdir);
228}
229
ad3c9f2a 230int mkpath_np(const char *path, mode_t omode) {
974e3884
A
231 return _mkpath(AT_FDCWD, path, omode, NULL);
232}
233
234int mkpathat_np(int dfd, const char *path, mode_t omode) {
235 return _mkpath(dfd, path, omode, NULL);
ad3c9f2a 236}