/
githubmirror
/
nvme-cli
Обзор
Документация
Войти
/
githubmirror
/
nvme-cli
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
libnvme/src/nvme/ctrl-map.c
1 032 строки
23 KB
Daniel Wagner
shared: move compiler-attributes to common code
29 июл 2026, 15:04
29 июл 2026, 15:04
88e3754
Код
Авторство
О чём код?
// SPDX-License-Identifier: LGPL-2.1-or-later /* * This file is part of libnvme. * Copyright (c) 2026 Micron Technology, Inc. * * Authors: Brandon Capener <bcapener@micron.com> */ #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <wchar.h> #include <ccan/hash/hash.h> #include <ccan/htable/htable_type.h> #include <compiler-attributes.h> #include <libnvme.h> #include "private.h" #include "private-ctrl-map.h" #include "cleanup.h" #include <windows.h> #include <cfgmgr32.h> #include <setupapi.h> #include <winioctl.h> #include <ntddscsi.h> #include <initguid.h> #include <devpkey.h> struct ctrl_map_entry { char *ctrl_name; /* format "nvmeX" */ WCHAR *ctrl_path; /* format "\\\\?\\pci..." */ struct nvme_id_ctrl id_ctrl; char *subsys_name; /* borrowed from subsys_map */ }; static const char *ctrl_map_entry_keyof(const struct ctrl_map_entry *entry) { return entry->ctrl_name; } static size_t ctrl_map_entry_hash(const char *ctrl_name) { return hash_string(ctrl_name); } static bool ctrl_map_entry_eq(const struct ctrl_map_entry *entry, const char *ctrl_name) { return strcmp(entry->ctrl_name, ctrl_name) == 0; } HTABLE_DEFINE_TYPE(struct ctrl_map_entry, ctrl_map_entry_keyof, ctrl_map_entry_hash, ctrl_map_entry_eq, ctrl_map); /* * Maps a subsystem NQN to its subsystem name. The subnqn is a * fixed-length, space-padded buffer (not necessarily NUL-terminated), * so it is hashed and compared over its full NVME_NQN_LENGTH bytes. */ struct subsys_entry { char subnqn[NVME_NQN_LENGTH]; char *subsys_name; }; static const char *subsys_entry_keyof(const struct subsys_entry *entry) { return entry->subnqn; } static size_t subsys_entry_hash(const char *subnqn) { return hash(subnqn, NVME_NQN_LENGTH, 0); } static bool subsys_entry_eq(const struct subsys_entry *entry, const char *subnqn) { return memcmp(entry->subnqn, subnqn, NVME_NQN_LENGTH) == 0; } HTABLE_DEFINE_TYPE(struct subsys_entry, subsys_entry_keyof, subsys_entry_hash, subsys_entry_eq, subsys_map); static struct ctrl_map ctrl_map; static bool ctrl_map_initialized = false; static struct subsys_map subsys_map; static bool htables_ready = false; /* ccan hash tables have been initialized */ /* * Compare two controller names numerically. Names share a common * textual prefix ("nvme") followed by an integer index, so a plain * strcmp() would order "nvme10" before "nvme2". Walk both strings * together and, whenever a run of digits begins in both, compare the * runs by numeric value; otherwise fall back to byte comparison. */ static int ctrl_name_cmp(const void *a, const void *b) { const char *const *ka = a; const char *const *kb = b; const char *sa = *ka; const char *sb = *kb; while (*sa && *sb) { if (isdigit((unsigned char)*sa) && isdigit((unsigned char)*sb)) { char *enda, *endb; unsigned long va = strtoul(sa, &enda, 10); unsigned long vb = strtoul(sb, &endb, 10); if (va != vb) return va < vb ? -1 : 1; sa = enda; sb = endb; continue; } if (*sa != *sb) return (unsigned char)*sa - (unsigned char)*sb; sa++; sb++; } return (unsigned char)*sa - (unsigned char)*sb; } const char **libnvme_ctrl_map_get_ctrl_names(size_t *count) { struct ctrl_map_iter iter; struct ctrl_map_entry *entry; const char **keys; size_t n = ctrl_map_count(&ctrl_map); size_t i = 0; if (count) *count = 0; if (!n) return NULL; keys = calloc(n + 1, sizeof(*keys)); if (!keys) return NULL; for (entry = ctrl_map_first(&ctrl_map, &iter); entry; entry = ctrl_map_next(&ctrl_map, &iter)) { if (i >= n) break; keys[i++] = ctrl_map_entry_keyof(entry); } qsort(keys, i, sizeof(*keys), ctrl_name_cmp); keys[i] = NULL; if (count) *count = i; return keys; } void libnvme_ctrl_map_clear(void) { struct ctrl_map_entry *entry; struct ctrl_map_iter ctrl_iter; struct subsys_entry *subsys; struct subsys_map_iter subsys_iter; if (!htables_ready) return; for (entry = ctrl_map_first(&ctrl_map, &ctrl_iter); entry; entry = ctrl_map_next(&ctrl_map, &ctrl_iter)) { free(entry->ctrl_name); free(entry->ctrl_path); /* subsys_name is owned by subsys_map, freed below */ free(entry); } ctrl_map_clear(&ctrl_map); for (subsys = subsys_map_first(&subsys_map, &subsys_iter); subsys; subsys = subsys_map_next(&subsys_map, &subsys_iter)) { free(subsys->subsys_name); free(subsys); } subsys_map_clear(&subsys_map); ctrl_map_initialized = false; htables_ready = false; } /* * Resolve the subsystem name for a controller's subnqn. Looks the * subnqn up in subsys_map; if present, returns the cached name. * Otherwise generates the next "nvme-subsysN" name, inserts it, and * returns it. The returned string is owned by subsys_map (borrowed by * the caller). Returns NULL on allocation failure. */ static char *find_or_create_subsys_name(const struct nvme_id_ctrl *id_ctrl) { struct subsys_entry *entry; char *subsys_name; int next_subsys_index; entry = subsys_map_get(&subsys_map, id_ctrl->subnqn); if (entry) return entry->subsys_name; next_subsys_index = subsys_map_count(&subsys_map); if (asprintf(&subsys_name, "nvme-subsys%d", next_subsys_index) < 0) return NULL; entry = calloc(1, sizeof(*entry)); if (!entry) { free(subsys_name); return NULL; } memcpy(entry->subnqn, id_ctrl->subnqn, NVME_NQN_LENGTH); entry->subsys_name = subsys_name; /* Key is derived from entry->subnqn via subsys_entry_keyof() */ if (!subsys_map_add(&subsys_map, entry)) { free(subsys_name); free(entry); return NULL; } return subsys_name; } static int libnvme_ctrl_map_add(DWORD ctrl_index, const WCHAR *ctrl_path, const struct nvme_id_ctrl *id_ctrl) { struct ctrl_map_entry *entry; char *ctrl_name_copy; WCHAR *ctrl_path_copy; char *subsys_name; if (!ctrl_path) return -EINVAL; if (asprintf(&ctrl_name_copy, "nvme%lu", ctrl_index) < 0) return -ENOMEM; ctrl_path_copy = _wcsdup(ctrl_path); if (!ctrl_path_copy) { free(ctrl_name_copy); return -ENOMEM; } subsys_name = find_or_create_subsys_name(id_ctrl); if (!subsys_name) { free(ctrl_name_copy); free(ctrl_path_copy); return -ENOMEM; } entry = calloc(1, sizeof(*entry)); if (!entry) { free(ctrl_name_copy); free(ctrl_path_copy); return -ENOMEM; } entry->ctrl_name = ctrl_name_copy; entry->ctrl_path = ctrl_path_copy; entry->id_ctrl = *id_ctrl; entry->subsys_name = subsys_name; /* Key is derived from entry->ctrl_name via ctrl_map_entry_keyof() */ if (!ctrl_map_add(&ctrl_map, entry)) { free(ctrl_name_copy); free(ctrl_path_copy); free(entry); return -ENOMEM; } return 0; } /* * Extract the "nvmeX" controller portion from a controller or namespace * name (e.g. "nvme0" or "nvme0n1"), where X is one or more digits, and * return it as a newly allocated string. Returns NULL if the name does * not begin with "nvme" followed by a digit, or on allocation failure. * Any trailing characters after the controller index are ignored; the * caller must free the result. */ static char *ctrl_name_extract(const char *ctrl_name) { const char *p = ctrl_name; const char *ctrl_end; char *ctrl; size_t len; if (!ctrl_name) return NULL; /* ctrl_name must start with "nvme" */ if (strncmp(p, "nvme", 4)) return NULL; p += 4; /* X: one or more digits */ if (!isdigit((unsigned char)*p)) return NULL; while (isdigit((unsigned char)*p)) p++; ctrl_end = p; len = ctrl_end - ctrl_name; ctrl = malloc(len + 1); if (!ctrl) return NULL; memcpy(ctrl, ctrl_name, len); ctrl[len] = '\0'; return ctrl; } /* * Issue an Identify Controller command on the given handle. * Creates a temporary transport handle internally using the caller's * global context. * Returns 0 on success with id_ctrl filled in, negative errno on failure. */ static int identify_ctrl_from_handle(struct libnvme_global_ctx *ctx, libnvme_fd_t h, struct nvme_id_ctrl *id_ctrl) { struct libnvme_transport_handle *hdl; struct libnvme_passthru_cmd cmd; int ret; hdl = __libnvme_create_transport_handle(ctx); if (!hdl) return -ENOMEM; hdl->fd = h; hdl->type = LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT; memset(id_ctrl, 0, sizeof(*id_ctrl)); nvme_init_identify_ctrl(&cmd, id_ctrl); ret = libnvme_exec_admin_passthru(hdl, &cmd); /* * Detach the handle before freeing the transport * handle so the caller retains ownership of the handle. */ hdl->fd = INVALID_HANDLE_VALUE; free(hdl); return ret; } /* * Query adapter BusType via IOCTL_STORAGE_QUERY_PROPERTY / * StorageAdapterProperty. * Returns 0 on success (bus type written to *out_bus_type). */ static int get_adapter_bus_type(libnvme_fd_t h, STORAGE_BUS_TYPE *out_bus_type) { STORAGE_PROPERTY_QUERY q = { 0 }; STORAGE_DESCRIPTOR_HEADER hdr; STORAGE_ADAPTER_DESCRIPTOR *ad; BYTE *buf; DWORD bytes = 0; q.PropertyId = StorageAdapterProperty; q.QueryType = PropertyStandardQuery; /* First query to get the required buffer size */ if (!DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &q, sizeof(q), &hdr, sizeof(hdr), &bytes, NULL)) return -1; buf = calloc(1, hdr.Size); if (!buf) return -ENOMEM; if (!DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &q, sizeof(q), buf, hdr.Size, &bytes, NULL)) { free(buf); return -1; } ad = (STORAGE_ADAPTER_DESCRIPTOR *)buf; *out_bus_type = (STORAGE_BUS_TYPE)ad->BusType; free(buf); return 0; } /* * Enumerate the next device interface and return a wide-string * copy of the device path. Returns: * 0 success (*device_interface_path set, caller must free) * -1 non-fatal failure (skip this interface) * -ENOMEM allocation failure * -ENOENT no more items (enumeration complete) */ static int get_device_interface_path(HDEVINFO hdev, DWORD index, WCHAR **device_interface_path, SP_DEVINFO_DATA *devinfo_data_out) { SP_DEVICE_INTERFACE_DATA if_data = { .cbSize = sizeof(if_data), }; DWORD required_size = 0; PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail; SP_DEVINFO_DATA dev_info_data = { .cbSize = sizeof(SP_DEVINFO_DATA), }; if (!SetupDiEnumDeviceInterfaces(hdev, NULL, &GUID_DEVINTERFACE_STORAGEPORT, index, &if_data)) { if (GetLastError() == ERROR_NO_MORE_ITEMS) return -ENOENT; return -1; } SetupDiGetDeviceInterfaceDetailW(hdev, &if_data, NULL, 0, &required_size, NULL); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || !required_size) return -1; detail = calloc(1, required_size); if (!detail) return -ENOMEM; detail->cbSize = sizeof(*detail); if (!SetupDiGetDeviceInterfaceDetailW(hdev, &if_data, detail, required_size, NULL, &dev_info_data)) { free(detail); return -1; } *device_interface_path = _wcsdup(detail->DevicePath); free(detail); if (!*device_interface_path) return -ENOMEM; if (devinfo_data_out) *devinfo_data_out = dev_info_data; return 0; } int libnvme_ctrl_map_init(struct libnvme_global_ctx *ctx) { HDEVINFO hdev; PWSTR ctrl_path = NULL; HANDLE h = INVALID_HANDLE_VALUE; int ret; DWORD index; DWORD ctrl_index = 0; STORAGE_BUS_TYPE bus_type; if (ctrl_map_initialized) return 0; ctrl_map_init(&ctrl_map); subsys_map_init(&subsys_map); htables_ready = true; hdev = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_STORAGEPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hdev == INVALID_HANDLE_VALUE) return -ENODEV; for (index = 0;; index++) { struct nvme_id_ctrl id_ctrl; h = INVALID_HANDLE_VALUE; ctrl_path = NULL; ret = get_device_interface_path(hdev, index, &ctrl_path, NULL); if (ret == -ENOENT) break; if (ret == -ENOMEM) goto enomem; if (ret == -1) continue; h = CreateFileW(ctrl_path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL); if (h == INVALID_HANDLE_VALUE) goto next_entry; if (get_adapter_bus_type(h, &bus_type) || bus_type != BusTypeNvme) goto next_entry; ret = identify_ctrl_from_handle(ctx, h, &id_ctrl); if (ret) goto next_entry; ret = libnvme_ctrl_map_add(ctrl_index, ctrl_path, &id_ctrl); if (ret < 0) goto enomem; ctrl_index++; next_entry: free(ctrl_path); ctrl_path = NULL; if (h != INVALID_HANDLE_VALUE) CloseHandle(h); } SetupDiDestroyDeviceInfoList(hdev); ctrl_map_initialized = true; return 0; enomem: free(ctrl_path); if (h != INVALID_HANDLE_VALUE) CloseHandle(h); SetupDiDestroyDeviceInfoList(hdev); libnvme_ctrl_map_clear(); return -ENOMEM; } struct ctrl_map_entry *libnvme_ctrl_map_lookup(struct libnvme_global_ctx *ctx, const char *ctrl_name) { __cleanup_free char *ctrl_name_copy = NULL; if (!ctrl_name) return NULL; ctrl_name_copy = ctrl_name_extract(ctrl_name); if (!ctrl_name_copy) return NULL; if (libnvme_ctrl_map_init(ctx)) return NULL; return ctrl_map_get(&ctrl_map, ctrl_name_copy); } const struct ctrl_map_entry * libnvme_ctrl_map_lookup_by_physdrive(struct libnvme_global_ctx *ctx, const char *drive_path) { DWORD target_num; char *endptr; const char *num_str; struct ctrl_map_entry *entry; struct ctrl_map_iter iter; if (!drive_path) return NULL; /* * Accept \\.\PhysicalDriveX format and extract the * device number X from the path. */ num_str = drive_path; if (strncmp(num_str, "\\\\.\\PhysicalDrive", 17) == 0) num_str += 17; else if (strncmp(num_str, "PhysicalDrive", 13) == 0) num_str += 13; target_num = strtoul(num_str, &endptr, 10); if (endptr == num_str || *endptr != '\0') return NULL; if (libnvme_ctrl_map_init(ctx)) return NULL; for (entry = ctrl_map_first(&ctrl_map, &iter); entry; entry = ctrl_map_next(&ctrl_map, &iter)) { DWORD *device_numbers = NULL; int dev_count = 0; int ret; int j; ret = libnvme_ctrl_map_entry_scan_device_numbers( entry, &device_numbers, &dev_count); if (ret) continue; for (j = 0; j < dev_count; j++) { if (device_numbers[j] == target_num) { free(device_numbers); return entry; } } free(device_numbers); } return NULL; } int libnvme_ctrl_map_entry_set_id_ctrl(struct ctrl_map_entry *entry, const struct nvme_id_ctrl *id) { if (!entry || !id) return -EINVAL; entry->id_ctrl = *id; return 0; } const char *libnvme_ctrl_map_entry_get_ctrl_name( const struct ctrl_map_entry *entry) { if (!entry || !entry->ctrl_name) return NULL; return entry->ctrl_name; } int libnvme_ctrl_map_entry_get_ctrl_path(const struct ctrl_map_entry *entry, char **ctrl_path) { int path_len; char *ctrl_path_copy; if (!entry || !ctrl_path || !entry->ctrl_path) return -EINVAL; *ctrl_path = NULL; path_len = WideCharToMultiByte(CP_UTF8, 0, entry->ctrl_path, -1, NULL, 0, NULL, NULL); if (path_len <= 0) return -EINVAL; ctrl_path_copy = malloc(path_len); if (!ctrl_path_copy) return -ENOMEM; if (!WideCharToMultiByte(CP_UTF8, 0, entry->ctrl_path, -1, ctrl_path_copy, path_len, NULL, NULL)) { free(ctrl_path_copy); return -EINVAL; } *ctrl_path = ctrl_path_copy; return 0; } int libnvme_ctrl_map_entry_get_pci_address(const struct ctrl_map_entry *entry, char **address) { WCHAR instance_id[MAX_DEVICE_ID_LEN]; WCHAR location_info[256]; DEVPROPTYPE prop_type = 0; ULONG size; CONFIGRET cr; DEVINST devinst; unsigned int bus = 0, device = 0, function = 0; char *addr; if (!entry || !address || !entry->ctrl_path) return -EINVAL; *address = NULL; /* Get the device instance ID from the interface path */ size = sizeof(instance_id); cr = CM_Get_Device_Interface_PropertyW( entry->ctrl_path, &DEVPKEY_Device_InstanceId, &prop_type, (PBYTE)instance_id, &size, 0); if (cr != CR_SUCCESS || prop_type != DEVPROP_TYPE_STRING) return -ENODEV; cr = CM_Locate_DevNodeW(&devinst, instance_id, CM_LOCATE_DEVNODE_NORMAL); if (cr != CR_SUCCESS) return -ENODEV; /* * Query DEVPKEY_Device_LocationInfo which returns a string like * "PCI bus 2, device 0, function 0" */ size = sizeof(location_info); prop_type = 0; cr = CM_Get_DevNode_PropertyW( devinst, &DEVPKEY_Device_LocationInfo, &prop_type, (PBYTE)location_info, &size, 0); if (cr != CR_SUCCESS || prop_type != DEVPROP_TYPE_STRING) return -ENODEV; if (swscanf(location_info, L"PCI bus %u, device %u, function %u", &bus, &device, &function) != 3) return -EINVAL; /* Format as Linux-compatible BDF: DOMAIN:BUS:DEVICE.FUNCTION */ if (asprintf(&addr, "0000:%02x:%02x.%x", bus, device, function) < 0) return -ENOMEM; *address = addr; return 0; } static int get_device_number(libnvme_fd_t h, DWORD *device_number) { STORAGE_DEVICE_NUMBER info = { 0 }; DWORD bytes_returned = 0; if (!device_number) return -EINVAL; *device_number = 0; if (h == INVALID_HANDLE_VALUE) return -EINVAL; if (!DeviceIoControl(h, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &info, sizeof(info), &bytes_returned, NULL)) return -1; *device_number = info.DeviceNumber; return 0; } int libnvme_ctrl_map_entry_scan_device_numbers( const struct ctrl_map_entry *entry, DWORD **device_numbers, int *count) { WCHAR ctrl_instance_id[MAX_DEVICE_ID_LEN]; DEVPROPTYPE prop_type = 0; ULONG id_size = sizeof(ctrl_instance_id); CONFIGRET cr; DEVINST ctrl_devinst, child; DWORD *nums = NULL; int capacity = 0; int num_count = 0; if (!entry || !device_numbers || !count) return -EINVAL; *device_numbers = NULL; *count = 0; cr = CM_Get_Device_Interface_PropertyW( entry->ctrl_path, &DEVPKEY_Device_InstanceId, &prop_type, (PBYTE)ctrl_instance_id, &id_size, 0); if (cr != CR_SUCCESS || prop_type != DEVPROP_TYPE_STRING) return -ENODEV; cr = CM_Locate_DevNodeW(&ctrl_devinst, ctrl_instance_id, CM_LOCATE_DEVNODE_NORMAL); if (cr != CR_SUCCESS) return -ENODEV; cr = CM_Get_Child(&child, ctrl_devinst, 0); while (cr == CR_SUCCESS) { WCHAR child_id[MAX_DEVICE_ID_LEN]; ULONG child_id_size = sizeof(child_id); DEVPROPTYPE child_prop_type = 0; ULONG list_size = 0; WCHAR *iface_list = NULL; CONFIGRET cr2; DEVINST sibling; cr2 = CM_Get_DevNode_PropertyW( child, &DEVPKEY_Device_InstanceId, &child_prop_type, (PBYTE)child_id, &child_id_size, 0); if (cr2 != CR_SUCCESS) goto next_child; cr2 = CM_Get_Device_Interface_List_SizeW( &list_size, (LPGUID)&GUID_DEVINTERFACE_DISK, child_id, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); if (cr2 != CR_SUCCESS || list_size <= 1) goto next_child; iface_list = malloc(list_size * sizeof(WCHAR)); if (!iface_list) goto enomem; cr2 = CM_Get_Device_Interface_ListW( (LPGUID)&GUID_DEVINTERFACE_DISK, child_id, iface_list, list_size, CM_GET_DEVICE_INTERFACE_LIST_PRESENT); if (cr2 != CR_SUCCESS) goto next_child; for (WCHAR *iface = iface_list; *iface; iface += wcslen(iface) + 1) { DWORD device_number = 0; HANDLE h; h = CreateFileW(iface, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (get_device_number(h, &device_number)) { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); continue; } CloseHandle(h); if (num_count == capacity) { int new_capacity = capacity ? capacity * 2 : 8; DWORD *new_nums; new_nums = realloc( nums, (size_t)new_capacity * sizeof(*nums)); if (!new_nums) { free(iface_list); goto enomem; } nums = new_nums; capacity = new_capacity; } nums[num_count++] = device_number; } next_child: free(iface_list); cr = CM_Get_Sibling(&sibling, child, 0); child = sibling; } *device_numbers = nums; *count = num_count; return 0; enomem: free(nums); return -ENOMEM; } int libnvme_ctrl_map_entry_map_nsid_to_drive_path( const struct ctrl_map_entry *entry, __u32 nsid, char **drive_path) { DWORD *device_numbers = NULL; int dev_count = 0; int ret; int i; if (!entry || !drive_path || nsid == 0) return -EINVAL; *drive_path = NULL; ret = libnvme_ctrl_map_entry_scan_device_numbers(entry, &device_numbers, &dev_count); if (ret) return ret; for (i = 0; i < dev_count; i++) { char path[MAX_PATH]; SCSI_ADDRESS addr = { 0 }; DWORD bytes = 0; HANDLE h; snprintf(path, sizeof(path), "\\\\.\\PhysicalDrive%lu", device_numbers[i]); h = CreateFileA(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (h == INVALID_HANDLE_VALUE) continue; addr.Length = sizeof(addr); if (!DeviceIoControl(h, IOCTL_SCSI_GET_ADDRESS, NULL, 0, &addr, sizeof(addr), &bytes, NULL)) { CloseHandle(h); continue; } CloseHandle(h); if ((__u32)(addr.Lun + 1) == nsid) { *drive_path = strdup(path); free(device_numbers); if (!*drive_path) return -ENOMEM; return 0; } } free(device_numbers); return -ENODEV; } char *libnvme_ctrl_map_entry_get_subsys_name( const struct ctrl_map_entry *entry) { char *subsysname; if (!entry || !entry->subsys_name) return NULL; subsysname = strdup(entry->subsys_name); if (!subsysname) return NULL; return subsysname; } static char *copy_and_rtrim(const char *src, size_t src_size) { char *dst; size_t len = src_size; if (!src) return NULL; while (len > 0 && isspace((unsigned char)src[len - 1])) len--; dst = malloc(len + 1); if (!dst) return NULL; memcpy(dst, src, len); dst[len] = '\0'; return dst; } char *libnvme_ctrl_map_entry_get_subnqn(const struct ctrl_map_entry *entry) { if (!entry) return NULL; return copy_and_rtrim(entry->id_ctrl.subnqn, sizeof(entry->id_ctrl.subnqn)); } char *libnvme_ctrl_map_entry_get_serial(const struct ctrl_map_entry *entry) { if (!entry) return NULL; return copy_and_rtrim(entry->id_ctrl.sn, sizeof(entry->id_ctrl.sn)); } char *libnvme_ctrl_map_entry_get_model(const struct ctrl_map_entry *entry) { if (!entry) return NULL; return copy_and_rtrim(entry->id_ctrl.mn, sizeof(entry->id_ctrl.mn)); } char *libnvme_ctrl_map_entry_get_firmware(const struct ctrl_map_entry *entry) { if (!entry) return NULL; return copy_and_rtrim(entry->id_ctrl.fr, sizeof(entry->id_ctrl.fr)); } HDEVINFO libnvme_ctrl_map_entry_get_devinfo( const struct ctrl_map_entry *entry, SP_DEVINFO_DATA *dev_info_data) { HDEVINFO hdev; DWORD index; PWSTR ctrl_path = NULL; if (!dev_info_data || !entry || !entry->ctrl_path) return INVALID_HANDLE_VALUE; dev_info_data->cbSize = sizeof(*dev_info_data); hdev = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_STORAGEPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hdev == INVALID_HANDLE_VALUE) return INVALID_HANDLE_VALUE; for (index = 0;; index++) { get_device_interface_path(hdev, index, &ctrl_path, dev_info_data); if (_wcsicmp(ctrl_path, entry->ctrl_path) == 0) { free(ctrl_path); return hdev; } free(ctrl_path); ctrl_path = NULL; } SetupDiDestroyDeviceInfoList(hdev); return INVALID_HANDLE_VALUE; } void libnvme_ctrl_map_entry_free_devinfo(HDEVINFO hdev) { if (hdev != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList(hdev); }