diff options
Diffstat (limited to 'nvme-models.c')
-rw-r--r-- | nvme-models.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/nvme-models.c b/nvme-models.c new file mode 100644 index 0000000..f638e4d --- /dev/null +++ b/nvme-models.c @@ -0,0 +1,357 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include "nvme-models.h" + +static char *_fmt1 = "/sys/class/nvme/nvme%d/device/subsystem_vendor"; +static char *_fmt2 = "/sys/class/nvme/nvme%d/device/subsystem_device"; +static char *_fmt3 = "/sys/class/nvme/nvme%d/device/vendor"; +static char *_fmt4 = "/sys/class/nvme/nvme%d/device/device"; +static char *_fmt5 = "/sys/class/nvme/nvme%d/device/class"; + +static char fmt1[78]; +static char fmt2[78]; +static char fmt3[78]; +static char fmt4[78]; +static char fmt5[78]; + +static char *device_top; +static char *device_mid; +static char *device_final; +static char *class_top; +static char *class_mid; +static char *class_final; + + + +static void free_all(void) +{ + free(device_top); + device_top = NULL; + free(device_mid); + device_mid = NULL; + free(device_final); + device_final = NULL; + free(class_top); + class_top = NULL; + free(class_mid); + class_mid = NULL; + free(class_final); + class_final = NULL; +} + +static char *find_data(char *data) +{ + while (*data != '\0') { + if (*data >= '0' && *data <= '9') + return data; + data++; + } + return NULL; +} + +static char *locate_info(char *data, bool is_inner, bool is_class) +{ + char *orig = data; + char *locate; + if (!data) + return orig; + + locate = find_data(data); + if (!locate) + return orig; + if (is_class) + return locate + 4; + if (!is_inner) + /* 4 to get over the number, 2 for spaces */ + return locate + 4 + 2; + + /* Inner data, has "sub_ven(space)sub_dev(space)(space)string */ + return locate + 4 + 1 + 4 + 2; +} + +static void format_and_print(char *save) +{ + + if (!class_mid) { + if (device_final) + snprintf(save, 1024, "%s %s %s", + locate_info(device_top, false, false), + locate_info(device_mid, false, false), + locate_info(device_final, true, false)); + else + snprintf(save, 1024, "%s %s", + locate_info(device_top, false, false), + locate_info(device_mid, false, false)); + } else { + if (device_final) + snprintf(save, 1024, "%s: %s %s %s", + locate_info(class_mid, false, true), + locate_info(device_top, false, false), + locate_info(device_mid, false, false), + locate_info(device_final, true, false)); + else + snprintf(save, 1024, "%s: %s %s", + locate_info(class_mid, false, true), + locate_info(device_top, false, false), + locate_info(device_mid, false, false)); + } +} + +static void format_all(char *save, char *vendor, char *device) +{ + if (device_top && device_mid) + format_and_print(save); + + else if (device_top && !device_mid && class_mid) + snprintf(save, 1024, "%s: %s Device %s", + locate_info(class_mid, false, true), + locate_info(device_top, false, false), + device); + + else if (!device_top && class_mid) + snprintf(save, 1024, "%s: Vendor %s Device %s", + locate_info(class_mid, false, true), + vendor, + device); + else + snprintf(save, 1024, "Unknown device"); +} + +static int is_final_match(char *line, char *search) +{ + return !memcmp(&line[2], search, 2); +} + +static int is_inner_sub_vendev(char *line, char *search, char *search2) +{ + char combine[10]; + snprintf(combine, sizeof(combine), "%s %s", &search[2], &search2[2]); + if (line[0] != '\t' && line[1] != '\t') + return 0; + + return !memcmp(combine, &line[2], 9); +} + +static int is_mid_level_match(char *line, char *device, bool class) +{ + if (!class) + return !memcmp(&line[1], &device[2], 4); + + return !memcmp(&line[1], device, 2); +} + +static inline bool is_comment(char *line) +{ + return line[0] == '#'; +} + +static int is_top_level_match(char *line, const char* device, bool class) +{ + if (line[0] == '\t') + return false; + if (line[0] == '#') + return false; + if (!class) + return !memcmp(line, &device[2], 4); + if (line[0] != 'C') + return false; + /* Skipping C(SPACE) 0x */ + return !memcmp(&line[2], &device[2], 2); +} + +static inline int is_tab(char *line) +{ + return line[0] == '\t'; +} + +static inline int is_class_info(char *line) +{ + return !memcmp(line, "# C class", 9); +} + +static void parse_vendor_device(char **line, FILE *file, + char *device, char *subdev, + char *subven) +{ + bool device_single_found = false; + size_t amnt = 1024; + size_t found = 0; + char *newline; + + while ((found = getline(line, &amnt, file)) != -1) { + newline = *line; + if (is_comment(newline)) + continue; + if (!is_tab(newline)) + return; + + newline[found - 1] = '\0'; + if (!device_single_found && is_mid_level_match(newline, device, false)) { + device_single_found = true; + device_mid = strdup(newline); + continue; + } + + if (device_single_found && is_inner_sub_vendev(newline, subven, subdev)) { + device_final = strdup(newline); + break; + } + } +} + +static void pull_class_info(char **_newline, FILE *file, char *class) +{ + size_t amnt; + size_t size = 1024; + bool top_found = false; + bool mid_found = false; + char *newline; + + while ((amnt = getline(_newline, &size, file)) != -1) { + newline = *_newline; + newline[amnt - 1] = '\0'; + if (!top_found && is_top_level_match(newline, class, true)) { + class_top = strdup(newline); + top_found = true; + continue; + } + if (!mid_found && top_found && + is_mid_level_match(newline, &class[4], true)) { + class_mid = strdup(newline); + mid_found = true; + continue; + } + if (top_found && mid_found && + is_final_match(newline, &class[6])) { + class_final = strdup(newline); + break; + } + } +} + +static int read_sys_node(char *where, char *save, size_t savesz) +{ + char *new; + int fd, ret = 0, len; + fd = open(where, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open %s with errno %s\n", + where, strerror(errno)); + return 1; + } + /* -1 so we can safely use strstr below */ + len = read(fd, save, savesz - 1); + if (!len) + ret = 1; + else { + save[len] = '\0'; + new = strstr(save, "\n"); + if (new) + new[0] = '\0'; + } + close(fd); + return ret; +} + +static FILE *open_pci_ids(void) +{ + int i; + char *pci_ids_path; + FILE *fp; + + const char* pci_ids[] = { + "/usr/share/hwdata/pci.ids", /* RHEL */ + "/usr/share/pci.ids", /* SLES */ + "/usr/share/misc/pci.ids", /* Ubuntu */ + NULL + }; + + /* First check if user gave pci ids in environment */ + if ((pci_ids_path = getenv("PCI_IDS_PATH")) != NULL) { + if ((fp = fopen(pci_ids_path, "r")) != NULL) { + return fp; + } else { + /* fail if user provided environment variable but could not open */ + perror(pci_ids_path); + return NULL; + } + } + + /* NO environment, check in predefined places */ + for (i = 0; pci_ids[i] != NULL; i++) { + if ((fp = fopen(pci_ids[i], "r")) != NULL) + return fp; + } + + fprintf(stderr, "Could not find pci.ids file\n"); + return NULL; +} + +char *nvme_product_name(int id) +{ + char *line = NULL; + ssize_t amnt; + char vendor[7] = { 0 }; + char device[7] = { 0 }; + char sub_device[7] = { 0 }; + char sub_vendor[7] = { 0 }; + char class[13] = { 0 }; + size_t size = 1024; + char ret; + FILE *file = open_pci_ids(); + + if (!file) + goto error1; + + snprintf(fmt1, 78, _fmt1, id); + snprintf(fmt2, 78, _fmt2, id); + snprintf(fmt3, 78, _fmt3, id); + snprintf(fmt4, 78, _fmt4, id); + snprintf(fmt5, 78, _fmt5, id); + + ret = read_sys_node(fmt1, sub_vendor, 7); + ret |= read_sys_node(fmt2, sub_device, 7); + ret |= read_sys_node(fmt3, vendor, 7); + ret |= read_sys_node(fmt4, device, 7); + ret |= read_sys_node(fmt5, class, 13); + if (ret) + goto error0; + + line = malloc(1024); + if (!line) { + fprintf(stderr, "malloc: %s\n", strerror(errno)); + goto error0; + } + + while ((amnt = getline(&line, &size, file)) != -1) { + if (is_comment(line) && !is_class_info(line)) + continue; + if (is_top_level_match(line, vendor, false)) { + line[amnt - 1] = '\0'; + free(device_top); + device_top = strdup(line); + parse_vendor_device(&line, file, + device, + sub_device, + sub_vendor); + } + if (is_class_info(line)) + pull_class_info(&line, file, class); + } + fclose(file); + format_all(line, vendor, device); + free_all(); + return line; +error0: + fclose(file); +error1: + return strdup("NULL"); +} |