xref: /netbsd-src/bin/mkdir/mkdir.c (revision 326b2259b73e878289ebd80cd9d20bc5aee35e99)
1 /* $NetBSD: mkdir.c,v 1.31 2003/08/07 09:05:16 agc Exp $ */
2 
3 /*
4  * Copyright (c) 1983, 1992, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __COPYRIGHT("@(#) Copyright (c) 1983, 1992, 1993\n\
35 	The Regents of the University of California.  All rights reserved.\n");
36 #endif /* not lint */
37 
38 #ifndef lint
39 #if 0
40 static char sccsid[] = "@(#)mkdir.c	8.2 (Berkeley) 1/25/94";
41 #else
42 __RCSID("$NetBSD: mkdir.c,v 1.31 2003/08/07 09:05:16 agc Exp $");
43 #endif
44 #endif /* not lint */
45 
46 #include <sys/param.h>
47 #include <sys/stat.h>
48 #include <sys/types.h>
49 
50 #include <err.h>
51 #include <errno.h>
52 #include <locale.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 #include <vis.h>
58 
59 int	stdout_ok;			/* stdout connected to a terminal */
60 
61 int	mkpath(char *, mode_t, mode_t);
62 void	usage(void);
63 int	main(int, char *[]);
64 char	*printescaped(const char *);
65 
66 int
67 main(int argc, char *argv[])
68 {
69 	int ch, exitval, pflag;
70 	void *set;
71 	mode_t mode, dir_mode;
72 
73 	setprogname(argv[0]);
74 	(void)setlocale(LC_ALL, "");
75 
76 	/*
77 	 * The default file mode is a=rwx (0777) with selected permissions
78 	 * removed in accordance with the file mode creation mask.  For
79 	 * intermediate path name components, the mode is the default modified
80 	 * by u+wx so that the subdirectories can always be created.
81 	 */
82 	mode = (S_IRWXU | S_IRWXG | S_IRWXO) & ~umask(0);
83 	dir_mode = mode | S_IWUSR | S_IXUSR;
84 
85 	pflag = 0;
86 	while ((ch = getopt(argc, argv, "m:p")) != -1)
87 		switch (ch) {
88 		case 'p':
89 			pflag = 1;
90 			break;
91 		case 'm':
92 			if ((set = setmode(optarg)) == NULL) {
93 				errx(EXIT_FAILURE, "invalid file mode: %s",
94 					printescaped(optarg));
95 				/* NOTREACHED */
96 			}
97 			mode = getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
98 			free(set);
99 			break;
100 		case '?':
101 		default:
102 			usage();
103 			/* NOTREACHED */
104 		}
105 	argc -= optind;
106 	argv += optind;
107 
108 	if (*argv == NULL) {
109 		usage();
110 		/* NOTREACHED */
111 	}
112 
113 	stdout_ok = isatty(STDOUT_FILENO);
114 
115 	for (exitval = EXIT_SUCCESS; *argv != NULL; ++argv) {
116 		char *slash;
117 
118 		/* Remove trailing slashes, per POSIX. */
119 		slash = strrchr(*argv, '\0');
120 		while (--slash > *argv && *slash == '/')
121 			*slash = '\0';
122 
123 		if (pflag) {
124 			if (mkpath(*argv, mode, dir_mode) < 0)
125 				exitval = EXIT_FAILURE;
126 		} else {
127 			if (mkdir(*argv, mode) < 0) {
128 				warn("%s", *argv);
129 				exitval = EXIT_FAILURE;
130 			} else {
131 				/*
132 				 * The mkdir() and umask() calls both honor
133 				 * only the file permission bits, so if you try
134 				 * to set a mode including the sticky, setuid,
135 				 * setgid bits you lose them. So chmod().
136 				 */
137 				if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) != 0 &&
138 				    chmod(*argv, mode) == -1) {
139 					char *fn;
140 					fn = printescaped(*argv);
141 					warn("%s", fn);
142 					free(fn);
143 					exitval = EXIT_FAILURE;
144 				}
145 			}
146 		}
147 	}
148 	exit(exitval);
149 	/* NOTREACHED */
150 }
151 
152 /*
153  * mkpath -- create directories.
154  *	path     - path
155  *	mode     - file mode of terminal directory
156  *	dir_mode - file mode of intermediate directories
157  */
158 int
159 mkpath(char *path, mode_t mode, mode_t dir_mode)
160 {
161 	struct stat sb;
162 	char *slash, *fn;
163 	int done, rv;
164 
165 	done = 0;
166 	slash = path;
167 	fn = printescaped(path);
168 
169 	for (;;) {
170 		slash += strspn(slash, "/");
171 		slash += strcspn(slash, "/");
172 
173 		done = (*slash == '\0');
174 		*slash = '\0';
175 
176 		rv = mkdir(path, done ? mode : dir_mode);
177 		if (rv < 0) {
178 			/*
179 			 * Can't create; path exists or no perms.
180 			 * stat() path to determine what's there now.
181 			 */
182 			int	sverrno;
183 
184 			sverrno = errno;
185 			if (stat(path, &sb) < 0) {
186 					/* Not there; use mkdir()s error */
187 				errno = sverrno;
188 				warn("%s", fn);
189 				free(fn);
190 				return -1;
191 			}
192 			if (!S_ISDIR(sb.st_mode)) {
193 					/* Is there, but isn't a directory */
194 				errno = ENOTDIR;
195 				warn("%s", fn);
196 				free(fn);
197 				return -1;
198 			}
199 		} else if (done) {
200 			/*
201 			 * Created ok, and this is the last element
202 			 */
203 			/*
204 			 * The mkdir() and umask() calls both honor only the
205 			 * file permission bits, so if you try to set a mode
206 			 * including the sticky, setuid, setgid bits you lose
207 			 * them. So chmod().
208 			 */
209 			if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) != 0 &&
210 			    chmod(path, mode) == -1) {
211 				warn("%s", fn);
212 				free(fn);
213 				return -1;
214 			}
215 		}
216 
217 		if (done) {
218 			break;
219 		}
220 		*slash = '/';
221 	}
222 
223 	free(fn);
224 	return 0;
225 }
226 
227 void
228 usage(void)
229 {
230 
231 	(void)fprintf(stderr, "usage: %s [-p] [-m mode] dirname ...\n",
232 	    getprogname());
233 	exit(EXIT_FAILURE);
234 	/* NOTREACHED */
235 }
236 
237 char *
238 printescaped(const char *src)
239 {
240 	size_t len;
241 	char *retval;
242 
243 	len = strlen(src);
244 	if (len != 0 && SIZE_T_MAX/len <= 4) {
245 		errx(EXIT_FAILURE, "%s: name too long", src);
246 		/* NOTREACHED */
247 	}
248 
249 	retval = (char *)malloc(4*len+1);
250 	if (retval != NULL) {
251 		if (stdout_ok)
252 			(void)strvis(retval, src, VIS_NL | VIS_CSTYLE);
253 		else
254 			(void)strcpy(retval, src);
255 		return retval;
256 	} else
257 		errx(EXIT_FAILURE, "out of memory!");
258 		/* NOTREACHED */
259 }
260