/* * mapscsi - program for creating consistent scsi device mappings * * Author: Michael Clark * Copyright Metaparadigm Pte. Ltd. 2001 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "qlogic_api.h" #include "scsi_api.h" #include "mapscsi.h" /* #define DEBUG 1 */ /* file locations */ static char* rules_file = "/etc/mapscsi.rules"; static char* devicemap_file = "/var/state/mapscsi/devicemap"; /* string globals */ static const char* device_tmpl = "/dev/%D/c%hb%ct%tu%l"; static const char* partition_tmpl = "/dev/%D/c%hb%ct%tu%lp%p"; /* command line flags */ static int silent_flag = 0; /* don't show mappings on stdout */ static int execute_flag = 0; /* process rules and make links */ static int bkgnd_flag = 0; /* run in the background */ static int print_flag = 0; /* print device scan information */ static int rm_links_flag = 1; /* remove links in state file */ static int save_state_flag = 1; /* save created links to state file */ static int sg_numeric = 1; /* use numeric sg device names */ /* daemon mode exit signal variable */ static volatile int running; /* linked list globals */ static scsi_map_rule_t *rules_head = NULL; static scsi_map_t *devmap_head = NULL; static scsi_map_t *devmap_last; static int subst_device(char *dest, int dest_size, scsi_device_t *scsidev, const char *template) { const char *r = template; char *w = dest; int len = dest_size; while(*r) { char c; int l; if(*r == '%') { scsi_device_param_t *param = params; r++; c = *r++; while(param->substchar) { if(param->substchar == c) break; param++; } if(param->enc) { l = param->enc(w, ((void*)scsidev)+param->offset, len); } else { fprintf(stderr, "subst_device: invalid subsitution " "symbol '%c'\n", *r); return -1; } if(l == -1) { fprintf(stderr, "subst_device: buffer overun\n"); return -1; } len -= l; w += l; } else { *w++ = *r++; len--; if(len <= 0) { fprintf(stderr, "subst_device: buffer overun\n"); return -1; } } } *w = '\0'; return 0; } static int make_link(const char *linkname, const char* device) { char pathtemp[PATH_MAX+1]; struct stat s; int rc, nc; /* make needed directories */ do { nc = 0; strcpy(pathtemp, linkname); while(dirname(pathtemp) && strcmp(pathtemp, ".") != 0 && strcmp(pathtemp, "/") != 0) { if((rc = stat(pathtemp, &s)) < 0 && errno == ENOENT) { if((rc = mkdir(pathtemp, 0777)) < 0 && errno != ENOENT) { return -1; } else { nc++; } } else if (rc < 0) { return -1; } } } while (nc > 0); /* make the link */ #ifdef DEBUG printf("symlink %s %s\n", device, linkname); #endif if(symlink(device, linkname) < 0) { return -1; } return 0; } static unsigned int hash_partitions() { FILE *parts; char data[256]; unsigned int hash = 12345; if((parts = fopen("/proc/partitions", "r")) == NULL) { perror("hash_partitions: fopen"); exit(1); } while(fgets(data, 255, parts) != NULL) { char *part_dev, *p; /* 4th field is device name */ if(!(part_dev = strtok(data, " "))) continue; if(!(part_dev = strtok(NULL, " "))) continue; if(!(part_dev = strtok(NULL, " "))) continue; if(!(part_dev = strtok(NULL, " "))) continue; p = part_dev; while(*p) hash = hash*129 + (unsigned int)(*p++) + 987654321L; } fclose(parts); return hash; } static unsigned int hash_scsi() { FILE *parts; char data[256]; unsigned int hash = 12345; if((parts = fopen("/proc/scsi/scsi", "r")) == NULL) { perror("hash_scsi: fopen"); exit(1); } while(fgets(data, 255, parts) != NULL) { char *p; p = data; while(*p) hash = hash*129 + (unsigned int)(*p++) + 987654321L; } fclose(parts); return hash; } static void count_partitions() { scsi_device_t *scsidev = NULL; FILE *parts; char data[256]; if((parts = fopen("/proc/partitions", "r")) == NULL) { perror("count_partitions: fopen"); exit(1); } while(fgets(data, 255, parts) != NULL) { char *part_dev, *p; int part_num; /* 4th field is device name */ if(!(part_dev = strtok(data, " "))) continue; if(!(part_dev = strtok(NULL, " "))) continue; if(!(part_dev = strtok(NULL, " "))) continue; if(!(part_dev = strtok(NULL, " "))) continue; /* split the field into device name and part no */ p = part_dev; part_num = 0; while(*p) { if(isdigit(*p) && sscanf(p, "%d", &part_num) == 1) { *p = '\0'; /* set the number of partitions on this device */ if(scsidev = find_dev_by_name(part_dev)) { if(part_num > scsidev->num_parts) scsidev->num_parts = part_num; } break; } p++; } } fclose(parts); } static scsi_map_t* read_devicemap(const char *filename) { char buf[1024]; FILE *devicemap; char *device, *link, *t; scsi_map_t *devmap, *c_devmap; devmap = c_devmap = calloc(1, sizeof(scsi_map_t)); if((devicemap = fopen(filename, "r")) == NULL) { fprintf(stderr, "read_devicemap: cant open '%s': ", devicemap_file); perror(""); return devmap; } while(fgets(buf, 1023, devicemap) != NULL) { /* Remove trailing \n, if present. */ t = buf + strlen(buf) - 1; if(*t == '\n') *t = '\0'; if((device = strtok(buf, "\t")) && (link = strtok(NULL, "\t"))) { #ifdef DEBUG printf("read %s %s\n", device, link); #endif c_devmap->device = strdup(device); c_devmap->link = strdup(link); c_devmap->next = calloc(1, sizeof(scsi_map_t)); c_devmap = c_devmap->next; } else { fprintf(stderr, "read_devicemap: invalid format\n"); exit(1); } } fclose(devicemap); return devmap; } static int write_devicemap(scsi_map_t *devmap, const char *filename) { FILE *devicemap; scsi_map_t *c_devmap = devmap; if((devicemap = fopen(filename, "w+")) == NULL) { fprintf(stderr, "write_devicemap: cant open '%s': ", filename); perror(""); return -1; } while(c_devmap->next) { fprintf(devicemap, "%s\t%s\n", c_devmap->device, c_devmap->link); c_devmap = c_devmap->next; } fclose(devicemap); } static int link_devicemap(scsi_map_t *devmap) { scsi_map_t *c_devmap = devmap; while(c_devmap->next) { if(make_link(c_devmap->link, c_devmap->device)) return -1; c_devmap = c_devmap->next; } return 0; } static int free_devicemap(scsi_map_t *devmap) { scsi_map_t *c_devmap = devmap; while(c_devmap->next) { scsi_map_t *t = c_devmap; c_devmap = c_devmap->next; if(t->link) free(t->link); if(t->device) free(t->device); free(t); } return 0; } static int unlink_devicemap(scsi_map_t *devmap) { scsi_map_t *c_devmap = devmap; char pathtemp[PATH_MAX+1]; while(c_devmap->next) { #ifdef DEBUG printf("unlink %s\n", c_devmap->link); #endif if(unlink(c_devmap->link) < 0) { perror("unlink_devices"); return -1; } strcpy(pathtemp, c_devmap->link); /* remove empty parent directories */ while(dirname(pathtemp) && strcmp(pathtemp, ".") != 0 && strcmp(pathtemp, "/") != 0) unlink(pathtemp); c_devmap = c_devmap->next; } return 0; } static int parse_rule(char *buf, int *line, scsi_map_rule_t **c_rule) { char *k, *v, *t, q; scsi_map_rule_t *c_constraint; *line++; /* Remove trailing \n, if present. */ t = buf + strlen(buf) - 1; if(*t == '\n') *t = '\0'; k = buf; if(*k == '#') return 0; if((*c_rule)->next_constraint) { (*c_rule)->next_rule = calloc(1, sizeof(scsi_map_rule_t)); (*c_rule)->next_rule->rule_num = (*c_rule)->rule_num + 1; (*c_rule) = (*c_rule)->next_rule; } c_constraint = (*c_rule); while(*k) { /* eat whitespace */ while(*k && isspace(*k)) k++; /* find value after = */ v = k; while (*v != '=' && *v != '\0') { if(!isalnum(*v)) { fprintf(stderr, "read_rules: invalid paramater " "character '%c', line %d\n", *v, *line); return -1; } v++; } if( *v == '\0' ) break; *v++ = '\0'; if(*v == '"' || *v == '\'') { q = *v; v++; } else q = '\0'; /* terminate value */ t = v; while(*t && ((q && *t != q) || (!q && *t != ' ' && *t != '\t'))) t++; if(q && *t != q) { fprintf(stderr, "read_rules: unterminated quotes on " "parameter '%s', line %d\n", k, *line); return -1; } if(*t) *t++ = '\0'; c_constraint->key = strdup(k); c_constraint->value = strdup(v); c_constraint->next_constraint = calloc(1, sizeof(scsi_map_rule_t)); c_constraint = c_constraint->next_constraint; k = t; } return 0; } static int read_rules() { char buf[1024]; FILE *conf; int line; scsi_map_rule_t *c_rule; if((conf = fopen(rules_file, "r")) == NULL) return -1; c_rule = rules_head = calloc(1, sizeof(scsi_map_rule_t)); c_rule->rule_num = 1; while(fgets(buf, 1023, conf) != NULL) { if(parse_rule(buf, &line, &c_rule)) exit(1); } if(c_rule->next_constraint) { c_rule->next_rule = calloc(1, sizeof(scsi_map_rule_t)); c_rule->next_rule->rule_num = c_rule->rule_num + 1; } fclose(conf); return 0; } static int default_rules() { rules_head = calloc(1, sizeof(scsi_map_rule_t)); rules_head->next_rule = calloc(1, sizeof(scsi_map_rule_t)); rules_head->next_constraint = calloc(1, sizeof(scsi_map_rule_t)); rules_head->rule_num = 1; rules_head->key = strdup("map"); rules_head->value = strdup(device_tmpl); } static void str_path(char *p) { char *t, *r, *w = p; t = r = strdup(p); while(*r) { if(*r != ' ') *w++ = *r++; else r++; } *w = '\0'; free(t); while(*p) { if(!isalnum(*p) && *p != '/') *p = '-'; p++; } } static int create_links(scsi_device_t *scsidev, const char* map, const char* partmap) { char dev[PATH_MAX+1]; char link[PATH_MAX+1]; int p; if(map) { if(subst_device(link, PATH_MAX, scsidev, map) == 0) { str_path(link); if(!silent_flag) printf("%s -> %s\n", link, scsidev->device); devmap_last->link = strdup(link); devmap_last->device = strdup(scsidev->device);; devmap_last->next = calloc(1, sizeof(scsi_map_t)); devmap_last = devmap_last->next; } else { exit(1); } } if(partmap) { for(p=1; p <= scsidev->num_parts; p++) { scsidev->part = p; if(subst_device(link, PATH_MAX, scsidev, partmap) == 0) { str_path(link); sprintf(dev, "%s%d", scsidev->device, p); if(!silent_flag) printf("%s -> %s\n", link, dev); devmap_last->link = strdup(link); devmap_last->device = strdup(dev);; devmap_last->next = calloc(1, sizeof(scsi_map_t)); devmap_last = devmap_last->next; } else { exit(1); } } } } int match_rules(scsi_device_t *scsidev) { char buf[256]; scsi_map_rule_t *c_rule = rules_head; /* loop through the rules */ while(c_rule->next_rule) { scsi_map_rule_t *c_constraint = c_rule; int rules = 0, matches = 0; char *map = NULL, *partmap = NULL; /* see if we can match this rule */ while(c_constraint->next_constraint) { if(strcmp(c_constraint->key, "map") == 0) { map = c_constraint->value; } else if(strcmp(c_constraint->key, "partmap") == 0) { partmap = c_constraint->value; } else { scsi_device_param_t *param = params; while(param->substchar) { if(strcmp(param->name, c_constraint->key) == 0) break; param++; } /* okay found param, see if values match */ if(param->substchar) { rules++; if(param->enc(buf, ((void*)scsidev)+param->offset, 255) >= 0 && strcmp(buf, c_constraint->value) == 0) { matches++; } } } c_constraint = c_constraint->next_constraint; } if(rules == matches) { if(map && !partmap) { partmap = (char*)malloc(strlen(map)+4); strcpy(partmap, map); strcat(partmap, "p%p"); create_links(scsidev, map, partmap); free(partmap); } else if(!map && !partmap) { create_links(scsidev, device_tmpl, partition_tmpl); } else { create_links(scsidev, map, partmap); } } c_rule = c_rule->next_rule; } } static void do_mapscsi() { if(scsidev_head) free_scsidev(scsidev_head); scsidev_head = calloc(1, sizeof(scsi_device_t)); if(devmap_head) free_devicemap(devmap_head); devmap_head = devmap_last = calloc(1, sizeof(scsi_map_t)); scan_scsi_devices(sg_numeric); map_sg_devices(TYPE_DISK, "sd", 0); map_sg_devices(TYPE_MOD, "sd", 0); /* this can block ?? */ map_sg_devices(TYPE_TAPE, "st", 1); map_sg_devices(TYPE_WORM, "scd", 1); map_sg_devices(TYPE_ROM, "scd", 1); map_sg_devices(TYPE_SCANNER, "sg", 1); /* need to optimize */ map_sg_devices(TYPE_PROCESSOR, "sg", 1); /* need to optimize */ qlogic_map_wwn_to_sd(); qlogic_free(); count_partitions(); } static void parse_command_line(int argc, char *argv[]) { int c; int error_flag = 0, help_flag = 0; while ((c = getopt(argc, argv, "hpanxbsRSc:d:")) != EOF) switch (c) { case 'p': print_flag++; break; case 'h': help_flag++; break; case 'a': sg_numeric = 0; break; case 'n': sg_numeric = 1; break; case 'x': execute_flag = 1; break; case 'b': bkgnd_flag = 1; silent_flag = 1; break; case 's': silent_flag = 1; break; case 'c': rules_file = optarg; break; case 'd': devicemap_file = optarg; break; case 'R': rm_links_flag = 0; break; case 'S': save_state_flag = 0; break; case '?': error_flag++; } /* read rules from the command line */ if (argc - optind > 0) { int k, line=0; scsi_map_rule_t *c_rule; c_rule = rules_head = calloc(1, sizeof(scsi_map_rule_t)); c_rule->rule_num = 1; for(k=optind; k < argc; k++) { if(parse_rule(argv[k], &line, &c_rule)) exit(1); } if(c_rule->next_constraint) { c_rule->next_rule = calloc(1, sizeof(scsi_map_rule_t)); c_rule->next_rule->rule_num = c_rule->rule_num + 1; } } if(print_flag + execute_flag + bkgnd_flag > 1) { fprintf(stderr, "must choose only one of -x -b or -p\n"); error_flag++; } if (error_flag || help_flag) { scsi_device_param_t *param = params; fprintf(stderr, "usage:\t%s [-h] [-x] [-s] [-n] [-a] [-R] [-S]\\\n" "\t[-c ] [-d ] \\\n" "\t[attrib=value [attrib=value]...]\n\n", argv[0]); fprintf(stderr, "-h show this help message\n" "-p print - print device scan information\n" "-x execute - actually make the links\n" "-b background - poll for changes in the background\n" "-s silent - don't show mappings on stdout\n" "-n numeric sg device names eg. /dev/sg0 (default)\n" "-a alpha sg device names eg. /dev/sga\n" "-R don't remove previous links\n" "-S don't save state in /var/state/mapscsi/devicemap\n" "-c mapscsi rules file (default /etc/mapscsi.rules)\n" "-d mapscsi state file (default /var/state/mapscsi/devicemap)\n\n" "example /etc/mapscsi.rules\n\n"); fprintf(stderr, "type=disk map='%s' partmap='%s'\n", device_tmpl, partition_tmpl); fprintf(stderr, "type=cdrom map='%s'\n", "/dev/scsi/cdrom-%V-%P"); fprintf(stderr, "%s", "\nTemplate codes:\n"); while(param->substchar) { fprintf(stderr, "%c\t%s\n", param->substchar, param->description); param++; } exit(1); } } static void sighandler(int signal) { switch(signal) { case SIGQUIT: case SIGTERM: syslog(LOG_INFO, "exiting on SIGTERM"); running = 0; break; default: break; } } int main(int argc, char **argv) { int k; scsi_device_t *scsidev; scsi_map_t *devmap_old = NULL; parse_command_line(argc, argv); if(bkgnd_flag) { unsigned int last_part_hash = 0, last_scsi_hash = 0; int pid; if ((pid = fork()) == -1) { perror("Unable to fork:"); exit(1); } if (pid) exit(0); close(0); close(1); close(2); open("/dev/null", O_RDONLY); open("/dev/null", O_WRONLY); open("/dev/null", O_WRONLY); chdir("/"); setsid(); signal(SIGTERM, &sighandler); running = 1; openlog("mapscsi", LOG_PID, LOG_DAEMON); if(!rules_head && read_rules()) { syslog(LOG_ERR, "%s reading rules file %s\n" "Using default rules", strerror(errno), rules_file); default_rules(); } while(running) { unsigned int part_hash, scsi_hash; part_hash = hash_partitions(); scsi_hash = hash_scsi(); if(part_hash != last_part_hash || scsi_hash != last_scsi_hash) { if(part_hash && last_part_hash) syslog(LOG_INFO, "device change detected. rescanning..."); do_mapscsi(); scsidev = scsidev_head; while(scsidev->next) { if(scsidev->device) match_rules(scsidev); scsidev = scsidev->next; } if(rm_links_flag) { if(!devmap_old) devmap_old = read_devicemap(devicemap_file); if(unlink_devicemap(devmap_old)) { syslog(LOG_ERR, "error unlinking old devices"); } free_devicemap(devmap_old); } if(link_devicemap(devmap_head)) { syslog(LOG_ERR, "error linking new devices"); exit(1); } if(save_state_flag && write_devicemap(devmap_head, devicemap_file)) { syslog(LOG_ERR, "error writing device map"); exit(1); } devmap_old = devmap_head; devmap_head = NULL; /* prevent freeing of devmap */ } last_part_hash = part_hash; last_scsi_hash = scsi_hash; if(running) sleep(5); } closelog(); } do_mapscsi(); if(print_flag) { scsidev = scsidev_head; while(scsidev->next) { if(scsidev->device) print_scsi_dev_info(scsidev); scsidev = scsidev->next; } } else { if(!rules_head && read_rules()) { if(!silent_flag) fprintf(stderr, "%s reading rules file %s:" " using default rules\n", strerror(errno), rules_file); default_rules(); } scsidev = scsidev_head; while(scsidev->next) { if(scsidev->device) match_rules(scsidev); scsidev = scsidev->next; } if(execute_flag) { if(rm_links_flag) { devmap_old = read_devicemap(devicemap_file); if(unlink_devicemap(devmap_old)) { fprintf(stderr,"error unlinking old devices\n"); } free_devicemap(devmap_old); } if(link_devicemap(devmap_head)) { fprintf(stderr,"error linking new devices\n"); exit(1); } if(save_state_flag && write_devicemap(devmap_head, devicemap_file)) { fprintf(stderr,"error writing device map\n"); exit(1); } } } return 0; }