1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 /*
14 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
15 * Use is subject to license terms.
16 */
17
18 /* Copyright (c) 1988 AT&T */
19 /* All Rights Reserved */
20
21 /*
22 * Creates directory and it's parents if the parents do not
23 * exist yet.
24 *
25 * Returns -1 if fails for reasons other than non-existing
26 * parents.
27 * Does NOT simplify pathnames with . or .. in them.
28 */
29
30 #include <sys/types.h>
31 #include <libgen.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <sys/stat.h>
37
38 static char *simplify(const char *str);
39
40 int
mkdirp(const char * d,mode_t mode)41 mkdirp(const char *d, mode_t mode)
42 {
43 char *endptr, *ptr, *slash, *str;
44
45 str = simplify(d);
46
47 /* If space couldn't be allocated for the simplified names, return. */
48
49 if (str == NULL)
50 return (-1);
51
52 /* Try to make the directory */
53
54 if (mkdir(str, mode) == 0) {
55 free(str);
56 return (0);
57 }
58 if (errno != ENOENT) {
59 free(str);
60 return (-1);
61 }
62 endptr = strrchr(str, '\0');
63 slash = strrchr(str, '/');
64
65 /* Search upward for the non-existing parent */
66
67 while (slash != NULL) {
68
69 ptr = slash;
70 *ptr = '\0';
71
72 /* If reached an existing parent, break */
73
74 if (access(str, F_OK) == 0)
75 break;
76
77 /* If non-existing parent */
78
79 else {
80 slash = strrchr(str, '/');
81
82 /* If under / or current directory, make it. */
83
84 if (slash == NULL || slash == str) {
85 if (mkdir(str, mode) != 0 && errno != EEXIST) {
86 free(str);
87 return (-1);
88 }
89 break;
90 }
91 }
92 }
93
94 /* Create directories starting from upmost non-existing parent */
95
96 while ((ptr = strchr(str, '\0')) != endptr) {
97 *ptr = '/';
98 if (mkdir(str, mode) != 0 && errno != EEXIST) {
99 /*
100 * If the mkdir fails because str already
101 * exists (EEXIST), then str has the form
102 * "existing-dir/..", and this is really
103 * ok. (Remember, this loop is creating the
104 * portion of the path that didn't exist)
105 */
106 free(str);
107 return (-1);
108 }
109 }
110 free(str);
111 return (0);
112 }
113
114 /*
115 * simplify - given a pathname, simplify that path by removing
116 * duplicate contiguous slashes.
117 *
118 * A simplified copy of the argument is returned to the
119 * caller, or NULL is returned on error.
120 *
121 * The caller should handle error reporting based upon the
122 * returned value, and should free the returned value,
123 * when appropriate.
124 */
125
126 static char *
simplify(const char * str)127 simplify(const char *str)
128 {
129 int i;
130 size_t mbPathlen; /* length of multi-byte path */
131 size_t wcPathlen; /* length of wide-character path */
132 wchar_t *wptr; /* scratch pointer */
133 wchar_t *wcPath; /* wide-character version of the path */
134 char *mbPath; /* The copy fo the path to be returned */
135
136 /*
137 * bail out if there is nothing there.
138 */
139
140 if (!str) {
141 errno = ENOENT;
142 return (NULL);
143 }
144
145 /*
146 * Get a copy of the argument.
147 */
148
149 if ((mbPath = strdup(str)) == NULL) {
150 return (NULL);
151 }
152
153 /*
154 * convert the multi-byte version of the path to a
155 * wide-character rendering, for doing our figuring.
156 */
157
158 mbPathlen = strlen(mbPath);
159
160 if ((wcPath = calloc(mbPathlen+1, sizeof (wchar_t))) == NULL) {
161 free(mbPath);
162 return (NULL);
163 }
164
165 if ((wcPathlen = mbstowcs(wcPath, mbPath, mbPathlen)) == (size_t)-1) {
166 free(mbPath);
167 free(wcPath);
168 return (NULL);
169 }
170
171 /*
172 * remove duplicate slashes first ("//../" -> "/")
173 */
174
175 for (wptr = wcPath, i = 0; i < wcPathlen; i++) {
176 *wptr++ = wcPath[i];
177
178 if (wcPath[i] == '/') {
179 i++;
180
181 while (wcPath[i] == '/') {
182 i++;
183 }
184
185 i--;
186 }
187 }
188
189 *wptr = '\0';
190
191 /*
192 * now convert back to the multi-byte format.
193 */
194
195 if (wcstombs(mbPath, wcPath, mbPathlen) == (size_t)-1) {
196 free(mbPath);
197 free(wcPath);
198 return (NULL);
199 }
200
201 free(wcPath);
202 return (mbPath);
203 }
204