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