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