xref: /illumos-gate/usr/src/cmd/mkdir/mkdir.c (revision 7c478bd9)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T		*/
23 /*	  All Rights Reserved					*/
24 /*								*/
25 
26 /*
27  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 /*
34  * make directory.
35  * If -m is used with a valid mode, directories will be
36  * created in that mode.  Otherwise, the default mode will
37  * be 777 possibly altered by the process's file mode creation
38  * mask.
39  * If -p is used, make the directory as well as
40  * its non-existing parent directories.
41  */
42 
43 #include	<signal.h>
44 #include	<stdio.h>
45 #include	<sys/types.h>
46 #include	<sys/stat.h>
47 #include	<errno.h>
48 #include	<string.h>
49 #include	<locale.h>
50 #include	<stdlib.h>
51 #include	<unistd.h>
52 #include	<libgen.h>
53 #include	<stdarg.h>
54 #include	<wchar.h>
55 
56 #define	MSGEXISTS	"\"%s\": Exists but is not a directory\n"
57 #define	MSGUSAGE 	"usage: mkdir [-m mode] [-p] dirname ...\n"
58 #define	MSGFMT1  	"\"%s\": %s\n"
59 #define	MSGFAILED	"Failed to make directory \"%s\"; %s\n"
60 
61 extern int optind,  errno;
62 extern char *optarg;
63 
64 static char
65 *simplify(char *path);
66 
67 void
68 errmsg(int severity, int code, char *format, ...);
69 
70 extern mode_t
71 newmode(char *ms, mode_t new_mode, mode_t umsk, char *file, char *path);
72 
73 #define	ALLRWX (S_IRWXU | S_IRWXG | S_IRWXO)
74 
75 
76 void
77 main(int argc, char *argv[])
78 {
79 	int 	pflag, errflg, mflag;
80 	int 	c, local_errno, tmp_errno;
81 	mode_t	cur_umask;
82 	mode_t	mode;
83 	mode_t	modediff;
84 	char 	*d;
85 	struct stat	buf;
86 
87 	pflag = mflag = errflg = 0;
88 	local_errno = 0;
89 
90 	(void) setlocale(LC_ALL, "");
91 
92 #if	!defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
93 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
94 #endif
95 
96 	(void) textdomain(TEXT_DOMAIN);
97 
98 	cur_umask = umask(0);
99 
100 	mode = ALLRWX;
101 
102 	while ((c = getopt(argc, argv, "m:p")) != EOF) {
103 		switch (c) {
104 		case 'm':
105 			mflag++;
106 			mode = newmode(optarg, ALLRWX, cur_umask, "", "");
107 			break;
108 		case 'p':
109 			pflag++;
110 			break;
111 		case '?':
112 			errflg++;
113 			break;
114 		}
115 	}
116 
117 
118 	/*
119 	 * When using default ACLs, mkdir() should be called with
120 	 * 0777 always; and umask or default ACL should do the work.
121 	 * Because of the POSIX.2 requirement that the
122 	 * intermediate mode be at least -wx------,
123 	 * we do some trickery here.
124 	 *
125 	 * If pflag is not set, we can just leave the umask as
126 	 * it the user specified it, unless it masks any of bits 0300.
127 	 */
128 	if (pflag) {
129 		modediff = cur_umask & (S_IXUSR | S_IWUSR);
130 		if (modediff)
131 			cur_umask &= ~modediff;
132 	}
133 	(void) umask(cur_umask);
134 
135 	argc -= optind;
136 	if (argc < 1 || errflg) {
137 		errmsg(0, 2, gettext(MSGUSAGE));
138 	}
139 	argv = &argv[optind];
140 
141 	errno = 0;
142 	while (argc--) {
143 		if ((d = simplify(*argv++)) == NULL) {
144 			exit(2);
145 		}
146 
147 		/*
148 		 * When -p is set, invokes mkdirp library routine.
149 		 * Although successfully invoked, mkdirp sets errno to ENOENT
150 		 * if one of the directory in the pathname does not exist,
151 		 * thus creates a confusion on success/failure status
152 		 * possibly checked by the calling routine or shell.
153 		 * Therefore, errno is reset only when
154 		 * mkdirp has executed successfully, otherwise save
155 		 * in local_errno.
156 		 */
157 		if (pflag) {
158 			/*
159 			 * POSIX.2 says that it is not an error if
160 			 * the argument names an existing directory.
161 			 * We will, however, complain if the argument
162 			 * exists but is not a directory.
163 			 */
164 			if (lstat(d, &buf) != -1) {
165 				if (S_ISDIR(buf.st_mode)) {
166 					continue;
167 				} else {
168 					local_errno = EEXIST;
169 					errmsg(0, 0, gettext(MSGEXISTS), d);
170 					continue;
171 				}
172 			}
173 			errno = 0;
174 
175 			if (mkdirp(d, ALLRWX) < 0) {
176 				tmp_errno = errno;
177 
178 				if (tmp_errno == EEXIST) {
179 					if (lstat(d, &buf) != -1) {
180 						if (! S_ISDIR(buf.st_mode)) {
181 							local_errno =
182 							    tmp_errno;
183 							errmsg(0, 0, gettext(
184 							    MSGEXISTS), d);
185 							continue;
186 						}
187 						/* S_ISDIR: do nothing */
188 					} else {
189 						local_errno = tmp_errno;
190 						perror("mkdir");
191 						errmsg(0, 0,
192 						    gettext(MSGFAILED), d,
193 						    strerror(local_errno));
194 						continue;
195 					}
196 				} else {
197 					local_errno = tmp_errno;
198 					errmsg(0, 0, gettext(MSGFMT1), d,
199 					    strerror(tmp_errno));
200 					continue;
201 				}
202 			}
203 
204 			errno = 0;
205 
206 			/*
207 			 * get the file mode for the newly
208 			 * created directory and test for
209 			 * set gid bit being inherited from the parent
210 			 * directory to include it with the file
211 			 * mode creation for the last directory
212 			 * on the dir path.
213 			 *
214 			 * This is only needed if mflag was specified
215 			 * or if the umask was adjusted with -wx-----
216 			 *
217 			 * If mflag is specified, we chmod to the specified
218 			 * mode, oring in the 02000 bit.
219 			 *
220 			 * If modediff is set, those bits need to be
221 			 * removed from the last directory component,
222 			 * all other bits are kept regardless of umask
223 			 * in case a default ACL is present.
224 			 */
225 			if (mflag || modediff) {
226 				mode_t tmpmode;
227 
228 				(void) lstat(d, &buf);
229 				if (modediff && !mflag)
230 					tmpmode = (buf.st_mode & 07777)
231 								& ~modediff;
232 				else
233 					tmpmode = mode | (buf.st_mode & 02000);
234 
235 				if (chmod(d, tmpmode) < 0) {
236 					tmp_errno = errno;
237 					local_errno = errno;
238 					errmsg(0, 0, gettext(MSGFMT1), d,
239 					    strerror(tmp_errno));
240 					continue;
241 				}
242 				errno = 0;
243 			}
244 
245 			continue;
246 		} else {
247 			/*
248 			 * No -p. Make only one directory
249 			 */
250 
251 			errno = 0;
252 
253 			if (mkdir(d, mode) < 0) {
254 				local_errno = tmp_errno = errno;
255 				errmsg(0, 0, gettext(MSGFAILED), d,
256 				    strerror(tmp_errno));
257 				continue;
258 			}
259 			if (mflag) {
260 				mode_t tmpmode;
261 				(void) lstat(d, &buf);
262 				tmpmode = mode | (buf.st_mode & 02000);
263 
264 				if (chmod(d, tmpmode) < 0) {
265 					tmp_errno = errno;
266 					local_errno = errno;
267 					errmsg(0, 0, gettext(MSGFMT1), d,
268 					    strerror(tmp_errno));
269 					continue;
270 				}
271 				errno = 0;
272 			}
273 		}
274 	} /* end while */
275 
276 	/* When pflag is set, the errno is saved in local_errno */
277 
278 	if (local_errno)
279 	    errno = local_errno;
280 	exit(errno ? 2: 0);
281 }
282 
283 /*
284  *  errmsg - This is an interface required by the code common to mkdir and
285  *		chmod. The severity parameter is ignored here, but is meaningful
286  *		to chmod.
287  */
288 
289 /* ARGSUSED */
290 /* PRINTFLIKE3 */
291 void
292 errmsg(int severity, int code, char *format, ...)
293 {
294 	va_list ap;
295 	va_start(ap, format);
296 
297 	(void) fprintf(stderr, "mkdir: ");
298 	(void) vfprintf(stderr, format, ap);
299 
300 	va_end(ap);
301 
302 	if (code > 0) {
303 		exit(code);
304 	}
305 }
306 
307 /*
308  *	simplify - given a pathname in a writable buffer, simplify that
309  *		   path by removing meaningless occurances of path
310  *		   syntax.
311  *
312  *		   The change happens in place in the argument.  The
313  *		   result is neceassarily no longer than the original.
314  *
315  *		   Return the pointer supplied by the caller on success, or
316  *		   NULL on error.
317  *
318  *		   The caller should handle error reporting based upon the
319  *		   returned vlaue.
320  */
321 
322 static char *
323 simplify(char *mbPath)
324 {
325 	int i;
326 	size_t mbPathlen;	/* length of multi-byte path */
327 	size_t wcPathlen;	/* length of wide-character path */
328 	wchar_t *wptr;		/* scratch pointer */
329 	wchar_t *wcPath;	/* wide-character version of the path */
330 
331 	/*
332 	 *  bail out if there is nothing there.
333 	 */
334 
335 	if (!mbPath)
336 	    return (mbPath);
337 
338 	/*
339 	 *  convert the multi-byte version of the path to a
340 	 *  wide-character rendering, for doing our figuring.
341 	 */
342 
343 	mbPathlen = strlen(mbPath);
344 
345 	if ((wcPath = calloc(sizeof (wchar_t), mbPathlen+1)) == NULL) {
346 		perror("mkdir");
347 		exit(2);
348 	}
349 
350 	if ((wcPathlen = mbstowcs(wcPath, mbPath, mbPathlen)) == (size_t)-1) {
351 		free(wcPath);
352 		return (NULL);
353 	}
354 
355 	/*
356 	 *  remove duplicate slashes first ("//../" -> "/")
357 	 */
358 
359 	for (wptr = wcPath, i = 0; i < wcPathlen; i++) {
360 		*wptr++ = wcPath[i];
361 
362 		if (wcPath[i] == '/') {
363 			i++;
364 
365 			while (wcPath[i] == '/') {
366 				i++;
367 			}
368 
369 			i--;
370 		}
371 	}
372 
373 	*wptr = '\0';
374 
375 	/*
376 	 *  next skip initial occurances of "./"
377 	 */
378 
379 	for (wcPathlen = wcslen(wcPath), wptr = wcPath, i = 0;
380 	    i < wcPathlen-2 && wcPath[i] == '.' && wcPath[i+1] == '/';
381 	    i += 2) {
382 		/* empty body */
383 	}
384 
385 	/*
386 	 *  now make reductions of various forms.
387 	 */
388 
389 	while (i < wcPathlen) {
390 		if (i < wcPathlen-2 && wcPath[i] == '/' &&
391 		    wcPath[i+1] == '.' && wcPath[i+2] == '/') {
392 			/* "/./" -> "/" */
393 			i += 2;
394 		} else {
395 			/* Normal case: copy the character */
396 			*wptr++ = wcPath[i++];
397 		}
398 	}
399 
400 	*wptr = '\0';
401 
402 	/*
403 	 *  now convert back to the multi-byte format.
404 	 */
405 
406 	if (wcstombs(mbPath, wcPath, mbPathlen) == (size_t)-1) {
407 		free(wcPath);
408 		return (NULL);
409 	}
410 
411 	free(wcPath);
412 	return (mbPath);
413 }
414