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 <stdio.h> 27 #include <stdlib.h> 28 #include <ctype.h> 29 #include <unistd.h> 30 #include <sys/sysmacros.h> 31 #include <libintl.h> 32 #include <wait.h> 33 #include <string.h> 34 #include <strings.h> 35 #include <errno.h> 36 #include <fcntl.h> 37 #include <signal.h> 38 #include <sys/buf.h> 39 #include <sys/stat.h> 40 #include <grp.h> 41 #include "addrem.h" 42 #include "errmsg.h" 43 #include "plcysubr.h" 44 45 static char *add_rem_lock; /* lock file */ 46 static char *tmphold; /* temperary file for updating */ 47 static int add_rem_lock_fd = -1; 48 49 static int get_cached_n_to_m_file(char *filename, char ***cache); 50 static int get_name_to_major_entry(int *major_no, char *driver_name, 51 char *file_name); 52 53 static int is_blank(char *); 54 55 /*ARGSUSED*/ 56 void 57 log_minorperm_error(minorperm_err_t err, int key) 58 { 59 switch (err) { 60 case MP_FOPEN_ERR: 61 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 62 MINOR_PERM_FILE); 63 break; 64 case MP_FCLOSE_ERR: 65 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 66 MINOR_PERM_FILE); 67 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 68 break; 69 case MP_IGNORING_LINE_ERR: 70 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 71 MINOR_PERM_FILE); 72 break; 73 case MP_ALLOC_ERR: 74 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 75 MINOR_PERM_FILE); 76 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 77 break; 78 case MP_NVLIST_ERR: 79 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 80 MINOR_PERM_FILE); 81 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 82 break; 83 case MP_CANT_FIND_USER_ERR: 84 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 85 MINOR_PERM_FILE); 86 break; 87 case MP_CANT_FIND_GROUP_ERR: 88 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 89 MINOR_PERM_FILE); 90 break; 91 } 92 } 93 94 /* 95 * open file 96 * for each entry in list 97 * where list entries are separated by <list_separator> 98 * append entry : driver_name <entry_separator> entry 99 * close file 100 * return error/noerr 101 */ 102 int 103 append_to_file( 104 char *driver_name, 105 char *entry_list, 106 char *filename, 107 char list_separator, 108 char *entry_separator, 109 int quoted) 110 { 111 int i, len, line_len; 112 int fpint; 113 char *current_head, *previous_head; 114 char *line, *one_entry; 115 FILE *fp; 116 117 if ((fp = fopen(filename, "a")) == NULL) { 118 perror(NULL); 119 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 120 filename); 121 return (ERROR); 122 } 123 124 len = strlen(entry_list); 125 126 one_entry = calloc(len + 1, 1); 127 if (one_entry == NULL) { 128 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), filename); 129 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 130 (void) fclose(fp); 131 return (ERROR); 132 } 133 134 previous_head = entry_list; 135 136 line_len = strlen(driver_name) + len + 4; 137 if (quoted) 138 line_len += 2; 139 140 line = calloc(line_len, 1); 141 if (line == NULL) { 142 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 143 (void) fclose(fp); 144 err_exit(); 145 } 146 147 /* 148 * get one entry at a time from list and append to <filename> file 149 */ 150 151 do { 152 153 for (i = 0; i <= len; i++) 154 one_entry[i] = 0; 155 156 bzero(line, line_len); 157 158 current_head = get_entry(previous_head, one_entry, 159 list_separator, quoted); 160 previous_head = current_head; 161 162 (void) strlcpy(line, driver_name, line_len); 163 (void) strlcat(line, entry_separator, line_len); 164 if (quoted) 165 (void) strlcat(line, "\"", line_len); 166 (void) strlcat(line, one_entry, line_len); 167 if (quoted) 168 (void) strlcat(line, "\"", line_len); 169 (void) strlcat(line, "\n", line_len); 170 171 if ((fputs(line, fp)) == EOF) { 172 perror(NULL); 173 (void) fprintf(stderr, gettext(ERR_NO_UPDATE), 174 filename); 175 } 176 177 } while (*current_head != '\0'); 178 179 180 (void) fflush(fp); 181 182 fpint = fileno(fp); 183 (void) fsync(fpint); 184 185 (void) fclose(fp); 186 187 free(one_entry); 188 free(line); 189 190 return (NOERR); 191 } 192 193 /* 194 * Require exact match to delete a driver alias/permission entry. 195 * Note line argument does not remain unchanged. Return 1 if matched. 196 */ 197 static int 198 match_entry(char *line, char *match) 199 { 200 char *token, *p; 201 int n; 202 203 /* skip any leading white space */ 204 while (*line && ((*line == ' ') || (*line == '\t'))) 205 line++; 206 /* 207 * Find separator for driver name, either space or colon 208 * minor_perm: <driver>:<perm> 209 * driver_aliases: <driver> <alias> 210 * extra_privs: <driver>:<priv> 211 */ 212 if ((token = strpbrk(line, " :\t")) == NULL) 213 return (0); 214 token++; 215 /* skip leading white space and quotes */ 216 while (*token && (*token == ' ' || *token == '\t' || 217 *token == '"' || *token == '\'')) 218 token++; 219 /* strip trailing newline, white space and quotes */ 220 n = strlen(token); 221 p = token + n-1; 222 while (n > 0 && (*p == '\n' || *p == ' ' || *p == '\t' || 223 *p == '"' || *p == '\'')) { 224 *p-- = 0; 225 n--; 226 } 227 if (n == 0) 228 return (0); 229 return (strcmp(token, match) == 0); 230 } 231 232 /* 233 * open file 234 * read thru file, deleting all entries if first 235 * entry = driver_name 236 * close 237 * if error, leave original file intact with message 238 * assumption : drvconfig has been modified to work with clone 239 * entries in /etc/minor_perm as driver:mummble NOT 240 * clone:driver mummble 241 * this implementation will NOT find clone entries 242 * clone:driver mummble 243 * match: 244 * delete just the matching entry 245 * 246 */ 247 int 248 delete_entry( 249 char *oldfile, 250 char *driver_name, 251 char *marker, 252 char *match) 253 { 254 int rv, i; 255 int status = NOERR; 256 int drvr_found = 0; 257 boolean_t nomatch = B_TRUE; 258 char *newfile, *tptr, *cp; 259 char line[MAX_DBFILE_ENTRY], drv[FILENAME_MAX + 1]; 260 FILE *fp, *newfp; 261 struct group *sysgrp; 262 char *copy; /* same size as line */ 263 char *match2 = NULL; /* match with quotes cleaned up */ 264 265 /* 266 * if match is specified, sanity check it and clean it 267 * up by removing surrounding quotes as we require 268 * an exact match. 269 */ 270 if (match) { 271 cp = match; 272 while (*cp && (*cp == '"' || *cp == '\'')) 273 cp++; 274 i = strlen(cp); 275 if (i > 0) { 276 if ((match2 = strdup(cp)) == NULL) { 277 perror(NULL); 278 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 279 return (ERROR); 280 } 281 if ((cp = strchr(match2, '\'')) != NULL) 282 *cp = 0; 283 if ((cp = strchr(match2, '"')) != NULL) 284 *cp = 0; 285 if ((cp = strchr(match2, ' ')) != NULL) 286 *cp = 0; 287 } 288 if (match2 == NULL || (strlen(match2) == 0)) { 289 (void) fprintf(stderr, 290 gettext(ERR_INT_UPDATE), oldfile); 291 return (ERROR); 292 } 293 } 294 295 if ((fp = fopen(oldfile, "r")) == NULL) { 296 perror(NULL); 297 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), oldfile); 298 return (ERROR); 299 } 300 301 /* Space for defensive copy of input line */ 302 copy = calloc(sizeof (line), 1); 303 304 /* Build filename for temporary file */ 305 tptr = calloc(strlen(oldfile) + strlen(XEND) + 1, 1); 306 if (tptr == NULL || copy == NULL) { 307 perror(NULL); 308 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 309 return (ERROR); 310 } 311 312 (void) strcpy(tptr, oldfile); 313 (void) strcat(tptr, XEND); 314 315 /* 316 * Set gid so we preserve group attribute. Ideally we wouldn't 317 * assume a gid of "sys" but we can't undo the damage on already 318 * installed systems unless we force the issue. 319 */ 320 if ((sysgrp = getgrnam("sys")) != NULL) { 321 (void) setgid(sysgrp->gr_gid); 322 } 323 324 newfile = mktemp(tptr); 325 326 if ((newfp = fopen(newfile, "w")) == NULL) { 327 perror(NULL); 328 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 329 newfile); 330 return (ERROR); 331 } 332 333 while ((fgets(line, sizeof (line), fp) != NULL) && status == NOERR) { 334 /* copy the whole line */ 335 if (strlcpy(copy, line, sizeof (line)) >= sizeof (line)) { 336 (void) fprintf(stderr, gettext(ERR_UPDATE), oldfile); 337 status = ERROR; 338 break; 339 } 340 /* cut off comments starting with '#' */ 341 if ((cp = strchr(copy, '#')) != NULL) 342 *cp = '\0'; 343 /* ignore comment or blank lines */ 344 if (is_blank(copy)) { 345 if (fputs(line, newfp) == EOF) { 346 (void) fprintf(stderr, gettext(ERR_UPDATE), 347 oldfile); 348 status = ERROR; 349 } 350 continue; 351 } 352 353 /* get the driver name */ 354 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 355 if (sscanf(copy, "%s", drv) != 1) { 356 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 357 oldfile, line); 358 status = ERROR; 359 break; 360 } 361 362 for (i = strcspn(drv, marker); i < FILENAME_MAX; i++) { 363 drv[i] = '\0'; 364 } 365 366 if (strcmp(driver_name, drv) != 0) { 367 if ((fputs(line, newfp)) == EOF) { 368 (void) fprintf(stderr, gettext(ERR_UPDATE), 369 oldfile); 370 status = ERROR; 371 } 372 } else { 373 drvr_found++; 374 if (match2) { /* Just delete one entry */ 375 /* for now delete just minor_perm and aliases */ 376 if ((strcmp(oldfile, minor_perm) == 0) || 377 (strcmp(oldfile, extra_privs) == 0) || 378 (strcmp(oldfile, driver_aliases) == 0)) { 379 380 /* make defensive copy */ 381 if (strlcpy(copy, line, sizeof (line)) 382 >= sizeof (line)) { 383 (void) fprintf(stderr, 384 gettext(ERR_UPDATE), 385 oldfile); 386 status = ERROR; 387 break; 388 } 389 if (match_entry(copy, match2)) { 390 nomatch = B_FALSE; 391 } else { 392 if ((fputs(line, newfp)) == 393 EOF) { 394 (void) fprintf(stderr, 395 gettext(ERR_UPDATE), 396 oldfile); 397 status = ERROR; 398 } 399 if (nomatch != B_FALSE) 400 nomatch = B_TRUE; 401 } 402 } 403 } 404 405 } /* end of else */ 406 } /* end of while */ 407 408 (void) fclose(fp); 409 free(tptr); 410 free(copy); 411 if (match2) 412 free(match2); 413 414 /* Make sure that the file is on disk */ 415 if (fflush(newfp) != 0 || fsync(fileno(newfp)) != 0) 416 status = ERROR; 417 else 418 rv = NOERR; 419 420 (void) fclose(newfp); 421 422 /* no matching driver found */ 423 rv = NOERR; 424 if (!drvr_found || 425 (nomatch == B_TRUE)) { 426 rv = NONE_FOUND; 427 } 428 429 /* 430 * if error, leave original file, delete new file 431 * if noerr, replace original file with new file 432 */ 433 434 if (status == NOERR) { 435 if (rename(oldfile, tmphold) == -1) { 436 perror(NULL); 437 (void) fprintf(stderr, gettext(ERR_UPDATE), oldfile); 438 (void) unlink(newfile); 439 return (ERROR); 440 } else if (rename(newfile, oldfile) == -1) { 441 perror(NULL); 442 (void) fprintf(stderr, gettext(ERR_UPDATE), oldfile); 443 (void) unlink(oldfile); 444 (void) unlink(newfile); 445 if (link(tmphold, oldfile) == -1) { 446 perror(NULL); 447 (void) fprintf(stderr, gettext(ERR_BAD_LINK), 448 oldfile, tmphold); 449 } 450 return (ERROR); 451 } 452 (void) unlink(tmphold); 453 } else { 454 /* 455 * since there's an error, leave file alone; remove 456 * new file 457 */ 458 if (unlink(newfile) == -1) { 459 (void) fprintf(stderr, gettext(ERR_CANT_RM), newfile); 460 } 461 return (ERROR); 462 } 463 464 return (rv); 465 } 466 467 468 /* 469 * wrapper for call to get_name_to_major_entry(): given driver name, 470 * retrieve major number. 471 */ 472 int 473 get_major_no(char *driver_name, char *file_name) 474 { 475 int major = UNIQUE; 476 477 if (get_name_to_major_entry(&major, driver_name, file_name) == ERROR) 478 return (ERROR); 479 else 480 return (major); 481 } 482 483 /* 484 * wrapper for call to get_name_to_major_entry(): given major number, 485 * retrieve driver name. 486 */ 487 int 488 get_driver_name(int major, char *file_name, char *buf) 489 { 490 if (major < 0) 491 return (ERROR); 492 return (get_name_to_major_entry(&major, buf, file_name)); 493 } 494 495 496 /* 497 * return pointer to cached name_to_major file - reads file into 498 * cache if this has not already been done. Since there may be 499 * requests for multiple name_to_major files (rem_name_to_major, 500 * name_to_major), this routine keeps a list of cached files. 501 */ 502 static int 503 get_cached_n_to_m_file(char *filename, char ***cache) 504 { 505 struct n_to_m_cache { 506 char *file; 507 char **cached_file; 508 int size; 509 struct n_to_m_cache *next; 510 }; 511 static struct n_to_m_cache *head = NULL; 512 struct n_to_m_cache *ptr; 513 FILE *fp; 514 char drv[FILENAME_MAX + 1]; 515 char entry[FILENAME_MAX + 1]; 516 char line[MAX_N2M_ALIAS_LINE], *cp; 517 int maj; 518 int size = 0; 519 520 521 /* 522 * see if the file is already cached - either 523 * rem_name_to_major or name_to_major 524 */ 525 ptr = head; 526 while (ptr != NULL) { 527 if (strcmp(ptr->file, filename) == 0) 528 break; 529 ptr = ptr->next; 530 } 531 532 if (ptr == NULL) { /* we need to cache the contents */ 533 if ((fp = fopen(filename, "r")) == NULL) { 534 perror(NULL); 535 (void) fprintf(stderr, gettext(ERR_CANT_OPEN), 536 filename); 537 return (ERROR); 538 } 539 540 while (fgets(line, sizeof (line), fp) != NULL) { 541 /* cut off comments starting with '#' */ 542 if ((cp = strchr(line, '#')) != NULL) 543 *cp = '\0'; 544 /* ignore comment or blank lines */ 545 if (is_blank(line)) 546 continue; 547 /* sanity-check */ 548 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 549 if (sscanf(line, "%s%s", drv, entry) != 2) { 550 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 551 filename, line); 552 continue; 553 } 554 maj = atoi(entry); 555 if (maj > size) 556 size = maj; 557 } 558 559 /* allocate struct to cache the file */ 560 ptr = (struct n_to_m_cache *)calloc(1, 561 sizeof (struct n_to_m_cache)); 562 if (ptr == NULL) { 563 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 564 return (ERROR); 565 } 566 ptr->size = size + 1; 567 /* allocate space to cache contents of file */ 568 ptr->cached_file = (char **)calloc(ptr->size, sizeof (char *)); 569 if (ptr->cached_file == NULL) { 570 free(ptr); 571 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 572 return (ERROR); 573 } 574 575 rewind(fp); 576 577 /* 578 * now fill the cache 579 * the cache is an array of char pointers indexed by major 580 * number 581 */ 582 while (fgets(line, sizeof (line), fp) != NULL) { 583 /* cut off comments starting with '#' */ 584 if ((cp = strchr(line, '#')) != NULL) 585 *cp = '\0'; 586 /* ignore comment or blank lines */ 587 if (is_blank(line)) 588 continue; 589 /* sanity-check */ 590 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 591 if (sscanf(line, "%s%s", drv, entry) != 2) { 592 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 593 filename, line); 594 continue; 595 } 596 maj = atoi(entry); 597 if ((ptr->cached_file[maj] = strdup(drv)) == NULL) { 598 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 599 free(ptr->cached_file); 600 free(ptr); 601 return (ERROR); 602 } 603 (void) strcpy(ptr->cached_file[maj], drv); 604 } 605 (void) fclose(fp); 606 /* link the cache struct into the list of cached files */ 607 ptr->file = strdup(filename); 608 if (ptr->file == NULL) { 609 for (maj = 0; maj <= ptr->size; maj++) 610 free(ptr->cached_file[maj]); 611 free(ptr->cached_file); 612 free(ptr); 613 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 614 return (ERROR); 615 } 616 ptr->next = head; 617 head = ptr; 618 } 619 /* return value pointer to contents of file */ 620 *cache = ptr->cached_file; 621 622 /* return size */ 623 return (ptr->size); 624 } 625 626 627 /* 628 * Using get_cached_n_to_m_file(), retrieve maximum major number 629 * found in the specificed file (name_to_major/rem_name_to_major). 630 * 631 * The return value is actually the size of the internal cache including 0. 632 */ 633 int 634 get_max_major(char *file_name) 635 { 636 char **n_to_m_cache = NULL; 637 638 return (get_cached_n_to_m_file(file_name, &n_to_m_cache)); 639 } 640 641 642 /* 643 * searching name_to_major: if major_no == UNIQUE then the caller wants to 644 * use the driver name as the key. Otherwise, the caller wants to use 645 * the major number as a key. 646 * 647 * This routine caches the contents of the name_to_major file on 648 * first call. And it could be generalized to deal with other 649 * config files if necessary. 650 */ 651 static int 652 get_name_to_major_entry(int *major_no, char *driver_name, char *file_name) 653 { 654 int maj; 655 char **n_to_m_cache = NULL; 656 int size = 0; 657 658 int ret = NOT_UNIQUE; 659 660 /* 661 * read the file in - we cache it in case caller wants to 662 * do multiple lookups 663 */ 664 size = get_cached_n_to_m_file(file_name, &n_to_m_cache); 665 666 if (size == ERROR) 667 return (ERROR); 668 669 /* search with driver name as key */ 670 if (*major_no == UNIQUE) { 671 for (maj = 0; maj < size; maj++) { 672 if ((n_to_m_cache[maj] != NULL) && 673 (strcmp(driver_name, n_to_m_cache[maj]) == 0)) { 674 *major_no = maj; 675 break; 676 } 677 } 678 if (maj >= size) 679 ret = UNIQUE; 680 /* search with major number as key */ 681 } else { 682 /* 683 * Bugid 1254588, drvconfig dump core after loading driver 684 * with major number bigger than entries defined in 685 * /etc/name_to_major. 686 */ 687 if (*major_no >= size) 688 return (UNIQUE); 689 690 if (n_to_m_cache[*major_no] != NULL) { 691 (void) strcpy(driver_name, n_to_m_cache[*major_no]); 692 } else 693 ret = UNIQUE; 694 } 695 return (ret); 696 } 697 698 /* 699 * Given pointer to begining of member 'n' in a space (or separator) 700 * separated list, return pointer to member 'n+1', and establish member 'n' 701 * in *current_entry. If unquote, then we skip a leading quote and treat 702 * the trailing quote as a separator (and skip). 703 */ 704 char * 705 get_entry( 706 char *prev_member, 707 char *current_entry, 708 char separator, 709 int unquote) 710 { 711 char *ptr; 712 int quoted = 0; 713 714 ptr = prev_member; 715 716 /* skip white space */ 717 while (*ptr == '\t' || *ptr == ' ') 718 ptr++; 719 720 /* if unquote skip leading quote */ 721 if (unquote && *ptr == '"') { 722 quoted++; 723 ptr++; 724 } 725 726 /* read thru the current entry looking for end, separator, or unquote */ 727 while (*ptr && 728 (*ptr != separator) && 729 ((separator != ' ') || (*ptr != '\t')) && 730 (!quoted || (*ptr != '"'))) { 731 *current_entry++ = *ptr++; 732 } 733 *current_entry = '\0'; 734 735 if (separator && (*ptr == separator)) 736 ptr++; /* skip over separator */ 737 if (quoted && (*ptr == '"')) 738 ptr++; /* skip over trailing quote */ 739 740 /* skip white space */ 741 while (*ptr == '\t' || *ptr == ' ') { 742 ptr++; 743 } 744 745 return (ptr); 746 } 747 748 void 749 enter_lock(void) 750 { 751 struct flock lock; 752 753 /* 754 * attempt to create the lock file 755 */ 756 add_rem_lock_fd = open(add_rem_lock, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR); 757 if (add_rem_lock_fd < 0) { 758 (void) fprintf(stderr, gettext(ERR_CREAT_LOCK), 759 add_rem_lock, strerror(errno)); 760 exit(1); 761 } 762 763 lock.l_type = F_WRLCK; 764 lock.l_whence = SEEK_SET; 765 lock.l_start = 0; 766 lock.l_len = 0; 767 768 /* Try for the lock but don't wait. */ 769 if (fcntl(add_rem_lock_fd, F_SETLK, &lock) == -1) { 770 if (errno == EACCES || errno == EAGAIN) { 771 (void) fprintf(stderr, gettext(ERR_PROG_IN_USE)); 772 } else { 773 (void) fprintf(stderr, gettext(ERR_LOCK), 774 add_rem_lock, strerror(errno)); 775 } 776 exit(1); 777 } 778 } 779 780 void 781 err_exit(void) 782 { 783 /* release memory allocated for moddir */ 784 cleanup_moddir(); 785 /* remove add_drv/rem_drv lock */ 786 exit_unlock(); 787 exit(1); 788 } 789 790 void 791 cleanup_moddir(void) 792 { 793 struct drvmod_dir *walk_ptr; 794 struct drvmod_dir *free_ptr = moddir; 795 796 while (free_ptr != NULL) { 797 walk_ptr = free_ptr->next; 798 free(free_ptr); 799 free_ptr = walk_ptr; 800 } 801 } 802 803 void 804 exit_unlock(void) 805 { 806 struct flock unlock; 807 808 if (add_rem_lock_fd < 0) 809 return; 810 811 unlock.l_type = F_UNLCK; 812 unlock.l_whence = SEEK_SET; 813 unlock.l_start = 0; 814 unlock.l_len = 0; 815 816 if (fcntl(add_rem_lock_fd, F_SETLK, &unlock) == -1) { 817 (void) fprintf(stderr, gettext(ERR_UNLOCK), 818 add_rem_lock, strerror(errno)); 819 } else { 820 (void) close(add_rem_lock_fd); 821 add_rem_lock_fd = -1; 822 } 823 } 824 825 /* 826 * error adding driver; need to back out any changes to files. 827 * check flag to see which files need entries removed 828 * entry removal based on driver name 829 */ 830 void 831 remove_entry( 832 int c_flag, 833 char *driver_name) 834 { 835 836 if (c_flag & CLEAN_NAM_MAJ) { 837 if (delete_entry(name_to_major, driver_name, " ", 838 NULL) == ERROR) { 839 (void) fprintf(stderr, gettext(ERR_NO_CLEAN), 840 name_to_major, driver_name); 841 } 842 } 843 844 if (c_flag & CLEAN_DRV_ALIAS) { 845 if (delete_entry(driver_aliases, driver_name, " ", 846 NULL) == ERROR) { 847 (void) fprintf(stderr, gettext(ERR_DEL_ENTRY), 848 driver_name, driver_aliases); 849 } 850 } 851 852 if (c_flag & CLEAN_DRV_CLASSES) { 853 if (delete_entry(driver_classes, driver_name, "\t", NULL) == 854 ERROR) { 855 (void) fprintf(stderr, gettext(ERR_DEL_ENTRY), 856 driver_name, driver_classes); 857 } 858 } 859 860 if (c_flag & CLEAN_MINOR_PERM) { 861 if (delete_entry(minor_perm, driver_name, ":", NULL) == ERROR) { 862 (void) fprintf(stderr, gettext(ERR_DEL_ENTRY), 863 driver_name, minor_perm); 864 } 865 } 866 /* 867 * There's no point in removing entries from files that don't 868 * exist. Prevent error messages by checking for file existence 869 * first. 870 */ 871 if ((c_flag & CLEAN_DEV_POLICY) != 0 && 872 access(device_policy, F_OK) == 0) { 873 if (delete_plcy_entry(device_policy, driver_name) == ERROR) { 874 (void) fprintf(stderr, gettext(ERR_DEL_ENTRY), 875 driver_name, device_policy); 876 } 877 } 878 if ((c_flag & CLEAN_DRV_PRIV) != 0 && 879 access(extra_privs, F_OK) == 0) { 880 if (delete_entry(extra_privs, driver_name, ":", NULL) == 881 ERROR) { 882 (void) fprintf(stderr, gettext(ERR_DEL_ENTRY), 883 driver_name, extra_privs); 884 } 885 } 886 } 887 888 int 889 check_perms_aliases( 890 int m_flag, 891 int i_flag) 892 { 893 /* 894 * If neither i_flag nor m_flag are specified no need to check the 895 * files for access permissions 896 */ 897 if (!m_flag && !i_flag) 898 return (NOERR); 899 900 /* check minor_perm file : exits and is writable */ 901 if (m_flag) { 902 if (access(minor_perm, R_OK | W_OK)) { 903 perror(NULL); 904 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 905 minor_perm); 906 return (ERROR); 907 } 908 } 909 910 /* check driver_aliases file : exits and is writable */ 911 if (i_flag) { 912 if (access(driver_aliases, R_OK | W_OK)) { 913 perror(NULL); 914 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 915 driver_aliases); 916 return (ERROR); 917 } 918 } 919 920 return (NOERR); 921 } 922 923 924 int 925 check_name_to_major(int mode) 926 { 927 /* check name_to_major file : exists and is writable */ 928 if (access(name_to_major, mode)) { 929 perror(NULL); 930 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 931 name_to_major); 932 return (ERROR); 933 } 934 935 return (NOERR); 936 } 937 938 939 /* 940 * All this stuff is to support a server installing 941 * drivers on diskless clients. When on the server 942 * need to prepend the basedir 943 */ 944 int 945 build_filenames(char *basedir) 946 { 947 int len; 948 int driver_aliases_len; 949 int driver_classes_len; 950 int minor_perm_len; 951 int name_to_major_len; 952 int rem_name_to_major_len; 953 int add_rem_lock_len; 954 int tmphold_len; 955 int devfs_root_len; 956 int device_policy_len; 957 int extra_privs_len; 958 959 if (basedir == NULL) { 960 driver_aliases = DRIVER_ALIAS; 961 driver_classes = DRIVER_CLASSES; 962 minor_perm = MINOR_PERM; 963 name_to_major = NAM_TO_MAJ; 964 rem_name_to_major = REM_NAM_TO_MAJ; 965 add_rem_lock = ADD_REM_LOCK; 966 tmphold = TMPHOLD; 967 devfs_root = DEVFS_ROOT; 968 device_policy = DEV_POLICY; 969 extra_privs = EXTRA_PRIVS; 970 971 } else { 972 len = strlen(basedir) + 1; 973 974 driver_aliases_len = len + sizeof (DRIVER_ALIAS); 975 driver_classes_len = len + sizeof (DRIVER_CLASSES); 976 minor_perm_len = len + sizeof (MINOR_PERM); 977 name_to_major_len = len + sizeof (NAM_TO_MAJ); 978 rem_name_to_major_len = len + sizeof (REM_NAM_TO_MAJ); 979 add_rem_lock_len = len + sizeof (ADD_REM_LOCK); 980 tmphold_len = len + sizeof (TMPHOLD); 981 devfs_root_len = len + sizeof (DEVFS_ROOT); 982 device_policy_len = len + sizeof (DEV_POLICY); 983 extra_privs_len = len + sizeof (EXTRA_PRIVS); 984 985 driver_aliases = malloc(driver_aliases_len); 986 driver_classes = malloc(driver_classes_len); 987 minor_perm = malloc(minor_perm_len); 988 name_to_major = malloc(name_to_major_len); 989 rem_name_to_major = malloc(rem_name_to_major_len); 990 add_rem_lock = malloc(add_rem_lock_len); 991 tmphold = malloc(tmphold_len); 992 devfs_root = malloc(devfs_root_len); 993 device_policy = malloc(device_policy_len); 994 extra_privs = malloc(extra_privs_len); 995 996 if ((driver_aliases == NULL) || 997 (driver_classes == NULL) || 998 (minor_perm == NULL) || 999 (name_to_major == NULL) || 1000 (rem_name_to_major == NULL) || 1001 (add_rem_lock == NULL) || 1002 (tmphold == NULL) || 1003 (devfs_root == NULL) || 1004 (device_policy == NULL) || 1005 (extra_privs == NULL)) { 1006 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1007 return (ERROR); 1008 } 1009 1010 (void) snprintf(driver_aliases, driver_aliases_len, 1011 "%s%s", basedir, DRIVER_ALIAS); 1012 (void) snprintf(driver_classes, driver_classes_len, 1013 "%s%s", basedir, DRIVER_CLASSES); 1014 (void) snprintf(minor_perm, minor_perm_len, 1015 "%s%s", basedir, MINOR_PERM); 1016 (void) snprintf(name_to_major, name_to_major_len, 1017 "%s%s", basedir, NAM_TO_MAJ); 1018 (void) snprintf(rem_name_to_major, rem_name_to_major_len, 1019 "%s%s", basedir, REM_NAM_TO_MAJ); 1020 (void) snprintf(add_rem_lock, add_rem_lock_len, 1021 "%s%s", basedir, ADD_REM_LOCK); 1022 (void) snprintf(tmphold, tmphold_len, 1023 "%s%s", basedir, TMPHOLD); 1024 (void) snprintf(devfs_root, devfs_root_len, 1025 "%s%s", basedir, DEVFS_ROOT); 1026 (void) snprintf(device_policy, device_policy_len, 1027 "%s%s", basedir, DEV_POLICY); 1028 (void) snprintf(extra_privs, extra_privs_len, 1029 "%s%s", basedir, EXTRA_PRIVS); 1030 } 1031 1032 return (NOERR); 1033 } 1034 1035 static int 1036 exec_command(char *path, char *cmdline[MAX_CMD_LINE]) 1037 { 1038 pid_t pid; 1039 uint_t stat_loc; 1040 int waitstat; 1041 int exit_status; 1042 1043 /* child */ 1044 if ((pid = fork()) == 0) { 1045 (void) execv(path, cmdline); 1046 perror(NULL); 1047 return (ERROR); 1048 } else if (pid == -1) { 1049 /* fork failed */ 1050 perror(NULL); 1051 (void) fprintf(stderr, gettext(ERR_FORK_FAIL), cmdline); 1052 return (ERROR); 1053 } else { 1054 /* parent */ 1055 do { 1056 waitstat = waitpid(pid, (int *)&stat_loc, 0); 1057 1058 } while ((!WIFEXITED(stat_loc) && 1059 !WIFSIGNALED(stat_loc)) || (waitstat == 0)); 1060 1061 exit_status = WEXITSTATUS(stat_loc); 1062 1063 return (exit_status); 1064 } 1065 } 1066 1067 /* 1068 * Exec devfsadm to perform driver config/unconfig operation, 1069 * adding or removing aliases. 1070 */ 1071 static int 1072 exec_devfsadm( 1073 boolean_t config, 1074 char *driver_name, 1075 major_t major_num, 1076 char *aliases, 1077 char *classes, 1078 int verbose_flag, 1079 int force_flag) 1080 { 1081 int n = 0; 1082 char *cmdline[MAX_CMD_LINE]; 1083 char maj_num[128]; 1084 char *previous; 1085 char *current; 1086 int len; 1087 int rv; 1088 1089 /* build command line */ 1090 cmdline[n++] = DRVCONFIG; 1091 if (config == B_FALSE) { 1092 cmdline[n++] = "-u"; /* unconfigure */ 1093 if (force_flag) 1094 cmdline[n++] = "-f"; /* force if currently in use */ 1095 } 1096 if (verbose_flag) { 1097 cmdline[n++] = "-v"; 1098 } 1099 cmdline[n++] = "-b"; 1100 if (classes) { 1101 cmdline[n++] = "-c"; 1102 cmdline[n++] = classes; 1103 } 1104 cmdline[n++] = "-i"; 1105 cmdline[n++] = driver_name; 1106 cmdline[n++] = "-m"; 1107 (void) snprintf(maj_num, sizeof (maj_num), "%lu", major_num); 1108 cmdline[n++] = maj_num; 1109 1110 if (aliases != NULL) { 1111 len = strlen(aliases); 1112 previous = aliases; 1113 do { 1114 cmdline[n++] = "-a"; 1115 cmdline[n] = calloc(len + 1, 1); 1116 if (cmdline[n] == NULL) { 1117 (void) fprintf(stderr, 1118 gettext(ERR_NO_MEM)); 1119 return (ERROR); 1120 } 1121 current = get_entry(previous, 1122 cmdline[n++], ' ', 0); 1123 previous = current; 1124 1125 } while (*current != '\0'); 1126 1127 } 1128 cmdline[n] = (char *)0; 1129 1130 rv = exec_command(DRVCONFIG_PATH, cmdline); 1131 if (rv == NOERR) 1132 return (NOERR); 1133 return (ERROR); 1134 } 1135 1136 int 1137 unconfig_driver( 1138 char *driver_name, 1139 major_t major_num, 1140 char *aliases, 1141 int verbose_flag, 1142 int force_flag) 1143 { 1144 return (exec_devfsadm(B_FALSE, driver_name, major_num, 1145 aliases, NULL, verbose_flag, force_flag)); 1146 } 1147 1148 /* 1149 * check that major_num doesn't exceed maximum on this machine 1150 * do this here to support add_drv on server for diskless clients 1151 */ 1152 int 1153 config_driver( 1154 char *driver_name, 1155 major_t major_num, 1156 char *aliases, 1157 char *classes, 1158 int cleanup_flag, 1159 int verbose_flag) 1160 { 1161 int max_dev; 1162 int rv; 1163 1164 if (modctl(MODRESERVED, NULL, &max_dev) < 0) { 1165 perror(NULL); 1166 (void) fprintf(stderr, gettext(ERR_MAX_MAJOR)); 1167 return (ERROR); 1168 } 1169 1170 if (major_num >= max_dev) { 1171 (void) fprintf(stderr, gettext(ERR_MAX_EXCEEDS), 1172 major_num, max_dev); 1173 return (ERROR); 1174 } 1175 1176 /* bind major number and driver name */ 1177 rv = exec_devfsadm(B_TRUE, driver_name, major_num, 1178 aliases, classes, verbose_flag, 0); 1179 1180 if (rv == NOERR) 1181 return (NOERR); 1182 perror(NULL); 1183 remove_entry(cleanup_flag, driver_name); 1184 return (ERROR); 1185 } 1186 1187 void 1188 load_driver(char *driver_name, int verbose_flag) 1189 { 1190 int n = 0; 1191 char *cmdline[MAX_CMD_LINE]; 1192 int exec_status; 1193 1194 /* build command line */ 1195 cmdline[n++] = DEVFSADM; 1196 if (verbose_flag) { 1197 cmdline[n++] = "-v"; 1198 } 1199 cmdline[n++] = "-i"; 1200 cmdline[n++] = driver_name; 1201 cmdline[n] = (char *)0; 1202 1203 exec_status = exec_command(DEVFSADM_PATH, cmdline); 1204 1205 if (exec_status != NOERR) { 1206 /* no clean : name and major number are bound */ 1207 (void) fprintf(stderr, gettext(ERR_CONFIG), driver_name); 1208 } 1209 } 1210 1211 void 1212 get_modid(char *driver_name, int *mod) 1213 { 1214 struct modinfo modinfo; 1215 1216 modinfo.mi_id = -1; 1217 modinfo.mi_info = MI_INFO_ALL; 1218 do { 1219 /* 1220 * If we are at the end of the list of loaded modules 1221 * then set *mod = -1 and return 1222 */ 1223 if (modctl(MODINFO, 0, &modinfo) < 0) { 1224 *mod = -1; 1225 return; 1226 } 1227 1228 *mod = modinfo.mi_id; 1229 } while (strcmp(driver_name, modinfo.mi_name) != 0); 1230 } 1231 1232 int 1233 create_reconfig(char *basedir) 1234 { 1235 char reconfig_file[MAXPATHLEN + FILENAME_MAX + 1]; 1236 FILE *reconfig_fp; 1237 1238 if (basedir != NULL) { 1239 (void) strcpy(reconfig_file, basedir); 1240 (void) strcat(reconfig_file, RECONFIGURE); 1241 } else { 1242 (void) strcpy(reconfig_file, RECONFIGURE); 1243 } 1244 if ((reconfig_fp = fopen(reconfig_file, "a")) == NULL) 1245 return (ERROR); 1246 1247 (void) fclose(reconfig_fp); 1248 return (NOERR); 1249 } 1250 1251 1252 /* 1253 * update_minor_entry: 1254 * open file 1255 * for each entry in list 1256 * where list entries are separated by <list_separator> 1257 * modify entry : driver_name <entry_separator> entry 1258 * close file 1259 * 1260 * return error/noerr 1261 */ 1262 int 1263 update_minor_entry(char *driver_name, char *perm_list) 1264 { 1265 FILE *fp; 1266 FILE *newfp; 1267 struct group *sysgrp; 1268 int match = 0; 1269 char line[MAX_DBFILE_ENTRY], *cp, *dup; 1270 char drv[FILENAME_MAX + 1], *drv_minor; 1271 char minor[FILENAME_MAX + 1], perm[OPT_LEN + 1]; 1272 char own[OPT_LEN + 1], grp[OPT_LEN + 1]; 1273 int status = NOERR, i; 1274 char *newfile, *tptr; 1275 1276 if ((fp = fopen(minor_perm, "r")) == NULL) { 1277 perror(NULL); 1278 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 1279 minor_perm); 1280 1281 return (ERROR); 1282 } 1283 1284 /* 1285 * Build filename for temporary file 1286 */ 1287 if ((tptr = calloc(strlen(minor_perm) + strlen(XEND) + 1, 1)) == NULL) { 1288 perror(NULL); 1289 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1290 } 1291 (void) strcpy(tptr, minor_perm); 1292 (void) strcat(tptr, XEND); 1293 1294 /* 1295 * Set gid so we preserve group attribute. Ideally we wouldn't 1296 * assume a gid of "sys" but we can't undo the damage on already 1297 * installed systems unless we force the issue. 1298 */ 1299 if ((sysgrp = getgrnam("sys")) != NULL) { 1300 (void) setgid(sysgrp->gr_gid); 1301 } 1302 1303 newfile = mktemp(tptr); 1304 if ((newfp = fopen(newfile, "w")) == NULL) { 1305 perror(NULL); 1306 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 1307 newfile); 1308 return (ERROR); 1309 } 1310 1311 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 1312 if (sscanf(perm_list, "%s%s%s%s", minor, perm, own, grp) != 4) { 1313 status = ERROR; 1314 } 1315 1316 while ((fgets(line, sizeof (line), fp) != NULL) && status == NOERR) { 1317 /* copy the whole line into dup */ 1318 if ((dup = strdup(line)) == NULL) { 1319 perror(NULL); 1320 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1321 status = ERROR; 1322 break; 1323 } 1324 /* cut off comments starting with '#' */ 1325 if ((cp = strchr(dup, '#')) != NULL) 1326 *cp = '\0'; 1327 /* ignore comment or blank lines */ 1328 if (is_blank(dup)) { 1329 if (fputs(line, newfp) == EOF) { 1330 (void) fprintf(stderr, gettext(ERR_UPDATE), 1331 minor_perm); 1332 status = ERROR; 1333 } 1334 free(dup); 1335 continue; 1336 } 1337 1338 /* get the driver name */ 1339 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 1340 if (sscanf(dup, "%s", drv) != 1) { 1341 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 1342 minor_perm, line); 1343 status = ERROR; 1344 free(dup); 1345 break; 1346 } 1347 1348 /* 1349 * get the minor name; place the NULL character at the 1350 * end of the driver name, then make the drv_minor 1351 * point to the first character of the minor name. 1352 * the line missing ':' must be treated as a broken one. 1353 */ 1354 i = strcspn(drv, ":"); 1355 if (i == strlen(drv)) { 1356 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 1357 minor_perm, line); 1358 status = ERROR; 1359 free(dup); 1360 break; 1361 } 1362 drv[i] = '\0'; 1363 drv_minor = &drv[strlen(drv) + 1]; 1364 1365 /* 1366 * compare both of the driver name and the minor name. 1367 * then the new line should be written to the file if 1368 * both of them match 1369 */ 1370 if ((strcmp(drv, driver_name) == 0) && 1371 (strcmp(minor, drv_minor) == 0)) { 1372 /* if it has a comment, keep it */ 1373 if (cp != NULL) { 1374 cp++; /* skip a terminator */ 1375 (void) snprintf(line, sizeof (line), 1376 "%s:%s %s %s %s #%s\n", 1377 drv, minor, perm, own, grp, cp); 1378 } else { 1379 (void) snprintf(line, sizeof (line), 1380 "%s:%s %s %s %s\n", 1381 drv, minor, perm, own, grp); 1382 } 1383 match = 1; 1384 } 1385 free(dup); 1386 1387 /* update the file */ 1388 if ((fputs(line, newfp)) == EOF) { 1389 (void) fprintf(stderr, gettext(ERR_UPDATE), 1390 minor_perm); 1391 status = ERROR; 1392 } 1393 } 1394 1395 if (!match) { 1396 (void) bzero(line, sizeof (&line[0])); 1397 (void) snprintf(line, sizeof (line), 1398 "%s:%s %s %s %s\n", 1399 driver_name, minor, perm, own, grp); 1400 1401 /* add the new entry */ 1402 if ((fputs(line, newfp)) == EOF) { 1403 (void) fprintf(stderr, gettext(ERR_UPDATE), minor_perm); 1404 status = ERROR; 1405 } 1406 } 1407 1408 (void) fclose(fp); 1409 1410 if (fflush(newfp) != 0 || fsync(fileno(newfp)) != 0) 1411 status = ERROR; 1412 1413 (void) fclose(newfp); 1414 1415 /* 1416 * if error, leave original file, delete new file 1417 * if noerr, replace original file with new file 1418 */ 1419 if (status == NOERR) { 1420 if (rename(minor_perm, tmphold) == -1) { 1421 perror(NULL); 1422 (void) fprintf(stderr, gettext(ERR_UPDATE), minor_perm); 1423 (void) unlink(newfile); 1424 return (ERROR); 1425 } else if (rename(newfile, minor_perm) == -1) { 1426 perror(NULL); 1427 (void) fprintf(stderr, gettext(ERR_UPDATE), minor_perm); 1428 (void) unlink(minor_perm); 1429 (void) unlink(newfile); 1430 if (link(tmphold, minor_perm) == -1) { 1431 perror(NULL); 1432 (void) fprintf(stderr, gettext(ERR_BAD_LINK), 1433 minor_perm, tmphold); 1434 } 1435 return (ERROR); 1436 } 1437 (void) unlink(tmphold); 1438 } else { 1439 /* 1440 * since there's an error, leave file alone; remove 1441 * new file 1442 */ 1443 if (unlink(newfile) == -1) { 1444 (void) fprintf(stderr, gettext(ERR_CANT_RM), newfile); 1445 } 1446 return (ERROR); 1447 } 1448 1449 return (NOERR); 1450 1451 } 1452 1453 1454 /* 1455 * list_entry: 1456 * open file 1457 * read thru file, listing all entries if first entry = driver_name 1458 * close 1459 */ 1460 void 1461 list_entry( 1462 char *oldfile, 1463 char *driver_name, 1464 char *marker) 1465 { 1466 FILE *fp; 1467 int i; 1468 char line[MAX_DBFILE_ENTRY], *cp; 1469 char drv[FILENAME_MAX + 1]; 1470 1471 if ((fp = fopen(oldfile, "r")) == NULL) { 1472 perror(NULL); 1473 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), oldfile); 1474 1475 return; 1476 } 1477 1478 while (fgets(line, sizeof (line), fp) != NULL) { 1479 /* cut off comments starting with '#' */ 1480 if ((cp = strchr(line, '#')) != NULL) 1481 *cp = '\0'; 1482 /* ignore comment or blank lines */ 1483 if (is_blank(line)) 1484 continue; 1485 /* sanity-check */ 1486 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 1487 if (sscanf(line, "%s", drv) != 1) { 1488 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 1489 oldfile, line); 1490 } 1491 1492 for (i = strcspn(drv, marker); i < FILENAME_MAX; i++) { 1493 drv[i] = '\0'; 1494 } 1495 1496 if (strcmp(driver_name, drv) == 0) { 1497 (void) fprintf(stdout, "%s", line); 1498 } 1499 } 1500 1501 (void) fclose(fp); 1502 } 1503 1504 static boolean_t 1505 is_token(char *tok) 1506 { 1507 /* 1508 * Check the token here. According to IEEE1275 Open Firmware Boot 1509 * Standard, the name is composed of 1 to 31 letters, 1510 * digits and punctuation characters from the set ",._+-", and 1511 * uppercase and lowercase characters are considered distinct. 1512 * (ie. token := [a-zA-Z0-9,._+-]+, length(token) <= 31) 1513 * However, since either the definition of driver or aliase names is 1514 * not known well, only '#' is avoided explicitly. (the kernel lexical 1515 * analyzer treats it as a start of a comment) 1516 */ 1517 for (/* nothing */; *tok != '\0'; tok++) 1518 if (*tok == '#' || iscntrl(*tok)) 1519 return (B_FALSE); 1520 1521 return (B_TRUE); 1522 } 1523 1524 /* 1525 * check each entry in perm_list for: 1526 * 4 arguments 1527 * permission arg is in valid range 1528 * permlist entries separated by comma 1529 * return ERROR/NOERR 1530 */ 1531 int 1532 check_perm_opts(char *perm_list) 1533 { 1534 char *current_head; 1535 char *previous_head; 1536 char *one_entry; 1537 int i, len, scan_stat; 1538 char minor[FILENAME_MAX + 1]; 1539 char perm[OPT_LEN + 1]; 1540 char own[OPT_LEN + 1]; 1541 char grp[OPT_LEN + 1]; 1542 char dumb[OPT_LEN + 1]; 1543 int status = NOERR; 1544 int intperm; 1545 1546 len = strlen(perm_list); 1547 1548 if (len == 0) { 1549 return (ERROR); 1550 } 1551 1552 one_entry = calloc(len + 1, 1); 1553 if (one_entry == NULL) { 1554 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1555 return (ERROR); 1556 } 1557 1558 previous_head = perm_list; 1559 current_head = perm_list; 1560 1561 while (*current_head != '\0') { 1562 1563 for (i = 0; i <= len; i++) 1564 one_entry[i] = 0; 1565 1566 current_head = get_entry(previous_head, one_entry, ',', 0); 1567 1568 previous_head = current_head; 1569 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 1570 scan_stat = sscanf(one_entry, "%s%s%s%s%s", minor, perm, own, 1571 grp, dumb); 1572 1573 if (scan_stat < 4) { 1574 (void) fprintf(stderr, gettext(ERR_MIS_TOK), 1575 "-m", one_entry); 1576 status = ERROR; 1577 } 1578 if (scan_stat > 4) { 1579 (void) fprintf(stderr, gettext(ERR_TOO_MANY_ARGS), 1580 "-m", one_entry); 1581 status = ERROR; 1582 } 1583 1584 intperm = atoi(perm); 1585 if (intperm < 0000 || intperm > 4777) { 1586 (void) fprintf(stderr, gettext(ERR_BAD_MODE), perm); 1587 status = ERROR; 1588 } 1589 } 1590 1591 free(one_entry); 1592 return (status); 1593 } 1594 1595 1596 /* 1597 * check each alias : 1598 * alias list members separated by white space 1599 * cannot exist as driver name in /etc/name_to_major 1600 * cannot exist as driver or alias name in /etc/driver_aliases 1601 */ 1602 int 1603 aliases_unique(char *aliases) 1604 { 1605 char *current_head; 1606 char *previous_head; 1607 char *one_entry; 1608 int len; 1609 int is_unique; 1610 int err; 1611 1612 len = strlen(aliases); 1613 1614 one_entry = calloc(len + 1, 1); 1615 if (one_entry == NULL) { 1616 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1617 return (ERROR); 1618 } 1619 1620 previous_head = aliases; 1621 1622 do { 1623 bzero(one_entry, len+1); 1624 current_head = get_entry(previous_head, one_entry, ' ', 1); 1625 previous_head = current_head; 1626 1627 if ((unique_driver_name(one_entry, name_to_major, 1628 &is_unique)) == ERROR) 1629 goto err_out; 1630 1631 if (is_unique != UNIQUE) { 1632 (void) fprintf(stderr, gettext(ERR_ALIAS_IN_NAM_MAJ), 1633 one_entry); 1634 goto err_out; 1635 } 1636 1637 if ((err = unique_drv_alias(one_entry)) != UNIQUE) { 1638 if (err == NOT_UNIQUE) { 1639 (void) fprintf(stderr, 1640 gettext(ERR_ALIAS_IN_USE), one_entry); 1641 } 1642 goto err_out; 1643 } 1644 1645 if (!is_token(one_entry)) { 1646 (void) fprintf(stderr, gettext(ERR_BAD_TOK), 1647 "-i", one_entry); 1648 goto err_out; 1649 } 1650 1651 } while (*current_head != '\0'); 1652 1653 free(one_entry); 1654 return (NOERR); 1655 1656 err_out: 1657 free(one_entry); 1658 return (ERROR); 1659 } 1660 1661 /* 1662 * verify each alias : 1663 * alias list members separated by white space and quoted 1664 * exist as alias name in /etc/driver_aliases 1665 */ 1666 int 1667 aliases_exist(char *aliases) 1668 { 1669 char *current_head; 1670 char *previous_head; 1671 char *one_entry; 1672 int len; 1673 1674 len = strlen(aliases); 1675 1676 one_entry = calloc(len + 1, 1); 1677 if (one_entry == NULL) { 1678 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1679 return (ERROR); 1680 } 1681 1682 previous_head = aliases; 1683 1684 do { 1685 bzero(one_entry, len+1); 1686 current_head = get_entry(previous_head, one_entry, ' ', 1); 1687 previous_head = current_head; 1688 1689 if (unique_drv_alias(one_entry) != NOT_UNIQUE) 1690 goto err_out; 1691 1692 if (!is_token(one_entry)) { 1693 (void) fprintf(stderr, gettext(ERR_BAD_TOK), 1694 "-i", one_entry); 1695 goto err_out; 1696 } 1697 1698 } while (*current_head != '\0'); 1699 1700 free(one_entry); 1701 return (NOERR); 1702 1703 err_out: 1704 free(one_entry); 1705 return (ERROR); 1706 } 1707 1708 1709 /* 1710 * check each alias : 1711 * if path-oriented alias, path exists 1712 */ 1713 int 1714 aliases_paths_exist(char *aliases) 1715 { 1716 char *current_head; 1717 char *previous_head; 1718 char *one_entry; 1719 int i, len; 1720 char path[MAXPATHLEN]; 1721 struct stat buf; 1722 1723 len = strlen(aliases); 1724 1725 one_entry = calloc(len + 1, 1); 1726 if (one_entry == NULL) { 1727 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1728 return (ERROR); 1729 } 1730 1731 previous_head = aliases; 1732 1733 do { 1734 for (i = 0; i <= len; i++) 1735 one_entry[i] = 0; 1736 1737 current_head = get_entry(previous_head, one_entry, ' ', 1); 1738 previous_head = current_head; 1739 1740 /* if the alias is a path, ensure that the path exists */ 1741 if (*one_entry != '/') 1742 continue; 1743 (void) snprintf(path, sizeof (path), "/devices/%s", one_entry); 1744 if (stat(path, &buf) == 0) 1745 continue; 1746 1747 /* no device at specified path-oriented alias path */ 1748 (void) fprintf(stderr, gettext(ERR_PATH_ORIENTED_ALIAS), 1749 one_entry); 1750 free(one_entry); 1751 return (ERROR); 1752 1753 } while (*current_head != '\0'); 1754 1755 free(one_entry); 1756 1757 return (NOERR); 1758 } 1759 1760 1761 int 1762 update_driver_aliases( 1763 char *driver_name, 1764 char *aliases) 1765 { 1766 /* make call to update the aliases file */ 1767 return (append_to_file(driver_name, aliases, driver_aliases, 1768 ' ', " ", 1)); 1769 } 1770 1771 1772 /* 1773 * Return: 1774 * ERROR in case of memory or read error 1775 * UNIQUE if there is no existing match to the supplied alias 1776 * NOT_UNIQUE if there is a match 1777 * An error message is emitted in the case of ERROR, 1778 * up to the caller otherwise. 1779 */ 1780 int 1781 unique_drv_alias(char *drv_alias) 1782 { 1783 FILE *fp; 1784 char drv[FILENAME_MAX + 1]; 1785 char line[MAX_N2M_ALIAS_LINE + 1], *cp; 1786 char alias[FILENAME_MAX + 1]; 1787 char *a; 1788 int status = UNIQUE; 1789 1790 fp = fopen(driver_aliases, "r"); 1791 1792 if (fp != NULL) { 1793 while ((fgets(line, sizeof (line), fp) != 0) && 1794 status == UNIQUE) { 1795 /* cut off comments starting with '#' */ 1796 if ((cp = strchr(line, '#')) != NULL) 1797 *cp = '\0'; 1798 /* ignore comment or blank lines */ 1799 if (is_blank(line)) 1800 continue; 1801 /* sanity-check */ 1802 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 1803 if (sscanf(line, "%s %s", drv, alias) != 2) 1804 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 1805 driver_aliases, line); 1806 1807 /* unquote for compare */ 1808 if ((*alias == '"') && 1809 (*(alias + strlen(alias) - 1) == '"')) { 1810 a = &alias[1]; 1811 alias[strlen(alias) - 1] = '\0'; 1812 } else 1813 a = alias; 1814 1815 if ((strcmp(drv_alias, drv) == 0) || 1816 (strcmp(drv_alias, a) == 0)) { 1817 status = NOT_UNIQUE; 1818 break; 1819 } 1820 } 1821 (void) fclose(fp); 1822 return (status); 1823 } else { 1824 perror(NULL); 1825 (void) fprintf(stderr, gettext(ERR_CANT_OPEN), driver_aliases); 1826 return (ERROR); 1827 } 1828 } 1829 1830 1831 /* 1832 * search for driver_name in first field of file file_name 1833 * searching name_to_major and driver_aliases: name separated 1834 * from the remainder of the line by white space. 1835 */ 1836 int 1837 unique_driver_name(char *driver_name, char *file_name, 1838 int *is_unique) 1839 { 1840 int ret, err; 1841 1842 if ((ret = get_major_no(driver_name, file_name)) == ERROR) { 1843 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), 1844 file_name); 1845 } else { 1846 /* check alias file for name collision */ 1847 if ((err = unique_drv_alias(driver_name)) != UNIQUE) { 1848 if (err == NOT_UNIQUE) { 1849 (void) fprintf(stderr, 1850 gettext(ERR_ALIAS_IN_USE), 1851 driver_name); 1852 } 1853 ret = ERROR; 1854 } else { 1855 if (ret != UNIQUE) 1856 *is_unique = NOT_UNIQUE; 1857 else 1858 *is_unique = ret; 1859 ret = NOERR; 1860 } 1861 } 1862 return (ret); 1863 } 1864 1865 /* 1866 * returns: 1867 * SUCCESS - not an existing driver alias 1868 * NOT_UNIQUE - matching driver alias exists 1869 * ERROR - an error occurred 1870 */ 1871 int 1872 check_duplicate_driver_alias(char *driver_name, char *drv_alias) 1873 { 1874 FILE *fp; 1875 char drv[FILENAME_MAX + 1]; 1876 char line[MAX_N2M_ALIAS_LINE + 1], *cp; 1877 char alias[FILENAME_MAX + 1]; 1878 char *a; 1879 int status = SUCCESS; 1880 1881 if ((fp = fopen(driver_aliases, "r")) == NULL) { 1882 perror(NULL); 1883 (void) fprintf(stderr, gettext(ERR_CANT_OPEN), driver_aliases); 1884 return (ERROR); 1885 } 1886 1887 while (fgets(line, sizeof (line), fp) != 0) { 1888 /* cut off comments starting with '#' */ 1889 if ((cp = strchr(line, '#')) != NULL) 1890 *cp = '\0'; 1891 /* ignore comment or blank lines */ 1892 if (is_blank(line)) 1893 continue; 1894 /* sanity-check */ 1895 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 1896 if (sscanf(line, "%s %s", drv, alias) != 2) 1897 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 1898 driver_aliases, line); 1899 1900 /* unquote for compare */ 1901 if ((*alias == '"') && 1902 (*(alias + strlen(alias) - 1) == '"')) { 1903 a = &alias[1]; 1904 alias[strlen(alias) - 1] = '\0'; 1905 } else 1906 a = alias; 1907 1908 if ((strcmp(drv_alias, a) == 0) && 1909 (strcmp(drv, driver_name) == 0)) { 1910 status = NOT_UNIQUE; 1911 } 1912 1913 if ((strcmp(drv_alias, drv) == 0) || 1914 ((strcmp(drv_alias, a) == 0) && 1915 (strcmp(drv, driver_name) != 0))) { 1916 (void) fprintf(stderr, 1917 gettext(ERR_ALIAS_IN_USE), 1918 drv_alias); 1919 status = ERROR; 1920 goto done; 1921 } 1922 } 1923 1924 done: 1925 (void) fclose(fp); 1926 return (status); 1927 } 1928 1929 int 1930 trim_duplicate_aliases(char *driver_name, char *aliases, char **aliases2p) 1931 { 1932 char *current_head; 1933 char *previous_head; 1934 char *one_entry; 1935 char *aliases2; 1936 int rv, len; 1937 int n = 0; 1938 1939 *aliases2p = NULL; 1940 len = strlen(aliases) + 1; 1941 1942 one_entry = calloc(len, 1); 1943 aliases2 = calloc(len, 1); 1944 if (one_entry == NULL || aliases2 == NULL) { 1945 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 1946 return (ERROR); 1947 } 1948 1949 previous_head = aliases; 1950 1951 do { 1952 (void) bzero(one_entry, len); 1953 current_head = get_entry(previous_head, one_entry, ' ', 1); 1954 previous_head = current_head; 1955 1956 rv = check_duplicate_driver_alias(driver_name, one_entry); 1957 switch (rv) { 1958 case SUCCESS: 1959 /* not an existing driver alias: add it */ 1960 if (n > 0) { 1961 if (strlcat(aliases2, " ", len) >= len) 1962 goto err; 1963 } 1964 if (strlcat(aliases2, one_entry, len) >= len) 1965 goto err; 1966 n++; 1967 break; 1968 case NOT_UNIQUE: 1969 /* matching driver alias exists: do not add it */ 1970 break; 1971 case ERROR: 1972 /* error reading the alias file */ 1973 goto err; 1974 default: 1975 goto err; 1976 } 1977 1978 if (!is_token(one_entry)) { 1979 (void) fprintf(stderr, gettext(ERR_BAD_TOK), 1980 "-i", one_entry); 1981 goto err; 1982 } 1983 } while (*current_head != '\0'); 1984 1985 /* 1986 * If all the aliases listed are already 1987 * present we actually have none to do. 1988 */ 1989 if (n == 0) { 1990 free(aliases2); 1991 } else { 1992 *aliases2p = aliases2; 1993 } 1994 free(one_entry); 1995 return (NOERR); 1996 1997 err: 1998 free(aliases2); 1999 free(one_entry); 2000 return (ERROR); 2001 } 2002 2003 int 2004 check_space_within_quote(char *str) 2005 { 2006 register int i; 2007 register int len; 2008 int quoted = 0; 2009 2010 len = strlen(str); 2011 for (i = 0; i < len; i++, str++) { 2012 if (*str == '"') { 2013 if (quoted == 0) 2014 quoted++; 2015 else 2016 quoted--; 2017 } else if (*str == ' ' && quoted) 2018 return (ERROR); 2019 } 2020 2021 return (0); 2022 } 2023 2024 2025 /* 2026 * get major number 2027 * write driver_name major_num to name_to_major file 2028 * major_num returned in major_num 2029 * return success/failure 2030 */ 2031 int 2032 update_name_to_major(char *driver_name, major_t *major_num, int server) 2033 { 2034 char major[MAX_STR_MAJOR + 1]; 2035 struct stat buf; 2036 char *num_list; 2037 char drv_majnum_str[MAX_STR_MAJOR + 1]; 2038 int new_maj = -1; 2039 int i, tmp = 0, is_unique, have_rem_n2m = 0; 2040 int max_dev = 0; 2041 2042 /* 2043 * if driver_name already in rem_name_to_major 2044 * delete entry from rem_nam_to_major 2045 * put entry into name_to_major 2046 */ 2047 2048 if (stat(rem_name_to_major, &buf) == 0) { 2049 have_rem_n2m = 1; 2050 } 2051 2052 if (have_rem_n2m) { 2053 if ((is_unique = get_major_no(driver_name, rem_name_to_major)) 2054 == ERROR) 2055 return (ERROR); 2056 2057 /* 2058 * found a match in rem_name_to_major 2059 */ 2060 if (is_unique != UNIQUE) { 2061 char scratch[FILENAME_MAX]; 2062 2063 /* 2064 * If there is a match in /etc/rem_name_to_major then 2065 * be paranoid: is that major number already in 2066 * /etc/name_to_major (potentially under another name)? 2067 */ 2068 if (get_driver_name(is_unique, name_to_major, 2069 scratch) != UNIQUE) { 2070 /* 2071 * nuke the rem_name_to_major entry-- it 2072 * isn't helpful. 2073 */ 2074 (void) delete_entry(rem_name_to_major, 2075 driver_name, " ", NULL); 2076 } else { 2077 (void) snprintf(major, sizeof (major), 2078 "%d", is_unique); 2079 2080 if (append_to_file(driver_name, major, 2081 name_to_major, ' ', " ", 0) == ERROR) { 2082 (void) fprintf(stderr, 2083 gettext(ERR_NO_UPDATE), 2084 name_to_major); 2085 return (ERROR); 2086 } 2087 2088 if (delete_entry(rem_name_to_major, 2089 driver_name, " ", NULL) == ERROR) { 2090 (void) fprintf(stderr, 2091 gettext(ERR_DEL_ENTRY), driver_name, 2092 rem_name_to_major); 2093 return (ERROR); 2094 } 2095 2096 /* found matching entry : no errors */ 2097 *major_num = is_unique; 2098 return (NOERR); 2099 } 2100 } 2101 } 2102 2103 /* 2104 * Bugid: 1264079 2105 * In a server case (with -b option), we can't use modctl() to find 2106 * the maximum major number, we need to dig thru client's 2107 * /etc/name_to_major and /etc/rem_name_to_major for the max_dev. 2108 * 2109 * if (server) 2110 * get maximum major number thru (rem_)name_to_major file on client 2111 * else 2112 * get maximum major number allowable on current system using modctl 2113 */ 2114 if (server) { 2115 max_dev = 0; 2116 tmp = 0; 2117 2118 max_dev = get_max_major(name_to_major); 2119 2120 /* If rem_name_to_major exists, we need to check it too */ 2121 if (have_rem_n2m) { 2122 tmp = get_max_major(rem_name_to_major); 2123 2124 /* 2125 * If name_to_major is missing, we can get max_dev from 2126 * /etc/rem_name_to_major. If both missing, bail out! 2127 */ 2128 if ((max_dev == ERROR) && (tmp == ERROR)) { 2129 (void) fprintf(stderr, 2130 gettext(ERR_CANT_ACCESS_FILE), 2131 name_to_major); 2132 return (ERROR); 2133 } 2134 2135 /* guard against bigger maj_num in rem_name_to_major */ 2136 if (tmp > max_dev) 2137 max_dev = tmp; 2138 } else { 2139 /* 2140 * If we can't get major from name_to_major file 2141 * and there is no /etc/rem_name_to_major file, 2142 * then we don't have a max_dev, bail out quick! 2143 */ 2144 if (max_dev == ERROR) 2145 return (ERROR); 2146 } 2147 2148 /* 2149 * In case there is no more slack in current name_to_major 2150 * table, provide at least 1 extra entry so the add_drv can 2151 * succeed. Since only one add_drv process is allowed at one 2152 * time, and hence max_dev will be re-calculated each time 2153 * add_drv is ran, we don't need to worry about adding more 2154 * than 1 extra slot for max_dev. 2155 */ 2156 max_dev++; 2157 2158 } else { 2159 if (modctl(MODRESERVED, NULL, &max_dev) < 0) { 2160 perror(NULL); 2161 (void) fprintf(stderr, gettext(ERR_MAX_MAJOR)); 2162 return (ERROR); 2163 } 2164 } 2165 2166 /* 2167 * max_dev is really how many slots the kernel has allocated for 2168 * devices... [0 , maxdev-1], not the largest available device num. 2169 */ 2170 if ((num_list = calloc(max_dev, 1)) == NULL) { 2171 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 2172 return (ERROR); 2173 } 2174 2175 /* 2176 * Populate the num_list array 2177 */ 2178 if (fill_n2m_array(name_to_major, &num_list, &max_dev) != 0) { 2179 return (ERROR); 2180 } 2181 if (have_rem_n2m) { 2182 if (fill_n2m_array(rem_name_to_major, &num_list, &max_dev) != 0) 2183 return (ERROR); 2184 } 2185 2186 /* find first free major number */ 2187 for (i = 0; i < max_dev; i++) { 2188 if (num_list[i] != 1) { 2189 new_maj = i; 2190 break; 2191 } 2192 } 2193 2194 if (new_maj == -1) { 2195 (void) fprintf(stderr, gettext(ERR_NO_FREE_MAJOR)); 2196 return (ERROR); 2197 } 2198 2199 (void) snprintf(drv_majnum_str, sizeof (drv_majnum_str), 2200 "%d", new_maj); 2201 if (do_the_update(driver_name, drv_majnum_str) == ERROR) { 2202 return (ERROR); 2203 } 2204 2205 *major_num = new_maj; 2206 return (NOERR); 2207 } 2208 2209 2210 int 2211 fill_n2m_array(char *filename, char **array, int *nelems) 2212 { 2213 FILE *fp; 2214 char line[MAX_N2M_ALIAS_LINE + 1], *cp; 2215 char drv[FILENAME_MAX + 1]; 2216 u_longlong_t dnum; 2217 major_t drv_majnum; 2218 2219 /* 2220 * Read through the file, marking each major number found 2221 * order is not relevant 2222 */ 2223 if ((fp = fopen(filename, "r")) == NULL) { 2224 perror(NULL); 2225 (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), filename); 2226 return (ERROR); 2227 } 2228 2229 while (fgets(line, sizeof (line), fp) != 0) { 2230 /* cut off comments starting with '#' */ 2231 if ((cp = strchr(line, '#')) != NULL) 2232 *cp = '\0'; 2233 /* ignore comment or blank lines */ 2234 if (is_blank(line)) 2235 continue; 2236 /* sanity-check */ 2237 /* LINTED E_SEC_SCANF_UNBOUNDED_COPY */ 2238 if (sscanf(line, "%s %llu", drv, &dnum) != 2) { 2239 (void) fprintf(stderr, gettext(ERR_BAD_LINE), 2240 filename, line); 2241 (void) fclose(fp); 2242 return (ERROR); 2243 } 2244 2245 if (dnum > L_MAXMAJ32) { 2246 (void) fprintf(stderr, gettext(ERR_MAJ_TOOBIG), drv, 2247 dnum, filename, L_MAXMAJ32); 2248 continue; 2249 } 2250 /* 2251 * cast down to a major_t; we can be sure this is safe because 2252 * of the above range-check. 2253 */ 2254 drv_majnum = (major_t)dnum; 2255 2256 if (drv_majnum >= *nelems) { 2257 /* 2258 * Allocate some more space, up to drv_majnum + 1 so 2259 * we can accomodate 0 through drv_majnum. 2260 * 2261 * Note that in the failure case, we leak all of the 2262 * old contents of array. It's ok, since we just 2263 * wind up exiting immediately anyway. 2264 */ 2265 *nelems = drv_majnum + 1; 2266 *array = realloc(*array, *nelems); 2267 if (*array == NULL) { 2268 (void) fprintf(stderr, gettext(ERR_NO_MEM)); 2269 return (ERROR); 2270 } 2271 } 2272 (*array)[drv_majnum] = 1; 2273 } 2274 2275 (void) fclose(fp); 2276 return (0); 2277 } 2278 2279 2280 int 2281 do_the_update(char *driver_name, char *major_number) 2282 { 2283 return (append_to_file(driver_name, major_number, name_to_major, 2284 ' ', " ", 0)); 2285 } 2286 2287 /* 2288 * is_blank() returns 1 (true) if a line specified is composed of 2289 * whitespace characters only. otherwise, it returns 0 (false). 2290 * 2291 * Note. the argument (line) must be null-terminated. 2292 */ 2293 static int 2294 is_blank(char *line) 2295 { 2296 for (/* nothing */; *line != '\0'; line++) 2297 if (!isspace(*line)) 2298 return (0); 2299 return (1); 2300 } 2301