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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. 23 */ 24 25 /* 26 * Copyright (c) 1988 AT&T 27 * All Rights Reserved 28 * 29 */ 30 31 /* 32 * Incompatible Archive Header 33 * 34 * The archive file member header used in SunOS 4.1 archive files and 35 * Solaris archive files are incompatible. The header file is: 36 * /usr/include/ar.h, struct ar_hdr. 37 * The member ar_name[] in Solaris comforms with Standard and the 38 * member name terminates with '/'. The SunOS's member does not terminate 39 * with '/' character. A bug 4046054 was filed: 40 * The ar command in Solaris 2.5.1 is incompatible with archives 41 * created on 4.x. 42 * 43 * To handle archive files created in SunOS 4.1 system on Solaris, the 44 * following changes were made: 45 * 46 * 1. file.c/writefile() 47 * Before writing each member files into the output 48 * archive file, ar_name[] is checked. If it is NULL, 49 * it means that the original archive header for this 50 * member was incompatible with Solaris format. 51 * 52 * The original Solaris ar command ended up having 53 * NULL name for the header. The change here uses the 54 * ar_rawname, which is much closer to the original 55 * name. 56 * 57 * 2. cmd.c 58 * For the p command, the code used to use only ar_longname 59 * to seach the matching name. The member is set to NULL 60 * if the archive member header was incompatible. 61 * The ar_rawname is also used to find the matching member name. 62 * 63 * For commands to update the archive file, we do not 64 * use ar_rawname, and just use the ar_longname. The commands are 65 * r (replace), m (modify the position) and d (delete). 66 */ 67 68 #include "inc.h" 69 70 /* 71 * Forward Declarations 72 */ 73 static void ar_select(int *, unsigned long); 74 static void cleanup(Cmd_info *); 75 static int create_extract(ARFILE *, int, int, Cmd_info *); 76 static char *match(char *, Cmd_info *); 77 static void mesg(int, char *, Cmd_info *); 78 static void movefil(ARFILE *, struct stat *); 79 static FILE *stats(char *, struct stat *); 80 81 /* 82 * Commands 83 */ 84 void 85 rcmd(Cmd_info *cmd_info) 86 { 87 FILE *f; 88 ARFILE *fileptr; 89 ARFILE *abifile = NULL; 90 ARFILE *backptr = NULL; 91 ARFILE *endptr; 92 ARFILE *moved_files; 93 ARFILE *prev_entry, *new_listhead, *new_listend; 94 int deleted; 95 struct stat stbuf; 96 char *gfile; 97 98 new_listhead = NULL; 99 new_listend = NULL; 100 prev_entry = NULL; 101 102 for (fileptr = getfile(cmd_info); 103 fileptr; fileptr = getfile(cmd_info)) { 104 deleted = 0; 105 if (!abifile && cmd_info->ponam && 106 strcmp(fileptr->ar_longname, cmd_info->ponam) == 0) 107 abifile = fileptr; 108 else if (!abifile) 109 backptr = fileptr; 110 111 if (cmd_info->namc == 0 || 112 (gfile = match(fileptr->ar_longname, cmd_info)) != NULL) { 113 /* 114 * NOTE: 115 * Refer to "Incompatible Archive Header" 116 * blocked comment at the beginning of this file. 117 */ 118 f = stats(gfile, &stbuf); /* gfile is set by match */ 119 if (f == NULL) { 120 if (cmd_info->namc) { 121 int err = errno; 122 (void) fprintf(stderr, 123 MSG_INTL(MSG_SYS_OPEN), 124 gfile, strerror(err)); 125 } 126 /* 127 * Created 128 */ 129 mesg('c', gfile, cmd_info); 130 } else { 131 if ((cmd_info->opt_flgs & u_FLAG) && 132 stbuf.st_mtime <= fileptr->ar_date) { 133 (void) fclose(f); 134 continue; 135 } 136 /* 137 * Replaced 138 */ 139 mesg('r', fileptr->ar_longname, cmd_info); 140 movefil(fileptr, &stbuf); 141 /* 142 * Clear the previous contents. 143 */ 144 if (fileptr->ar_flag & F_ELFRAW) { 145 /* 146 * clear ar_elf 147 */ 148 (void) elf_end(fileptr->ar_elf); 149 fileptr->ar_elf = 0; 150 } 151 /* clear 'ar_flag' */ 152 fileptr->ar_flag &= ~F_ELFRAW; 153 154 /* 155 * Defer reading contents until needed, and 156 * then use an in-kernel file-to-file transfer 157 * to avoid excessive in-process memory use. 158 */ 159 fileptr->ar_contents = NULL; 160 161 if (fileptr->ar_pathname != NULL) 162 free(fileptr->ar_pathname); 163 if ((fileptr->ar_pathname = 164 malloc(strlen(gfile) + 1)) == NULL) { 165 int err = errno; 166 (void) fprintf(stderr, 167 MSG_INTL(MSG_MALLOC), 168 strerror(err)); 169 exit(1); 170 } 171 172 (void) strcpy(fileptr->ar_pathname, gfile); 173 (void) fclose(f); 174 175 if (cmd_info->ponam && (abifile != fileptr)) { 176 deleted = 1; 177 /* remove from archive list */ 178 if (prev_entry != NULL) 179 prev_entry->ar_next = NULL; 180 else 181 listhead = NULL; 182 listend = prev_entry; 183 184 /* add to moved list */ 185 if (new_listhead == NULL) 186 new_listhead = fileptr; 187 else 188 new_listend->ar_next = fileptr; 189 new_listend = fileptr; 190 } 191 cmd_info->modified++; 192 } 193 } 194 else 195 /* 196 * Unchaged 197 */ 198 mesg('u', fileptr->ar_longname, cmd_info); 199 200 if (deleted) 201 deleted = 0; 202 else 203 prev_entry = fileptr; 204 } 205 206 endptr = listend; 207 cleanup(cmd_info); 208 if (cmd_info->ponam && endptr && 209 (((moved_files = endptr->ar_next) != NULL) || new_listhead)) { 210 if (!abifile) { 211 (void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_POSNAM), 212 cmd_info->ponam); 213 exit(2); 214 } 215 endptr->ar_next = NULL; 216 217 /* 218 * link new/moved files into archive entry list... 219 * 1: prepend newlist to moved/appended list 220 */ 221 if (new_listhead) { 222 if (!moved_files) 223 listend = new_listend; 224 new_listend->ar_next = moved_files; 225 moved_files = new_listhead; 226 } 227 /* 2: insert at appropriate position... */ 228 if (cmd_info->opt_flgs & b_FLAG) 229 abifile = backptr; 230 if (abifile) { 231 listend->ar_next = abifile->ar_next; 232 abifile->ar_next = moved_files; 233 } else { 234 listend->ar_next = listhead; 235 listhead = moved_files; 236 } 237 listend = endptr; 238 } else if (cmd_info->ponam && !abifile) 239 (void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_POSNAM), 240 cmd_info->ponam); 241 } 242 243 void 244 dcmd(Cmd_info *cmd_info) 245 { 246 ARFILE *fptr; 247 ARFILE *backptr = NULL; 248 249 for (fptr = getfile(cmd_info); fptr; fptr = getfile(cmd_info)) { 250 if (match(fptr->ar_longname, cmd_info) != NULL) { 251 /* 252 * NOTE: 253 * Refer to "Incompatible Archive Header" 254 * blocked comment at the beginning of this file. 255 */ 256 257 /* 258 * Deleted 259 */ 260 mesg('d', fptr->ar_longname, cmd_info); 261 if (backptr == NULL) { 262 listhead = NULL; 263 listend = NULL; 264 } else { 265 backptr->ar_next = NULL; 266 listend = backptr; 267 } 268 cmd_info->modified = 1; 269 } else { 270 /* 271 * Unchaged 272 */ 273 mesg('u', fptr->ar_longname, cmd_info); 274 backptr = fptr; 275 } 276 } 277 } 278 279 void 280 xcmd(Cmd_info *cmd_info) 281 { 282 int f; 283 ARFILE *next; 284 int rawname = 0; 285 long f_len = 0; 286 287 /* 288 * If -T is specified, get the maximum file name length. 289 */ 290 if (cmd_info->opt_flgs & T_FLAG) { 291 f_len = pathconf(MSG_ORIG(MSG_STR_PERIOD), _PC_NAME_MAX); 292 if (f_len == -1) { 293 int err = errno; 294 (void) fprintf(stderr, MSG_INTL(MSG_PATHCONF), 295 strerror(err)); 296 exit(1); 297 } 298 } 299 for (next = getfile(cmd_info); next; next = getfile(cmd_info)) { 300 if ((next->ar_longname[0] == 0) && (next->ar_rawname[0] != 0)) 301 rawname = 1; 302 if (cmd_info->namc == 0 || 303 match(next->ar_longname, cmd_info) != NULL || 304 match(next->ar_rawname, cmd_info) != NULL) { 305 /* 306 * NOTE: 307 * Refer to "Incompatible Archive Header" 308 * blocked comment at the beginning of this file. 309 */ 310 f = create_extract(next, rawname, f_len, cmd_info); 311 if (f >= 0) { 312 if (rawname) { 313 /* 314 * eXtracted 315 */ 316 mesg('x', next->ar_rawname, cmd_info); 317 if (write(f, next->ar_contents, 318 (unsigned)next->ar_size) != 319 next->ar_size) { 320 int err = errno; 321 (void) fprintf(stderr, 322 MSG_INTL(MSG_SYS_WRITE), 323 next->ar_rawname, 324 strerror(err)); 325 exit(1); 326 } 327 } else { 328 /* 329 * eXtracted 330 */ 331 mesg('x', next->ar_longname, cmd_info); 332 if (write(f, next->ar_contents, 333 (unsigned)next->ar_size) != 334 next->ar_size) { 335 int err = errno; 336 (void) fprintf(stderr, 337 MSG_INTL(MSG_SYS_WRITE), 338 next->ar_longname, 339 strerror(err)); 340 exit(1); 341 } 342 } 343 (void) close(f); 344 } else 345 exit(1); 346 } 347 rawname = 0; 348 } /* for */ 349 } 350 351 void 352 pcmd(Cmd_info *cmd_info) 353 { 354 ARFILE *next; 355 356 for (next = getfile(cmd_info); next; next = getfile(cmd_info)) { 357 if (cmd_info->namc == 0 || 358 match(next->ar_longname, cmd_info) != NULL || 359 match(next->ar_rawname, cmd_info) != NULL) { 360 /* 361 * NOTE: 362 * Refer to "Incompatible Archive Header" 363 * blocked comment at the beginning of this file. 364 */ 365 if (cmd_info->opt_flgs & v_FLAG) { 366 (void) fprintf(stdout, 367 MSG_ORIG(MSG_FMT_P_TITLE), 368 next->ar_longname); 369 (void) fflush(stdout); 370 } 371 (void) fwrite(next->ar_contents, sizeof (char), 372 next->ar_size, stdout); 373 } 374 } 375 } 376 377 void 378 mcmd(Cmd_info *cmd_info) 379 { 380 ARFILE *fileptr; 381 ARFILE *abifile = NULL; 382 ARFILE *tmphead = NULL; 383 ARFILE *tmpend = NULL; 384 ARFILE *backptr1 = NULL; 385 ARFILE *backptr2 = NULL; 386 387 for (fileptr = getfile(cmd_info); 388 fileptr; fileptr = getfile(cmd_info)) { 389 if (match(fileptr->ar_longname, cmd_info) != NULL) { 390 /* 391 * position Modified 392 */ 393 mesg('m', fileptr->ar_longname, cmd_info); 394 if (tmphead) 395 tmpend->ar_next = fileptr; 396 else 397 tmphead = fileptr; 398 tmpend = fileptr; 399 if (backptr1) { 400 listend = backptr1; 401 listend->ar_next = NULL; 402 } 403 else 404 listhead = NULL; 405 continue; 406 } 407 /* 408 * position Unchaged 409 */ 410 mesg('u', fileptr->ar_longname, cmd_info); 411 backptr1 = fileptr; 412 if (cmd_info->ponam && !abifile) { 413 if (strcmp(fileptr->ar_longname, cmd_info->ponam) == 0) 414 abifile = fileptr; 415 else 416 backptr2 = fileptr; 417 } 418 } 419 420 if (!tmphead) 421 return; 422 423 if (!cmd_info->ponam) 424 listend->ar_next = tmphead; 425 else { 426 if (!abifile) { 427 (void) fprintf(stderr, MSG_INTL(MSG_NOT_FOUND_POSNAM), 428 cmd_info->ponam); 429 exit(2); 430 } 431 if (cmd_info->opt_flgs & b_FLAG) 432 abifile = backptr2; 433 if (abifile) { 434 tmpend->ar_next = abifile->ar_next; 435 abifile->ar_next = tmphead; 436 } else { 437 tmphead->ar_next = listhead; 438 listhead = tmphead; 439 } 440 } 441 (cmd_info->modified)++; 442 } 443 444 void 445 tcmd(Cmd_info *cmd_info) 446 { 447 ARFILE *next; 448 int **mp; 449 char buf[DATESIZE]; 450 int m1[] = {1, S_IRUSR, 'r', '-'}; 451 int m2[] = {1, S_IWUSR, 'w', '-'}; 452 int m3[] = {2, S_ISUID, 's', S_IXUSR, 'x', '-'}; 453 int m4[] = {1, S_IRGRP, 'r', '-'}; 454 int m5[] = {1, S_IWGRP, 'w', '-'}; 455 int m6[] = {2, S_ISGID, 's', S_IXGRP, 'x', '-'}; 456 int m7[] = {1, S_IROTH, 'r', '-'}; 457 int m8[] = {1, S_IWOTH, 'w', '-'}; 458 int m9[] = {2, S_ISVTX, 't', S_IXOTH, 'x', '-'}; 459 int *m[10]; 460 461 m[0] = m1; 462 m[1] = m2; 463 m[2] = m3; 464 m[3] = m4; 465 m[4] = m5; 466 m[5] = m6; 467 m[6] = m7; 468 m[7] = m8; 469 m[8] = m9; 470 m[9] = 0; 471 472 for (next = getfile(cmd_info); next; next = getfile(cmd_info)) { 473 if (cmd_info->namc == 0 || 474 match(next->ar_longname, cmd_info) != NULL || 475 match(next->ar_rawname, cmd_info) != NULL) { 476 /* 477 * NOTE: 478 * Refer to "Incompatible Archive Header" 479 * blocked comment at the beginning of this file. 480 */ 481 if (cmd_info->opt_flgs & v_FLAG) { 482 for (mp = &m[0]; mp < &m[9]; ) 483 ar_select(*mp++, next->ar_mode); 484 485 (void) fprintf(stdout, MSG_ORIG(MSG_FMT_T_IDSZ), 486 next->ar_uid, next->ar_gid, 487 EC_XWORD(next->ar_size)); 488 if ((strftime(buf, 489 DATESIZE, MSG_ORIG(MSG_FMT_T_DATE), 490 localtime(&(next->ar_date)))) == 0) { 491 (void) fprintf(stderr, 492 MSG_INTL(MSG_LOCALTIME)); 493 exit(1); 494 } 495 (void) fprintf(stdout, 496 MSG_ORIG(MSG_FMT_SPSTRSP), buf); 497 } 498 if ((next->ar_longname[0] == 0) && 499 (next->ar_rawname[0] != 0)) 500 (void) fprintf(stdout, 501 MSG_ORIG(MSG_FMT_STRNL), 502 trim(next->ar_rawname)); 503 else 504 (void) fprintf(stdout, 505 MSG_ORIG(MSG_FMT_STRNL), 506 trim(next->ar_longname)); 507 } 508 } /* for */ 509 } 510 511 void 512 qcmd(Cmd_info *cmd_info) 513 { 514 ARFILE *fptr; 515 516 if (cmd_info->opt_flgs & (a_FLAG | b_FLAG)) { 517 (void) fprintf(stderr, MSG_INTL(MSG_USAGE_05)); 518 exit(1); 519 } 520 for (fptr = getfile(cmd_info); fptr; fptr = getfile(cmd_info)) 521 ; 522 cleanup(cmd_info); 523 } 524 525 /* 526 * Supplementary functions 527 */ 528 static char * 529 match(char *file, Cmd_info *cmd_info) 530 { 531 int i; 532 533 for (i = 0; i < cmd_info->namc; i++) { 534 if (cmd_info->namv[i] == 0) 535 continue; 536 if (strcmp(trim(cmd_info->namv[i]), file) == 0) { 537 file = cmd_info->namv[i]; 538 cmd_info->namv[i] = 0; 539 return (file); 540 } 541 } 542 return (NULL); 543 } 544 545 /* 546 * puts the file which was in the list in the linked list 547 */ 548 static void 549 cleanup(Cmd_info *cmd_info) 550 { 551 int i; 552 FILE *f; 553 ARFILE *fileptr; 554 struct stat stbuf; 555 556 for (i = 0; i < cmd_info->namc; i++) { 557 if (cmd_info->namv[i] == 0) 558 continue; 559 /* 560 * Appended 561 */ 562 mesg('a', cmd_info->namv[i], cmd_info); 563 f = stats(cmd_info->namv[i], &stbuf); 564 if (f == NULL) { 565 int err = errno; 566 (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), 567 cmd_info->namv[i], strerror(err)); 568 } else { 569 fileptr = newfile(); 570 /* if short name */ 571 (void) strncpy(fileptr->ar_name, 572 trim(cmd_info->namv[i]), SNAME); 573 574 if ((fileptr->ar_longname = 575 malloc(strlen(trim(cmd_info->namv[i])) + 1)) == 576 NULL) { 577 int err = errno; 578 (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), 579 strerror(err)); 580 exit(1); 581 } 582 583 (void) strcpy(fileptr->ar_longname, 584 trim(cmd_info->namv[i])); 585 586 if ((fileptr->ar_pathname = 587 malloc(strlen(cmd_info->namv[i]) + 1)) == NULL) { 588 int err = errno; 589 (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), 590 strerror(err)); 591 exit(1); 592 } 593 594 (void) strcpy(fileptr->ar_pathname, cmd_info->namv[i]); 595 596 movefil(fileptr, &stbuf); 597 598 /* clear 'ar_flag' */ 599 fileptr->ar_flag &= ~F_ELFRAW; 600 601 /* 602 * Defer reading contents until needed, and then use 603 * an in-kernel file-to-file transfer to avoid 604 * excessive in-process memory use. 605 */ 606 fileptr->ar_contents = NULL; 607 608 (void) fclose(f); 609 (cmd_info->modified)++; 610 cmd_info->namv[i] = 0; 611 } 612 } 613 } 614 615 /* 616 * insert the file 'file' into the temporary file 617 */ 618 static void 619 movefil(ARFILE *fileptr, struct stat *stbuf) 620 { 621 fileptr->ar_size = stbuf->st_size; 622 fileptr->ar_date = stbuf->st_mtime; 623 fileptr->ar_mode = stbuf->st_mode; 624 625 /* 626 * The format of an 'ar' file includes a 6 character 627 * decimal string to contain the uid. 628 * 629 * If the uid or gid is too big to fit, then set it to 630 * nobody (for want of a better value). Clear the 631 * setuid/setgid bits in the mode to avoid setuid nobody 632 * or setgid nobody files unexpectedly coming into existence. 633 */ 634 if ((fileptr->ar_uid = stbuf->st_uid) > 999999) { 635 fileptr->ar_uid = UID_NOBODY; 636 if (S_ISREG(fileptr->ar_mode)) 637 fileptr->ar_mode &= ~S_ISUID; 638 } 639 if ((fileptr->ar_gid = stbuf->st_gid) > 999999) { 640 fileptr->ar_gid = GID_NOBODY; 641 if (S_ISREG(fileptr->ar_mode)) 642 fileptr->ar_mode &= ~S_ISGID; 643 } 644 } 645 646 static FILE * 647 stats(char *file, struct stat *stbuf) 648 { 649 FILE *f; 650 651 f = fopen(file, MSG_ORIG(MSG_STR_LCR)); 652 if (f == NULL) 653 return (f); 654 if (stat(file, stbuf) < 0) { 655 (void) fclose(f); 656 return (NULL); 657 } 658 return (f); 659 } 660 661 /* 662 * Used by xcmd() 663 */ 664 int 665 create_extract(ARFILE *a, int rawname, int f_len, Cmd_info *cmd_info) 666 { 667 668 int f; 669 char *f_name; 670 char *dup = NULL; 671 if (rawname) 672 f_name = a->ar_rawname; 673 else 674 f_name = a->ar_longname; 675 676 /* 677 * If -T is specified, check the file length. 678 */ 679 if (cmd_info->opt_flgs & T_FLAG) { 680 int len; 681 len = strlen(f_name); 682 if (f_len <= len) { 683 dup = malloc(f_len+1); 684 if (dup == NULL) { 685 int err = errno; 686 (void) fprintf(stderr, MSG_INTL(MSG_MALLOC), 687 strerror(err)); 688 exit(1); 689 } 690 (void) strncpy(dup, f_name, f_len); 691 } 692 f_name = dup; 693 } 694 695 /* 696 * Bug 4052067 - If a file to be extracted has the same 697 * filename as the archive, the archive gets overwritten 698 * which can lead to a corrupted archive or worse, a ufs 699 * deadlock because libelf has mmap'ed the archive! We 700 * can't rely on strcmp() to test for this case because 701 * the archive could be prefixed with a partial or full 702 * path (and we could be using the rawname from the archive) 703 * This means we have to do the same thing we did for mv, 704 * which is to explicitly check if the file we would extract 705 * to is identical to the archive. Because part of this 706 * test is essentially what the -C flag does, I've merged 707 * the code together. 708 */ 709 if (access(f_name, F_OK) != -1) { 710 struct stat s1, s2; 711 712 /* 713 * If -C is specified, this is an error anyway 714 */ 715 if (cmd_info->opt_flgs & C_FLAG) { 716 (void) fprintf(stderr, MSG_INTL(MSG_OVERRIDE_WARN), 717 f_name); 718 if (dup != NULL) 719 free(dup); 720 return (-1); 721 } 722 723 /* 724 * Okay, -C wasn't specified. However, now we do 725 * the check to see if the archive would be overwritten 726 * by extracting this file. stat() both objects and 727 * test to see if their identical. 728 */ 729 if ((stat(f_name, &s1) == 0) && 730 (stat(cmd_info->arnam, &s2) == 0)) { 731 732 if ((s1.st_dev == s2.st_dev) && 733 (s1.st_ino == s2.st_ino)) { 734 735 (void) fprintf(stderr, 736 MSG_INTL(MSG_OVERRIDE_WARN), f_name); 737 if (dup != NULL) 738 free(dup); 739 return (-1); 740 } 741 } 742 } 743 744 /* 745 * Okay to create extraction file... 746 */ 747 f = creat(f_name, (mode_t)a->ar_mode & 0777); 748 if (f < 0) { 749 int err = errno; 750 (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), f_name, 751 strerror(err)); 752 /* 753 * Created 754 */ 755 mesg('c', f_name, cmd_info); 756 } 757 if (dup) 758 free(dup); 759 return (f); 760 } 761 762 static void 763 mesg(int c, char *file, Cmd_info *cmd_info) 764 { 765 #ifdef XPG4 766 /* 767 * XPG4 does not have any message defined for 768 * 'c' operation. 769 * In fact, XPG only defines messages for 770 * d, r, a and x at the present. (03/05/'96) 771 */ 772 if (c == 'c' || c == 'u' || c == 'm') 773 return; 774 #endif 775 /* 776 * If 'u' is passed, convert it to 'c'. 777 * 'u' makes more sense since the operation did not 778 * do anything, Unchanged, but 'c' has been used so 779 * I do no want to break the compatibility at this moment. 780 * (03/05/'96). 781 */ 782 if (c == 'u') 783 c = 'c'; 784 if (cmd_info->opt_flgs & v_FLAG) 785 if (c != 'c') 786 (void) fprintf(stdout, MSG_ORIG(MSG_FMT_FILE), c, file); 787 } 788 789 static void 790 ar_select(int *pairp, unsigned long mode) 791 { 792 int n, *ap; 793 794 ap = pairp; 795 n = *ap++; 796 while (--n >= 0 && (mode & *ap++) == 0) 797 ap++; 798 (void) putchar(*ap); 799 } 800