summaryrefslogtreecommitdiffstats
path: root/nvme-models.c
diff options
context:
space:
mode:
Diffstat (limited to 'nvme-models.c')
-rw-r--r--nvme-models.c357
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");
+}