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