#pragma ident "@(#)add_drv.c 1.13 95/01/12 SMI" /* * Copyright (c) 1993 by Sun Microsystems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "addrem.h" #include "errmsg.h" static int module_not_found(char *, char *); static int unique_driver_name(char *, char *, char *, int *); static int check_perm_opts(char *); static int update_name_to_major(char *, major_t *); static int aliases_unique(char *); static int unique_drv_alias(char *); static int config_driver(char *, major_t, char *, char *, int, int); static void usage(); static int update_minor_perm(char *, char *); static int update_driver_classes(char *, char *); static int update_driver_aliases(char *, char *); static int do_the_update(char *, char *); static void signal_rtn(); static int exec_command(char *, char **); int main(int argc, char *argv[]) { int opt; struct stat buf; FILE *fp; major_t major_num; char driver_name[FILENAME_MAX + 1]; char *path_driver_name; char *perms = NULL; char *aliases = NULL; char *classes = NULL; int noload_flag = 0; int i_flag = 0; int c_flag = 0; int m_flag = 0; int cleanup_flag = 0; int server = 0; char *basedir = NULL; char dup_entry[MAX_N2M_ALIAS_LINE + 1]; char *cmdline[MAX_CMD_LINE]; int n; int is_unique; FILE *reconfig_fp; char basedir_rec[PATH_MAX + FILENAME_MAX + 1]; char *slash; int pathlen; int x; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); /* must be run by root */ if (getuid() != 0) { (void) fprintf(stderr, gettext(ERR_NOT_ROOT)); exit(1); } while ((opt = getopt(argc, argv, "m:ni:b:c:")) != EOF) { switch (opt) { case 'm' : m_flag = 1; perms = calloc(strlen(optarg) + 1, 1); if (perms == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); exit(1); } (void) strcat(perms, optarg); break; case 'n': noload_flag++; break; case 'i' : i_flag = 1; aliases = calloc(strlen(optarg) + 1, 1); if (aliases == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); exit(1); } (void) strcat(aliases, optarg); break; case 'b' : server = 1; basedir = calloc(strlen(optarg) + 1, 1); if (basedir == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); exit(1); } (void) strcat(basedir, optarg); break; case 'c': c_flag = 1; classes = strdup(optarg); if (classes == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); exit(1); } break; case '?' : default: usage(); exit(1); } } if (argv[optind] != NULL) { path_driver_name = calloc(strlen(argv[optind]) + 1, 1); if (path_driver_name == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); exit(1); } (void) strcat(path_driver_name, argv[optind]); /* * check for extra args */ if ((optind + 1) != argc) { usage(); exit(1); } } else { usage(); exit(1); } /* get module name from path */ /* if / ends with slash; strip off slash/s */ pathlen = strlen(path_driver_name); for (x = 1; ((path_driver_name[pathlen - x ] == '/') && (pathlen != 1)); x++) { path_driver_name[pathlen - x] = '\0'; } slash = strrchr(path_driver_name, '/'); if (slash == NULL) { (void) strcpy(driver_name, path_driver_name); } else { (void) strcpy(driver_name, ++slash); if (driver_name[0] == '\0') { (void) fprintf(stderr, gettext(ERR_NO_DRVNAME), path_driver_name); usage(); exit(1); } } /* set up add_drv filenames */ if ((build_filenames(basedir)) == ERROR) { exit(1); } /* must be only running version of add_drv/rem_drv */ if ((stat(add_rem_lock, &buf) == -1) && errno == ENOENT) { fp = fopen(add_rem_lock, "a"); (void) fclose(fp); } else { (void) fprintf(stderr, gettext(ERR_PROG_IN_USE)); exit(1); } /* * have opened lock file; want to be sure to remove it * whenever we exit. */ (void) sigset(SIGINT, signal_rtn); (void) sigset(SIGHUP, signal_rtn); (void) sigset(SIGTERM, signal_rtn); if ((some_checking(m_flag, i_flag)) == ERROR) err_exit(); /* * check validity of options */ if (m_flag) { if ((check_perm_opts(perms)) == ERROR) err_exit(); } if (i_flag) { if (aliases != NULL) if ((aliases_unique(aliases)) == ERROR) err_exit(); } if ((unique_driver_name(driver_name, name_to_major, dup_entry, &is_unique)) == ERROR) err_exit(); if (is_unique == NOT_UNIQUE) { (void) fprintf(stderr, gettext(ERR_NOT_UNIQUE), driver_name); err_exit(); } if (!server) { if ((module_not_found(driver_name, path_driver_name)) == ERROR) { perror(NULL); (void) fprintf(stderr, gettext(ERR_NOMOD), driver_name); err_exit(); } } if ((update_name_to_major(driver_name, &major_num)) == ERROR) { err_exit(); } cleanup_flag |= CLEAN_NAM_MAJ; if (m_flag) { if (update_minor_perm(driver_name, perms) == ERROR) { cleanup_flag |= CLEAN_MINOR_PERM; remove_entry(cleanup_flag, driver_name); err_exit(); } cleanup_flag |= CLEAN_MINOR_PERM; } if (i_flag) { if (update_driver_aliases(driver_name, aliases) == ERROR) { cleanup_flag |= CLEAN_DRV_ALIAS; remove_entry(cleanup_flag, driver_name); err_exit(); } cleanup_flag |= CLEAN_DRV_ALIAS; } if (c_flag) { if (update_driver_classes(driver_name, classes) == ERROR) { cleanup_flag |= CLEAN_DRV_CLASSES; remove_entry(cleanup_flag, driver_name); err_exit(); } cleanup_flag |= CLEAN_DRV_CLASSES; } if (server) { (void) fprintf(stderr, gettext(BOOT_CLIENT)); /* * create /reconfigure file so system reconfigures * on reboot */ (void) strcpy(basedir_rec, basedir); (void) strcat(basedir_rec, RECONFIGURE); reconfig_fp = fopen(basedir_rec, "a"); (void) fclose(reconfig_fp); } else { if (config_driver(driver_name, major_num, aliases, classes, cleanup_flag, noload_flag) == ERROR) { err_exit(); } } if (!server && !noload_flag) { /* * run devlinks -r / * run disks -r / * run ports -r / * run tapes -r / */ n = 0; cmdline[n++] = "devlinks"; cmdline[n] = (char *)0; if (exec_command(DEVLINKS_PATH, cmdline)) { (void) fprintf(stderr, gettext(ERR_DEVLINKS), DEVLINKS_PATH); } cmdline[0] = "disks"; if (exec_command(DISKS_PATH, cmdline)) { (void) fprintf(stderr, gettext(ERR_DISKS), DISKS_PATH); } cmdline[0] = "ports"; if (exec_command(PORTS_PATH, cmdline)) { (void) fprintf(stderr, gettext(ERR_PORTS), PORTS_PATH); } cmdline[0] = "tapes"; if (exec_command(TAPES_PATH, cmdline)) { (void) fprintf(stderr, gettext(ERR_TAPES), TAPES_PATH); } } exit_unlock(); return (NOERR); } int module_not_found(char *module_name, char *path_driver_name) { char path[MAXPATHLEN + FILENAME_MAX + 1]; struct stat buf; struct stat ukdbuf; char data [MAXMODPATHS]; char usr_kernel_drv[FILENAME_MAX + 17]; char *next = data; /* * if path * if (path/module doesn't exist AND * /usr/kernel/drv/module doesn't exist) * error msg * exit add_drv */ if (strcmp(module_name, path_driver_name)) { (void) strcpy(usr_kernel_drv, "/usr/kernel/drv/"); (void) strcat(usr_kernel_drv, module_name); if (((stat(path_driver_name, &buf) == 0) && ((buf.st_mode & S_IFMT) == S_IFREG)) || ((stat(usr_kernel_drv, &ukdbuf) == 0) && ((ukdbuf.st_mode & S_IFMT) == S_IFREG))) { return (NOERR); } } else { /* no path */ if (modctl(MODGETPATH, NULL, data) != 0) { (void) fprintf(stderr, gettext(ERR_MODPATH)); return (ERROR); } next = strtok(data, MOD_SEP); while (next != NULL) { (void) sprintf(path, "%s/drv/%s", next, module_name); if ((stat(path, &buf) == 0) && ((buf.st_mode & S_IFMT) == S_IFREG)) { return (NOERR); } next = strtok((char *)NULL, MOD_SEP); } } return (ERROR); } /* * search for driver_name in first field of file file_name * searching name_to_major and driver_aliases: name separated from rest of * line by blank * if there return * else return */ int unique_driver_name( char *driver_name, char *file_name, char *matched_line, int *is_unique) { FILE *fp; char drv[FILENAME_MAX + 1]; char entry[FILENAME_MAX + 1]; char line[MAX_N2M_ALIAS_LINE + 1]; *is_unique = UNIQUE; matched_line[0] = '\0'; fp = fopen(file_name, "r"); if (fp != NULL) { while ((fgets(line, sizeof (line), fp) != 0) && *is_unique == UNIQUE) { if (sscanf(line, "%s%s", drv, entry) != 2) { (void) fprintf(stderr, gettext(ERR_BAD_LINE), file_name, line); continue; } if (strcmp(driver_name, drv) == 0) *is_unique = NOT_UNIQUE; /* return line containing driver name */ (void) strcpy(matched_line, drv); (void) strcat(matched_line, " "); (void) strcat(matched_line, entry); } (void) fclose(fp); /* XXX */ /* check alias file for name collision */ if (unique_drv_alias(driver_name) == ERROR) { return (ERROR); } return (NOERR); } else { perror(NULL); (void) fprintf(stderr, gettext(ERR_CANNOT_OPEN), file_name); return (ERROR); } } /* * check each entry in perm_list for: * 4 arguments * permission arg is in valid range * permlist entries separated by comma * return ERROR/NOERR */ int check_perm_opts(char *perm_list) { char *current_head; char *previous_head; char *one_entry; int i, len, scan_stat; char minor[FILENAME_MAX + 1]; char perm[OPT_LEN + 1]; char own[OPT_LEN + 1]; char grp[OPT_LEN + 1]; char dumb[OPT_LEN + 1]; int status = NOERR; int intperm; len = strlen(perm_list); if (len == 0) { usage(); return (ERROR); } one_entry = calloc(len + 1, 1); if (one_entry == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); return (ERROR); } previous_head = perm_list; current_head = perm_list; while (*current_head != '\0') { for (i = 0; i <= len; i++) one_entry[i] = 0; current_head = get_entry(previous_head, one_entry, ','); previous_head = current_head; scan_stat = sscanf(one_entry, "%s%s%s%s%s", minor, perm, own, grp, dumb); if (scan_stat < 4) { (void) fprintf(stderr, gettext(ERR_MIS_TOK), "-m", one_entry); status = ERROR; } if (scan_stat > 4) { (void) fprintf(stderr, gettext(ERR_TOO_MANY_ARGS), "-m", one_entry); status = ERROR; } intperm = atoi(perm); if (intperm < 0000 || intperm > 4777) { (void) fprintf(stderr, gettext(ERR_BAD_MODE), perm); status = ERROR; } } free(one_entry); return (status); } /* * get major number * write driver_name major_num to name_to_major file * major_num returned in major_num * return success/failure */ int update_name_to_major( char *driver_name, major_t *major_num) { char dup_entry[MAX_N2M_ALIAS_LINE + 1]; char drv[FILENAME_MAX + 1]; char major[MAX_STR_MAJOR + 1]; struct stat buf; int max_dev; char *num_list; FILE *fp; char drv_majnum[MAX_STR_MAJOR + 1]; char line[MAX_N2M_ALIAS_LINE + 1]; int new_maj = -1; int i; int is_unique; /* * if driver_name already in rem_name_to_major * delete entry from rem_nam_to_major * put entry into name_to_major */ if (stat(rem_name_to_major, &buf) == 0) { if ((get_file_entry(driver_name, rem_name_to_major, dup_entry, &is_unique)) == ERROR) return (ERROR); if (is_unique == NOT_UNIQUE) { /* * found matching entry in /etc/rem_name_to_major */ if (dup_entry != NULL) { if (sscanf(dup_entry, "%s %s", drv, major) != 2) { (void) fprintf(stderr, gettext(ERR_BAD_LINE), rem_name_to_major, dup_entry); return (ERROR); } if (append_to_file(driver_name, major, name_to_major, ' ', " ") == ERROR) { (void) fprintf(stderr, gettext(ERR_NO_UPDATE), name_to_major); return (ERROR); } else { if (delete_entry(rem_name_to_major, driver_name, " ") == ERROR) { (void) fprintf(stderr, gettext(ERR_DEL_ENTRY), driver_name, rem_name_to_major); return (ERROR); } } } else { (void) fprintf(stderr, gettext(ERR_INT_UPDATE), name_to_major); return (ERROR); } /* found matching entry : no errors */ *major_num = atoi(major); return (NOERR); } /* * no match found in rem_name_to_major */ } /* * get maximum major number allowable on this system */ if (modctl(MODRESERVED, NULL, &max_dev) < 0) { perror(NULL); (void) fprintf(stderr, gettext(ERR_MAX_MAJOR)); return (ERROR); } num_list = calloc(max_dev, 1); if (num_list == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); return (ERROR); } /* * read thru name_to_major, marking each major number found * order of name_to_major not relevant */ if ((fp = fopen(name_to_major, "r")) == NULL) { perror(NULL); (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), name_to_major); return (ERROR); } while (fgets(line, sizeof (line), fp) != 0) { if (sscanf(line, "%s %s", drv, drv_majnum) != 2) { (void) fprintf(stderr, gettext(ERR_BAD_LINE), name_to_major, line); (void) fclose(fp); return (ERROR); } num_list[atoi(drv_majnum)] = 1; } /* * read thru rem_name_to_major, marking each major number found * order of rem_name_to_major not relevant */ (void) fclose(fp); fp = NULL; if (stat(rem_name_to_major, &buf) == 0) { if ((fp = fopen(rem_name_to_major, "r")) == NULL) { perror(NULL); (void) fprintf(stderr, gettext(ERR_CANT_ACCESS_FILE), rem_name_to_major); return (ERROR); } while (fgets(line, sizeof (line), fp) != 0) { if (sscanf(line, "%s %s", drv, drv_majnum) != 2) { (void) fprintf(stderr, gettext(ERR_BAD_LINE), name_to_major, line); (void) fclose(fp); return (ERROR); } num_list[atoi(drv_majnum)] = 1; } (void) fclose(fp); } /* find first free major number */ for (i = 0; i < max_dev; i++) { if (num_list[i] != 1) { new_maj = i; break; } } if (new_maj == -1) { (void) fprintf(stderr, gettext(ERR_NO_FREE_MAJOR)); return (ERROR); } (void) sprintf(drv_majnum, "%d", new_maj); if (do_the_update(driver_name, drv_majnum) == ERROR) { return (ERROR); } *major_num = new_maj; return (NOERR); } static void usage() { (void) fprintf(stderr, gettext(USAGE)); } /* * check each alias : * alias list members separated by white space * cannot exist as driver name in /etc/name_to_major * cannot exist as driver or alias name in /etc/driver_aliases */ int aliases_unique(char *aliases) { char *current_head; char *previous_head; char *one_entry; char return_string[MAX_N2M_ALIAS_LINE + 1]; int i, len; int is_unique; len = strlen(aliases); one_entry = calloc(len + 1, 1); if (one_entry == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); return (ERROR); } previous_head = aliases; do { for (i = 0; i <= len; i++) one_entry[i] = 0; current_head = get_entry(previous_head, one_entry, ' '); previous_head = current_head; if ((unique_driver_name(one_entry, name_to_major, return_string, &is_unique)) == ERROR) { free(one_entry); return (ERROR); } if (is_unique != UNIQUE) { (void) fprintf(stderr, gettext(ERR_ALIAS_IN_NAM_MAJ), one_entry); free(one_entry); return (ERROR); } if (unique_drv_alias(one_entry) != NOERR) { free(one_entry); return (ERROR); } } while (*current_head != '\0'); free(one_entry); return (NOERR); } int unique_drv_alias(char *drv_alias) { FILE *fp; char drv[FILENAME_MAX + 1]; char line[MAX_N2M_ALIAS_LINE + 1]; char alias[FILENAME_MAX + 1]; int status = NOERR; fp = fopen(driver_aliases, "r"); if (fp != NULL) { while ((fgets(line, sizeof (line), fp) != 0) && status != ERROR) { if (sscanf(line, "%s %s", drv, alias) != 2) (void) fprintf(stderr, gettext(ERR_BAD_LINE), driver_aliases, line); if ((strcmp(drv_alias, drv) == 0) || (strcmp(drv_alias, alias) == 0)) { (void) fprintf(stderr, gettext(ERR_ALIAS_IN_USE), drv_alias); status = ERROR; } } (void) fclose(fp); return (status); } else { perror(NULL); (void) fprintf(stderr, gettext(ERR_CANT_OPEN), driver_aliases); return (ERROR); } } /* * check that major_num doesn`t exceed maximum on this machine * do this here (again) to support add_drv on server for diskless clients */ int config_driver( char *driver_name, major_t major_num, char *aliases, char *classes, int cleanup_flag, int noload_flag) { int max_dev; int n = 0; char *cmdline[MAX_CMD_LINE]; char maj_num[128]; char *previous; char *current; int exec_status; int len; FILE *fp; if (modctl(MODRESERVED, NULL, &max_dev) < 0) { perror(NULL); (void) fprintf(stderr, gettext(ERR_MAX_MAJOR)); return (ERROR); } if (major_num >= max_dev) { (void) fprintf(stderr, gettext(ERR_MAX_EXCEEDS), major_num, max_dev); return (ERROR); } /* bind major number and driver name */ /* build command line */ cmdline[n++] = DRVCONFIG; if (noload_flag) cmdline[n++] = "-n"; cmdline[n++] = "-b"; if (classes) { cmdline[n++] = "-c"; cmdline[n++] = classes; } cmdline[n++] = "-i"; cmdline[n++] = driver_name; cmdline[n++] = "-m"; (void) sprintf(maj_num, "%lu", major_num); cmdline[n++] = maj_num; if (aliases != NULL) { len = strlen(aliases); previous = aliases; do { cmdline[n++] = "-a"; cmdline[n] = calloc(len + 1, 1); if (cmdline[n] == NULL) { (void) fprintf(stderr, gettext(ERR_NO_MEM)); return (ERROR); } current = get_entry(previous, cmdline[n++], ' '); previous = current; } while (*current != '\0'); } cmdline[n] = (char *)0; exec_status = exec_command(DRVCONFIG_PATH, cmdline); if (exec_status != NOERR) { perror(NULL); remove_entry(cleanup_flag, driver_name); return (ERROR); } /* * now that we have the name to major number bound, * config the driver */ /* * create /reconfigure file so system reconfigures * on reboot if we're actually loading the driver * now */ if (!noload_flag) { fp = fopen(RECONFIGURE, "a"); (void) fclose(fp); } /* build command line */ n = 0; cmdline[n++] = DRVCONFIG; if (noload_flag) cmdline[n++] = "-n"; cmdline[n++] = "-i"; cmdline[n++] = driver_name; cmdline[n++] = "-r"; cmdline[n++] = DEVFS_ROOT; cmdline[n] = (char *)0; exec_status = exec_command(DRVCONFIG_PATH, cmdline); if (exec_status != NOERR) { /* no clean : name and major number are bound */ (void) fprintf(stderr, gettext(ERR_CONFIG), driver_name); return (ERROR); } return (NOERR); } static int update_driver_classes( char *driver_name, char *classes) { /* make call to update the classes file */ return (append_to_file(driver_name, classes, driver_classes, ' ', "\t")); } static int update_driver_aliases( char *driver_name, char *aliases) { /* make call to update the aliases file */ return (append_to_file(driver_name, aliases, driver_aliases, ' ', " ")); } static int update_minor_perm( char *driver_name, char *perm_list) { return (append_to_file(driver_name, perm_list, minor_perm, ',', ":")); } static int do_the_update( char *driver_name, char *major_number) { return (append_to_file(driver_name, major_number, name_to_major, ' ', " ")); } static void signal_rtn() { exit_unlock(); } static int exec_command( char *path, char *cmdline[MAX_CMD_LINE]) { pid_t pid; u_int stat_loc; int waitstat; int exit_status; /* child */ if ((pid = fork()) == 0) { (void) execv(path, cmdline); perror(NULL); return (ERROR); } else if (pid == -1) { /* fork failed */ perror(NULL); (void) fprintf(stderr, gettext(ERR_FORK_FAIL), cmdline); return (ERROR); } else { /* parent */ do { waitstat = waitpid(pid, (int *)&stat_loc, 0); } while ((!WIFEXITED(stat_loc) && !WIFSIGNALED(stat_loc)) || (waitstat == 0)); exit_status = WEXITSTATUS(stat_loc); return (exit_status); } }