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