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 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 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 * 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