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