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