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
62extern int optind,  errno;
63extern char *optarg;
64
65static char
66*simplify(char *path);
67
68void
69errmsg(int severity, int code, char *format, ...);
70
71extern mode_t
72newmode(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
77int
78main(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 */
292void
293errmsg(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
323static char *
324simplify(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