]> git.saurik.com Git - apple/libc.git/blame - util/mkpath_np.c
Libc-997.1.1.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
ad3c9f2a
A
109 while (1) {
110 /* Increase our depth and try making that directory */
6465356a 111 s = strrchr(apath, '/');
ad3c9f2a
A
112 if (!s) {
113 /* We should never hit this under normal circumstances,
114 * but it can occur due to really unfortunate timing
115 */
116 retval = ENOENT;
117 goto mkpath_exit;
118 }
119 *s = '\0';
120 depth++;
121
122 if (0 == mkdir(apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
123 /* Found our starting point */
124
125 /* POSIX 1003.2:
126 * For each dir operand that does not name an existing
127 * directory, effects equivalent to those cased by the
128 * following command shall occcur:
129 *
130 * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
131 * mkdir [-m mode] dir
132 */
133
134 struct stat dirstat;
135 if (-1 == stat(apath, &dirstat)) {
136 /* Really unfortunate timing ... */
137 retval = ENOENT;
138 goto mkpath_exit;
139 }
140
141 if ((dirstat.st_mode & (S_IWUSR | S_IXUSR)) != (S_IWUSR | S_IXUSR)) {
142 chmod_mode = dirstat.st_mode | S_IWUSR | S_IXUSR;
143 if (-1 == chmod(apath, chmod_mode)) {
144 /* Really unfortunate timing ... */
145 retval = ENOENT;
146 goto mkpath_exit;
147 }
148 }
149
150 if (firstdir) {
151 *firstdir = strdup(apath);
152 }
153 break;
154 } else if (errno == EEXIST) {
155 /* Some other process won the race in creating this directory
156 * before we did. We will use this as our starting point.
157 * See: <rdar://problem/10279893>
158 */
159 if (stat(apath, &sbuf) == 0 &&
160 S_ISDIR(sbuf.st_mode)) {
161
162 if (firstdir) {
163 *firstdir = strdup(apath);
164 }
165 break;
166 }
167
168 retval = ENOTDIR;
169 goto mkpath_exit;
170 } else if (errno != ENOENT) {
171 retval = errno;
172 goto mkpath_exit;
173 }
174 }
175
176 while (depth > 1) {
177 /* Decrease our depth and make that directory */
6465356a 178 s = strrchr(apath, '\0');
ad3c9f2a
A
179 *s = '/';
180 depth--;
181
182 if (-1 == mkdir(apath, S_IRWXU | S_IRWXG | S_IRWXO)) {
183 /* This handles "." and ".." added to the new section of path */
184 if (errno == EEXIST)
185 continue;
186 retval = errno;
187 goto mkpath_exit;
188 }
189
190 if (chmod_mode) {
191 if (-1 == chmod(apath, chmod_mode)) {
192 /* Really unfortunate timing ... */
193 retval = ENOENT;
194 goto mkpath_exit;
195 }
196 }
197 }
198
199 if (-1 == mkdir(path, omode)) {
200 retval = errno;
201 if (errno == EEXIST &&
202 stat(path, &sbuf) == 0 &&
203 !S_ISDIR(sbuf.st_mode)) {
204 retval = ENOTDIR;
205 }
206 }
207
208mkpath_exit:
209 free(apath);
6465356a 210 free(opath);
ad3c9f2a
A
211
212 errno = old_errno;
213 return retval;
214}
215
216int mkpath_np(const char *path, mode_t omode) {
217 return _mkpath_np(path, omode, NULL);
218}