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