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
main(int argc,char * argv[])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
errmsg(int severity,int code,char * format,...)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 *
simplify(char * mbPath)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