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 (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <signal.h> 27 #include <unistd.h> 28 #include <sys/acl.h> 29 #include <sys/statvfs.h> 30 #include <sys/wait.h> 31 #include "bart.h" 32 #include <aclutils.h> 33 34 static int sanitize_reloc_root(char *root, size_t bufsize); 35 static int create_manifest_filelist(char **argv, char *reloc_root); 36 static int create_manifest_rule(char *reloc_root, FILE *rule_fp); 37 static void output_manifest(void); 38 static int eval_file(const char *fname, const struct stat64 *statb); 39 static char *sanitized_fname(const char *, boolean_t); 40 static char *get_acl_string(const char *fname, const struct stat64 *statb, 41 int *err_code); 42 static int generate_hash(int fdin, char *hash_str); 43 static int read_filelist(char *reloc_root, char **argv, char *buf, 44 size_t bufsize); 45 static int walker(const char *name, const struct stat64 *sp, 46 int type, struct FTW *ftwx); 47 48 /* 49 * The following globals are necessary due to the "walker" function 50 * provided by nftw(). Since there is no way to pass them through to the 51 * walker function, they must be global. 52 */ 53 static int compute_chksum = 1, eval_err = 0; 54 static struct rule *subtree_root; 55 static char reloc_root[PATH_MAX]; 56 static struct statvfs64 parent_vfs; 57 58 int 59 bart_create(int argc, char **argv) 60 { 61 boolean_t filelist_input; 62 int ret, c, output_pipe[2]; 63 FILE *rules_fd = NULL; 64 pid_t pid; 65 66 filelist_input = B_FALSE; 67 reloc_root[0] = '\0'; 68 69 while ((c = getopt(argc, argv, "Inr:R:")) != EOF) { 70 switch (c) { 71 case 'I': 72 if (rules_fd != NULL) { 73 (void) fprintf(stderr, "%s", INPUT_ERR); 74 usage(); 75 } 76 filelist_input = B_TRUE; 77 break; 78 79 case 'n': 80 compute_chksum = 0; 81 break; 82 83 case 'r': 84 if (strcmp(optarg, "-") == 0) 85 rules_fd = stdin; 86 else 87 rules_fd = fopen(optarg, "r"); 88 if (rules_fd == NULL) { 89 perror(optarg); 90 usage(); 91 } 92 break; 93 94 case 'R': 95 (void) strlcpy(reloc_root, optarg, sizeof (reloc_root)); 96 ret = sanitize_reloc_root(reloc_root, 97 sizeof (reloc_root)); 98 if (ret == 0) 99 usage(); 100 break; 101 102 case '?': 103 default : 104 usage(); 105 } 106 } 107 argv += optind; 108 109 if (pipe(output_pipe) < 0) { 110 perror(""); 111 exit(FATAL_EXIT); 112 } 113 114 pid = fork(); 115 if (pid < 0) { 116 perror(NULL); 117 exit(FATAL_EXIT); 118 } 119 120 /* 121 * Break the creation of a manifest into two parts: the parent process 122 * generated the data whereas the child process sorts the data. 123 * 124 * The processes communicate through the pipe. 125 */ 126 if (pid > 0) { 127 /* 128 * Redirect the stdout of this process so it goes into 129 * output_pipe[0]. The output of this process will be read 130 * by the child, which will sort the output. 131 */ 132 if (dup2(output_pipe[0], STDOUT_FILENO) != STDOUT_FILENO) { 133 perror(NULL); 134 exit(FATAL_EXIT); 135 } 136 (void) close(output_pipe[0]); 137 (void) close(output_pipe[1]); 138 139 if (filelist_input == B_TRUE) { 140 ret = create_manifest_filelist(argv, reloc_root); 141 } else { 142 ret = create_manifest_rule(reloc_root, rules_fd); 143 } 144 145 /* Close stdout so the sort in the child proc will complete */ 146 (void) fclose(stdout); 147 } else { 148 /* 149 * Redirect the stdin of this process so its read in from 150 * the pipe, which is the parent process in this case. 151 */ 152 if (dup2(output_pipe[1], STDIN_FILENO) != STDIN_FILENO) { 153 perror(NULL); 154 exit(FATAL_EXIT); 155 } 156 (void) close(output_pipe[0]); 157 158 output_manifest(); 159 } 160 161 /* Wait for the child proc (the sort) to complete */ 162 (void) wait(0); 163 164 return (ret); 165 } 166 167 /* 168 * Handle the -R option and sets 'root' to be the absolute path of the 169 * relocatable root. This is useful when the user specifies '-R ../../foo'. 170 * 171 * Return code is whether or not the location spec'd by the -R flag is a 172 * directory or not. 173 */ 174 static int 175 sanitize_reloc_root(char *root, size_t bufsize) 176 { 177 char pwd[PATH_MAX]; 178 179 /* 180 * First, save the current directory and go to the location 181 * specified with the -R option. 182 */ 183 (void) getcwd(pwd, sizeof (pwd)); 184 if (chdir(root) < 0) { 185 /* Failed to change directory, something is wrong.... */ 186 perror(root); 187 return (0); 188 } 189 190 /* 191 * Save the absolute path of the relocatable root directory. 192 */ 193 (void) getcwd(root, bufsize); 194 195 /* 196 * Now, go back to where we started, necessary for picking up a rules 197 * file. 198 */ 199 if (chdir(pwd) < 0) { 200 /* Failed to change directory, something is wrong.... */ 201 perror(root); 202 return (0); 203 } 204 205 /* 206 * Make sure the path returned does not have a trailing /. This 207 * can only happen when the entire pathname is "/". 208 */ 209 if (strcmp(root, "/") == 0) 210 root[0] = '\0'; 211 212 /* 213 * Since the earlier chdir() succeeded, return success. 214 */ 215 return (1); 216 } 217 218 /* 219 * This is the worker bee which creates the manifest based upon the command 220 * line options supplied by the user. 221 * 222 * NOTE: create_manifest() eventually outputs data to a pipe, which is read in 223 * by the child process. The child process is running output_manifest(), which 224 * is responsible for generating sorted output. 225 */ 226 static int 227 create_manifest_rule(char *reloc_root, FILE *rule_fp) 228 { 229 struct rule *root; 230 int ret_status = EXIT; 231 uint_t flags; 232 233 if (compute_chksum) 234 flags = ATTR_CONTENTS; 235 else 236 flags = 0; 237 ret_status = read_rules(rule_fp, reloc_root, flags, 1); 238 239 /* Loop through every single subtree */ 240 for (root = get_first_subtree(); root != NULL; 241 root = get_next_subtree(root)) { 242 243 /* 244 * Check to see if this subtree should have contents 245 * checking turned on or off. 246 * 247 * NOTE: The 'compute_chksum' and 'parent_vfs' 248 * are a necessary hack: the variables are used in 249 * walker(), both directly and indirectly. Since 250 * the parameters to walker() are defined by nftw(), 251 * the globals are really a backdoor mechanism. 252 */ 253 ret_status = statvfs64(root->subtree, &parent_vfs); 254 if (ret_status < 0) { 255 perror(root->subtree); 256 continue; 257 } 258 259 /* 260 * Walk the subtree and invoke the callback function 261 * walker() 262 */ 263 subtree_root = root; 264 (void) nftw64(root->subtree, &walker, 20, FTW_PHYS); 265 266 /* 267 * Ugly but necessary: 268 * 269 * walker() must return 0, or the tree walk will stop, 270 * so warning flags must be set through a global. 271 */ 272 if (eval_err == WARNING_EXIT) 273 ret_status = WARNING_EXIT; 274 275 } 276 return (ret_status); 277 } 278 279 static int 280 create_manifest_filelist(char **argv, char *reloc_root) 281 { 282 int ret_status = EXIT; 283 char input_fname[PATH_MAX]; 284 285 while (read_filelist(reloc_root, argv, 286 input_fname, sizeof (input_fname)) != -1) { 287 288 struct stat64 stat_buf; 289 int ret; 290 291 ret = lstat64(input_fname, &stat_buf); 292 if (ret < 0) { 293 ret_status = WARNING_EXIT; 294 perror(input_fname); 295 } else { 296 ret = eval_file(input_fname, &stat_buf); 297 298 if (ret == WARNING_EXIT) 299 ret_status = WARNING_EXIT; 300 } 301 } 302 303 return (ret_status); 304 } 305 306 /* 307 * output_manifest() the child process. It reads in the output from 308 * create_manifest() and sorts it. 309 */ 310 static void 311 output_manifest(void) 312 { 313 char *env[] = {"LC_CTYPE=C", "LC_COLLATE=C", "LC_NUMERIC=C", NULL}; 314 time_t time_val; 315 struct tm *tm; 316 char time_buf[1024]; 317 318 (void) printf("%s", MANIFEST_VER); 319 time_val = time((time_t)0); 320 tm = localtime(&time_val); 321 (void) strftime(time_buf, sizeof (time_buf), "%A, %B %d, %Y (%T)", tm); 322 (void) printf("! %s\n", time_buf); 323 (void) printf("%s", FORMAT_STR); 324 (void) fflush(stdout); 325 /* 326 * Simply run sort and read from the the current stdin, which is really 327 * the output of create_manifest(). 328 * Also, make sure the output is unique, since a given file may be 329 * included by several stanzas. 330 */ 331 if (execle("/usr/bin/sort", "sort", "-u", NULL, env) < 0) { 332 perror(""); 333 exit(FATAL_EXIT); 334 } 335 336 /*NOTREACHED*/ 337 } 338 339 /* 340 * Callback function for nftw() 341 */ 342 static int 343 walker(const char *name, const struct stat64 *sp, int type, struct FTW *ftwx) 344 { 345 int ret; 346 struct statvfs64 path_vfs; 347 boolean_t dir_flag = B_FALSE; 348 struct rule *rule; 349 350 switch (type) { 351 case FTW_F: /* file */ 352 rule = check_rules(name, 'F'); 353 if (rule != NULL) { 354 if (rule->attr_list & ATTR_CONTENTS) 355 compute_chksum = 1; 356 else 357 compute_chksum = 0; 358 } 359 break; 360 case FTW_SL: /* symbolic link */ 361 case FTW_DP: /* end of directory */ 362 case FTW_DNR: /* unreadable directory */ 363 case FTW_NS: /* unstatable file */ 364 break; 365 case FTW_D: /* enter directory */ 366 dir_flag = B_TRUE; 367 ret = statvfs64(name, &path_vfs); 368 if (ret < 0) 369 eval_err = WARNING_EXIT; 370 break; 371 default: 372 (void) fprintf(stderr, INVALID_FILE, name); 373 eval_err = WARNING_EXIT; 374 break; 375 } 376 377 /* This is the function which really processes the file */ 378 ret = eval_file(name, sp); 379 380 /* 381 * Since the parameters to walker() are constrained by nftw(), 382 * need to use a global to reflect a WARNING. Sigh. 383 */ 384 if (ret == WARNING_EXIT) 385 eval_err = WARNING_EXIT; 386 387 /* 388 * This is a case of a directory which crosses into a mounted 389 * filesystem of a different type, e.g., UFS -> NFS. 390 * BART should not walk the new filesystem (by specification), so 391 * set this consolidation-private flag so the rest of the subtree 392 * under this directory is not waled. 393 */ 394 if (dir_flag && 395 (strcmp(parent_vfs.f_basetype, path_vfs.f_basetype) != 0)) 396 ftwx->quit = FTW_PRUNE; 397 398 return (0); 399 } 400 401 /* 402 * This file does the per-file evaluation and is run to generate every entry 403 * in the manifest. 404 * 405 * All output is written to a pipe which is read by the child process, 406 * which is running output_manifest(). 407 */ 408 static int 409 eval_file(const char *fname, const struct stat64 *statb) 410 { 411 int fd, ret, err_code, i; 412 char last_field[PATH_MAX], ftype, *acl_str; 413 char *quoted_name; 414 415 err_code = EXIT; 416 417 switch (statb->st_mode & S_IFMT) { 418 /* Regular file */ 419 case S_IFREG: ftype = 'F'; break; 420 421 /* Directory */ 422 case S_IFDIR: ftype = 'D'; break; 423 424 /* Block Device */ 425 case S_IFBLK: ftype = 'B'; break; 426 427 /* Character Device */ 428 case S_IFCHR: ftype = 'C'; break; 429 430 /* Named Pipe */ 431 case S_IFIFO: ftype = 'P'; break; 432 433 /* Socket */ 434 case S_IFSOCK: ftype = 'S'; break; 435 436 /* Door */ 437 case S_IFDOOR: ftype = 'O'; break; 438 439 /* Symbolic link */ 440 case S_IFLNK: ftype = 'L'; break; 441 442 default: ftype = '-'; break; 443 } 444 445 /* First, make sure this file should be cataloged */ 446 447 if ((subtree_root != NULL) && 448 (exclude_fname(fname, ftype, subtree_root))) 449 return (err_code); 450 451 for (i = 0; i < PATH_MAX; i++) 452 last_field[i] = '\0'; 453 454 /* 455 * Regular files, compute the MD5 checksum and put it into 'last_field' 456 * UNLESS instructed to ignore the checksums. 457 */ 458 if (ftype == 'F') { 459 if (compute_chksum) { 460 fd = open(fname, O_RDONLY|O_LARGEFILE); 461 if (fd < 0) { 462 err_code = WARNING_EXIT; 463 perror(fname); 464 465 /* default value since the computution failed */ 466 (void) strcpy(last_field, "-"); 467 } else { 468 if (generate_hash(fd, last_field) != 0) { 469 err_code = WARNING_EXIT; 470 (void) fprintf(stderr, CONTENTS_WARN, 471 fname); 472 (void) strcpy(last_field, "-"); 473 } 474 } 475 (void) close(fd); 476 } 477 /* Instructed to ignore checksums, just put in a '-' */ 478 else 479 (void) strcpy(last_field, "-"); 480 } 481 482 /* 483 * For symbolic links, put the destination of the symbolic link into 484 * 'last_field' 485 */ 486 if (ftype == 'L') { 487 ret = readlink(fname, last_field, sizeof (last_field)); 488 if (ret < 0) { 489 err_code = WARNING_EXIT; 490 perror(fname); 491 492 /* default value since the computation failed */ 493 (void) strcpy(last_field, "-"); 494 } 495 else 496 (void) strlcpy(last_field, 497 sanitized_fname(last_field, B_FALSE), 498 sizeof (last_field)); 499 500 /* 501 * Boundary condition: possible for a symlink to point to 502 * nothing [ ln -s '' link_name ]. For this case, set the 503 * destination to "\000". 504 */ 505 if (strlen(last_field) == 0) 506 (void) strcpy(last_field, "\\000"); 507 } 508 509 acl_str = get_acl_string(fname, statb, &err_code); 510 511 /* Sanitize 'fname', so its in the proper format for the manifest */ 512 quoted_name = sanitized_fname(fname, B_TRUE); 513 514 /* Start to build the entry.... */ 515 (void) printf("%s %c %d %o %s %x %d %d", quoted_name, ftype, 516 (int)statb->st_size, (int)statb->st_mode, acl_str, 517 (int)statb->st_mtime, (int)statb->st_uid, (int)statb->st_gid); 518 519 /* Finish it off based upon whether or not it's a device node */ 520 if ((ftype == 'B') || (ftype == 'C')) 521 (void) printf(" %x\n", (int)statb->st_rdev); 522 else if (strlen(last_field) > 0) 523 (void) printf(" %s\n", last_field); 524 else 525 (void) printf("\n"); 526 527 /* free the memory consumed */ 528 free(acl_str); 529 free(quoted_name); 530 531 return (err_code); 532 } 533 534 /* 535 * When creating a manifest, make sure all '?', tabs, space, newline, '/' 536 * and '[' are all properly quoted. Convert them to a "\ooo" where the 'ooo' 537 * represents their octal value. For filesystem objects, as opposed to symlink 538 * targets, also canonicalize the pathname. 539 */ 540 static char * 541 sanitized_fname(const char *fname, boolean_t canon_path) 542 { 543 const char *ip; 544 unsigned char ch; 545 char *op, *quoted_name; 546 547 /* Initialize everything */ 548 quoted_name = safe_calloc((4 * PATH_MAX) + 1); 549 ip = fname; 550 op = quoted_name; 551 552 if (canon_path) { 553 /* 554 * In the case when a relocatable root was used, the relocatable 555 * root should *not* be part of the manifest. 556 */ 557 ip += strlen(reloc_root); 558 559 /* 560 * In the case when the '-I' option was used, make sure 561 * the quoted_name starts with a '/'. 562 */ 563 if (*ip != '/') 564 *op++ = '/'; 565 } 566 567 /* Now walk through 'fname' and build the quoted string */ 568 while ((ch = *ip++) != 0) { 569 switch (ch) { 570 /* Quote the following characters */ 571 case ' ': 572 case '*': 573 case '\n': 574 case '?': 575 case '[': 576 case '\\': 577 case '\t': 578 op += sprintf(op, "\\%.3o", (unsigned char)ch); 579 break; 580 581 /* Otherwise, simply append them */ 582 default: 583 *op++ = ch; 584 break; 585 } 586 } 587 588 *op = 0; 589 590 return (quoted_name); 591 } 592 593 /* 594 * Function responsible for generating the ACL information for a given 595 * file. Note, the string is put into buffer malloc'd by this function. 596 * It's the responsibility of the caller to free the buffer. This function 597 * should never return a NULL pointer. 598 */ 599 static char * 600 get_acl_string(const char *fname, const struct stat64 *statb, int *err_code) 601 { 602 acl_t *aclp; 603 char *acltext; 604 int error; 605 606 if (S_ISLNK(statb->st_mode)) { 607 return (safe_strdup("-")); 608 } 609 610 /* 611 * Include trivial acl's 612 */ 613 error = acl_get(fname, 0, &aclp); 614 615 if (error != 0) { 616 *err_code = WARNING_EXIT; 617 (void) fprintf(stderr, "%s: %s\n", fname, acl_strerror(error)); 618 return (safe_strdup("-")); 619 } else { 620 acltext = acl_totext(aclp, 0); 621 acl_free(aclp); 622 if (acltext == NULL) 623 return (safe_strdup("-")); 624 else 625 return (acltext); 626 } 627 } 628 629 630 /* 631 * 632 * description: This routine reads stdin in BUF_SIZE chunks, uses the bits 633 * to update the md5 hash buffer, and outputs the chunks 634 * to stdout. When stdin is exhausted, the hash is computed, 635 * converted to a hexadecimal string, and returned. 636 * 637 * returns: The md5 hash of stdin, or NULL if unsuccessful for any reason. 638 */ 639 static int 640 generate_hash(int fdin, char *hash_str) 641 { 642 unsigned char buf[BUF_SIZE]; 643 unsigned char hash[MD5_DIGEST_LENGTH]; 644 int i, amtread; 645 MD5_CTX ctx; 646 647 MD5Init(&ctx); 648 649 for (;;) { 650 amtread = read(fdin, buf, sizeof (buf)); 651 if (amtread == 0) 652 break; 653 if (amtread < 0) 654 return (1); 655 656 /* got some data. Now update hash */ 657 MD5Update(&ctx, buf, amtread); 658 } 659 660 /* done passing through data, calculate hash */ 661 MD5Final(hash, &ctx); 662 663 for (i = 0; i < MD5_DIGEST_LENGTH; i++) 664 (void) sprintf(hash_str + (i*2), "%2.2x", hash[i]); 665 666 return (0); 667 } 668 669 /* 670 * Used by 'bart create' with the '-I' option. Return each entry into a 'buf' 671 * with the appropriate exit code: '0' for success and '-1' for failure. 672 */ 673 static int 674 read_filelist(char *reloc_root, char **argv, char *buf, size_t bufsize) 675 { 676 static int argv_index = -1; 677 static boolean_t read_stdinput = B_FALSE; 678 char temp_buf[PATH_MAX]; 679 char *cp; 680 681 /* 682 * INITIALIZATION: 683 * Setup this code so it knows whether or not to read sdtin. 684 * Also, if reading from argv, setup the index, "argv_index" 685 */ 686 if (argv_index == -1) { 687 argv_index = 0; 688 689 /* In this case, no args after '-I', so read stdin */ 690 if (argv[0] == NULL) 691 read_stdinput = B_TRUE; 692 } 693 694 buf[0] = '\0'; 695 696 if (read_stdinput) { 697 if (fgets(temp_buf, PATH_MAX, stdin) == NULL) 698 return (-1); 699 cp = strtok(temp_buf, "\n"); 700 } else { 701 cp = argv[argv_index++]; 702 } 703 704 if (cp == NULL) 705 return (-1); 706 707 /* 708 * Unlike similar code elsewhere, avoid adding a leading 709 * slash for relative pathnames. 710 */ 711 (void) snprintf(buf, bufsize, 712 (reloc_root[0] == '\0' || cp[0] == '/') ? "%s%s" : "%s/%s", 713 reloc_root, cp); 714 715 return (0); 716 } 717