/* * dasdinfo - Display unique DASD ID, either UID or volser * * Copyright IBM Corp. 2007, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/dasd_base.h" #include "lib/util_file.h" #include "lib/util_libc.h" #include "lib/util_opt.h" #include "lib/util_prg.h" #include "lib/util_path.h" #include "lib/zt_common.h" #include "dasdinfo_cli.h" #define RD_BUFFER_SIZE 80 #define TEMP_DEV_MAX_RETRIES 1000 #ifndef FTW_CONTINUE #define FTW_CONTINUE 0 #define FTW_STOP 1 #endif static const struct util_prg prg = { .desc = "Display DASD volume serial number and ID information", .args = "-i BUSID | -b BLOCKDEV | -d DEVNODE", .copyright_vec = { { .owner = "IBM Corp.", .pub_first = 2007, }, UTIL_PRG_COPYRIGHT_END } }; /* needed because ftw can not pass arbitrary arguments */ static char *searchbusid; static char *busiddir; struct volume_label { char volkey[4]; char vollbl[4]; char volid[6]; } __packed; static char ebc_to_asc[256] = { /* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */ 0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F, /* 0x08 -GE -SPS -RPT VT FF CR SO SI */ 0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, /* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC */ 0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07, /* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB */ 0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC */ 0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B, /* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL */ 0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07, /* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */ 0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04, /* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */ 0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A, /* 0x40 SP RSP ? ---- */ 0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86, /* 0x48 . < ( + | */ 0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C, /* 0x50 & ---- */ 0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07, /* 0x58 ? ! $ * ) ; */ 0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA, /* 0x60 - / ---- ? ---- ---- ---- */ 0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F, /* 0x68 ---- , % _ > ? */ 0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F, /* 0x70 --- ---- ---- ---- ---- ---- ---- */ 0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, /* 0x78 * ` : # @ ' = " */ 0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22, /* 0x80 * a b c d e f g */ 0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, /* 0x88 h i ---- ---- ---- */ 0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1, /* 0x90 ? j k l m n o p */ 0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, /* 0x98 q r ---- ---- */ 0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07, /* 0xA0 ~ s t u v w x */ 0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, /* 0xA8 y z ---- ---- ---- ---- */ 0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07, /* 0xB0 ^ ---- ? ---- */ 0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC, /* 0xB8 ---- [ ] ---- ---- ---- ---- */ 0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07, /* 0xC0 { A B C D E F G */ 0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, /* 0xC8 H I ---- ? ---- */ 0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07, /* 0xD0 } J K L M N O P */ 0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, /* 0xD8 Q R ---- ? */ 0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98, /* 0xE0 \ S T U V W X */ 0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, /* 0xE8 Y Z ---- ? ---- ---- ---- */ 0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07, /* 0xF0 0 1 2 3 4 5 6 7 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, /* 0xF8 8 9 ---- ---- ? ---- ---- ---- */ 0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07 }; static char *dinfo_ebcdic_dec(char *source, char *target, int l) { int i; for (i = 0; i < l; i++) target[i] = ebc_to_asc[(unsigned char)(source[i])]; return target; } static int dinfo_read_dasd_uid(char *uidfile, char *readbuf) { return util_file_read_line(readbuf, RD_BUFFER_SIZE, uidfile); } static int dinfo_read_dasd_vlabel(char *device, struct volume_label *vlabel, char *readbuf) { struct volume_label tmp; int vlsize = sizeof(struct volume_label); dasd_information2_t dasd_info; unsigned long vlabel_start; unsigned int blksize; char vollbl[5]; int f; char *space; if (dasd_get_blocksize(device, &blksize) != 0) { warnx("Unable to figure out block size"); goto error; } if (dasd_get_info(device, &dasd_info) != 0) { warnx("Unable to figure out DASD information"); goto error; } f = open(device, O_RDONLY); if (f < 0) { warnx("Could not open device node"); goto error; } vlabel_start = dasd_info.label_block * blksize; if (lseek(f, vlabel_start, SEEK_SET) < 0) goto error_close; memset(vlabel, 0, vlsize); if (read(f, vlabel, vlsize) != vlsize) { warnx("Could not read volume label"); goto error_close; } if (dasd_info.FBA_layout) { memset(&tmp, 0, vlsize); memcpy(&tmp, vlabel, vlsize); memcpy(vlabel->vollbl, &tmp, vlsize - 4); } close(f); memset(readbuf, 0, 7); memset(vollbl, 0, 5); strncpy(vollbl, vlabel->vollbl, 4); dinfo_ebcdic_dec(vollbl, vollbl, 4); if ((strncmp(vollbl, "VOL1", 4) == 0) || (strncmp(vollbl, "LNX1", 4) == 0) || (strncmp(vollbl, "CMS1", 4) == 0)) { strncpy(readbuf, vlabel->volid, 6); dinfo_ebcdic_dec(readbuf, readbuf, 6); space = strchr(readbuf, ' '); if (space) *space = 0; } else { strcpy(readbuf, ""); } return 0; error_close: close(f); error: return -1; } static void *dinfo_malloc(size_t size) { void *result; result = malloc(size); if (!result) warnx("Could not allocate %lu bytes of memory", size); return result; } static char *dinfo_make_path(char *dirname, char *filename) { char *result; size_t len; len = strlen(dirname) + strlen(filename) + 2; result = (char *)dinfo_malloc(len); if (!result) return NULL; sprintf(result, "%s/%s", dirname, filename); return result; } static int dinfo_create_devnode(dev_t dev, char **devno) { char *result; char *pathname[] = { "/dev", getenv("TMPDIR"), "/tmp", getenv("HOME"), ".", "/"}; char filename[] = "dasdinfo0000"; mode_t mode; unsigned int path; unsigned int retry; int rc; int fd; mode = S_IFBLK | S_IRWXU; /* Try several locations for the temporary device node. */ for (path = 0; path < ARRAY_SIZE(pathname); path++) { if (!pathname[path]) continue; for (retry = 0; retry < TEMP_DEV_MAX_RETRIES; retry++) { snprintf(filename, sizeof(filename), "dasdinfo%04d", retry); result = dinfo_make_path(pathname[path], filename); if (!result) return -1; rc = mknod(result, mode, dev); if (rc == 0) { /* Need this test to cover * 'nodev'-mounted * filesystems. */ fd = open(result, O_RDONLY); if (fd != -1) { close(fd); *devno = result; return 0; } remove(result); retry = TEMP_DEV_MAX_RETRIES; } else if (errno != EEXIST) { retry = TEMP_DEV_MAX_RETRIES; } free(result); } } warnx("Error: Unable to create temporary device node"); return -1; } static void dinfo_free_devnode(char *device) { if (remove(device)) warnx("Warning: Could not remove temporary file %s", device); } static int dinfo_extract_dev(dev_t *dev, char *str) { char tmp[RD_BUFFER_SIZE]; char *p = NULL; int ma, mi; memset(tmp, 0, RD_BUFFER_SIZE); util_strlcpy(tmp, str, RD_BUFFER_SIZE); p = strchr(tmp, ':'); if (!p) { warnx("Error: unable to extract major/minor"); return -1; } *p = '\0'; ma = atoi(tmp); mi = atoi(p + sizeof(char)); *dev = makedev(ma, mi); return 0; } static int dinfo_get_dev_from_blockdev(char *blockdev, dev_t *dev) { char *readbuf = NULL; char *path; readbuf = dinfo_malloc(RD_BUFFER_SIZE); if (!readbuf) { warnx("Error: Not enough memory to allocate readbuffer"); return -1; } path = util_path_sysfs("block/%s/dev", blockdev); if (util_file_read_line(readbuf, RD_BUFFER_SIZE, path) < 0) { free(path); free(readbuf); return -1; } free(path); if (dinfo_extract_dev(dev, readbuf) != 0) { free(readbuf); return -1; } free(readbuf); return 0; } static int dinfo_is_busiddir(const char *fpath, const struct stat *UNUSED(sb), int tflag, struct FTW *ftwbuf) { char *tempdir; char *linkdir; if (tflag != FTW_D || (strncmp((fpath + ftwbuf->base), searchbusid, strlen(searchbusid)) != 0)) return FTW_CONTINUE; /* * ensure that the found entry is a busid and not a * subchannel ID * for large systems subchannel IDs may look like busids */ if (asprintf(&tempdir, "%s/driver", fpath) < 0) return -1; linkdir = util_readlink(tempdir); free(tempdir); if (!strstr(linkdir, "dasd")) { free(linkdir); return FTW_CONTINUE; } free(linkdir); free(busiddir); busiddir = strdup(fpath); if (!busiddir) return -1; return FTW_STOP; } static int dinfo_find_entry(const char *dir, const char *searchstring, char type, char **result) { DIR *directory = NULL; struct dirent *dir_entry = NULL; directory = opendir(dir); if (!directory) return -1; while ((dir_entry = readdir(directory)) != NULL) { /* compare if the found entry has exactly the same name and type * as searched */ if ((strncmp(dir_entry->d_name, searchstring, strlen(searchstring)) == 0) && (dir_entry->d_type & type)) { *result = strdup(dir_entry->d_name); if (!*result) goto out; closedir(directory); return 0; /* found */ } } out: closedir(directory); return -1; /* nothing found or error */ } static int dinfo_get_blockdev_from_busid(char *busid, char **blkdev) { int flags = FTW_PHYS; /* do not follow links */ int rc = -1; char *tempdir = NULL; char *result = NULL; char *sysfsdir = util_path_sysfs("devices/"); /* dinfo_is_devnode needs to know the busid */ searchbusid = busid; if (nftw(sysfsdir, dinfo_is_busiddir, 200, flags) != FTW_STOP) goto out; /* * new sysfs: busid directory contains a directory 'block' * which contains a directory 'dasdXXX' */ rc = dinfo_find_entry(busiddir, "block", DT_DIR, &result); if (rc == 0) { if (asprintf(&tempdir, "%s/%s/", busiddir, result) < 0) { rc = -1; goto out2; } rc = dinfo_find_entry(tempdir, "dasd", DT_DIR, blkdev); } else { /* * old sysfs: entry for busiddir contain a link * 'block:dasdXXX' */ rc = dinfo_find_entry(busiddir, "block:", DT_LNK, &result); if (rc != 0) goto out2; *blkdev = strdup(strchr(result, ':') + 1); if (!*blkdev) rc = -1; } out: free(tempdir); out2: free(busiddir); free(result); free(sysfsdir); return rc; } static int dinfo_get_uid_from_devnode(char **uidfile, char *devnode) { struct stat stat_buffer; char stat_dev[RD_BUFFER_SIZE]; char *readbuf; DIR *directory = NULL; struct dirent *dir_entry = NULL; int rc = 0; char *path; if (stat(devnode, &stat_buffer) != 0) { warnx("Error: could not stat %s", devnode); return -1; } sprintf(stat_dev, "%d:%d", major(stat_buffer.st_rdev), minor(stat_buffer.st_rdev)); path = util_path_sysfs("block/"); directory = opendir(path); if (!directory) { warnx("Error: could not open directory %s", path); free(path); return -1; } readbuf = dinfo_malloc(RD_BUFFER_SIZE); if (!readbuf) { warnx("Error: Not enough memory to allocate readbuffer"); free(path); return -1; } while ((dir_entry = readdir(directory)) != NULL) { if (util_file_read_line(readbuf, RD_BUFFER_SIZE, "%s%s/dev", path, dir_entry->d_name) < 0) continue; if (strncmp(stat_dev, readbuf, MAX(strlen(stat_dev), strlen(readbuf) - 1)) == 0) { rc = snprintf(*uidfile, RD_BUFFER_SIZE, "%s%s/device/uid", path, dir_entry->d_name); if (rc >= RD_BUFFER_SIZE) { fprintf(stderr, "Error: Device name was truncated\n"); free(path); free(readbuf); return -1; } break; } } closedir(directory); free(path); free(readbuf); return 0; } int main(int argc, char *argv[]) { char *uidfile = NULL; char *device = NULL; char *readbuf = NULL; dev_t dev; int export = 0; int c; int print_uid = 0; int print_extended_uid = 0; int print_vlabel = 0; char *blockdev = NULL; char *busid = NULL; char *devnode = NULL; struct volume_label vlabel; char *srchuid; int i, rc = 0; util_prg_init(&prg); util_opt_init(opt_vec, NULL); while (1) { c = util_opt_getopt_long(argc, argv); if (c == -1) break; switch (c) { case 'a': print_uid = 1; print_vlabel = 1; print_extended_uid = 1; break; case 'u': print_uid = 1; break; case 'x': print_extended_uid = 1; break; case 'l': print_vlabel = 1; break; case 'i': busid = strdup(optarg); break; case 'b': blockdev = strdup(optarg); break; case 'd': devnode = strdup(optarg); break; case 'e': export = 1; break; case 'h': util_prg_print_help(); util_opt_print_help(); exit(EXIT_SUCCESS); case 'v': util_prg_print_version(); exit(EXIT_SUCCESS); default: fprintf(stderr, "Try 'dasdinfo --help' for more information.\n"); exit(1); } } if (!busid && !blockdev && !devnode) errx(EXIT_FAILURE, "Error: please specify a device using either -b, -i or -d"); if ((busid && blockdev) || (busid && devnode) || (blockdev && devnode)) errx(EXIT_FAILURE, "Error: please specify device only once, either -b, -i or -d"); if (!print_uid && !print_extended_uid && !print_vlabel) errx(EXIT_FAILURE, "Error: no action specified (e.g. -u)"); readbuf = dinfo_malloc(RD_BUFFER_SIZE); if (!readbuf) exit(1); /* try to read the uid attribute */ if (busid) { uidfile = util_path_sysfs("bus/ccw/devices/%s/uid", busid); } else if (blockdev) { uidfile = util_path_sysfs("block/%s/device/uid", blockdev); } else if (devnode) { uidfile = dinfo_malloc(RD_BUFFER_SIZE); if (!uidfile) exit(1); if (dinfo_get_uid_from_devnode(&uidfile, devnode) != 0) goto error; } if (export) { printf("ID_BUS=ccw\n"); printf("ID_TYPE=disk\n"); } if (print_uid) { if (dinfo_read_dasd_uid(uidfile, readbuf) == 0) { /* look for the 4th '.' and cut there */ srchuid = readbuf - 1; for (i = 0; i < 4; ++i) { srchuid = index(srchuid + 1, '.'); if (!srchuid) break; } if (srchuid) srchuid[0] = '\0'; if (export) printf("ID_UID=%s\n", readbuf); else printf("%s\n", readbuf); if (!print_vlabel && !print_extended_uid) goto out; } } if (print_extended_uid) { if (dinfo_read_dasd_uid(uidfile, readbuf) == 0) { if (export) printf("ID_XUID=%s\n", readbuf); else printf("%s\n", readbuf); if (!print_vlabel) goto out; } } /* there is no uid, try to read the volume serial */ if (busid) { char *blockdev_name = NULL; if (dinfo_get_blockdev_from_busid(busid, &blockdev_name) != 0) goto error; if (dinfo_get_dev_from_blockdev(blockdev_name, &dev) != 0) goto error; if (dinfo_create_devnode(dev, &device) != 0) goto error; free(blockdev_name); } else if (blockdev) { if (dinfo_get_dev_from_blockdev(blockdev, &dev) != 0) goto error; if (dinfo_create_devnode(dev, &device) != 0) goto error; } else if (devnode) { device = dinfo_malloc(RD_BUFFER_SIZE); if (!device) exit(1); strcpy(device, devnode); } if (dinfo_read_dasd_vlabel(device, &vlabel, readbuf) == 0) { if (export) printf("ID_SERIAL=%s\n", readbuf); else printf("%s\n", readbuf); goto out; } error: warnx("Error: could not read unique DASD ID"); rc = 1; out: if (device && (busid || blockdev)) dinfo_free_devnode(device); free(uidfile); free(device); free(readbuf); exit(rc); }