xref: /freebsd/sys/contrib/openzfs/lib/libspl/mkdirp.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
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