diff --git a/libnvme/src/meson.build b/libnvme/src/meson.build index 3e0eb3e226..a11adcd4ef 100644 --- a/libnvme/src/meson.build +++ b/libnvme/src/meson.build @@ -18,6 +18,7 @@ sources = [ ] if host_system == 'windows' sources += [ + 'nvme/ctrl-map.c', 'nvme/filters-win.c', 'nvme/ioctl-win.c', 'nvme/lib-win.c', @@ -125,6 +126,8 @@ if host_system == 'windows' deps += [ kernel32_dep, bcrypt_dep, + setupapi_dep, + cfgmgr32_dep, ] endif diff --git a/libnvme/src/nvme/ctrl-map.c b/libnvme/src/nvme/ctrl-map.c new file mode 100644 index 0000000000..51c81e82f6 --- /dev/null +++ b/libnvme/src/nvme/ctrl-map.c @@ -0,0 +1,839 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * This file is part of libnvme. + * Copyright (c) 2026 Micron Technology, Inc. + * + * Authors: Brandon Capener + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "private.h" +#include "private-ctrl-map.h" +#include "compiler-attributes.h" + +#include +#include + +struct ctrl_map_entry { + char *ctrl_name; /* format "nvmeX" */ + WCHAR *ctrl_path; /* format "\\\\?\\pci..." */ + struct nvme_id_ctrl id_ctrl; + int subsys_index; + char *subsys_name; +}; + +static struct { + struct ctrl_map_entry *entries; + size_t count; + size_t capacity; + bool initialized; +} ctrl_map; + +size_t libnvme_ctrl_map_get_count(void) +{ + return ctrl_map.count; +} + +const char *libnvme_ctrl_map_get_name(size_t index) +{ + if (index >= ctrl_map.count) + return NULL; + return ctrl_map.entries[index].ctrl_name; +} + +void libnvme_ctrl_map_clear(void) +{ + size_t i; + + for (i = 0; i < ctrl_map.count; i++) { + free(ctrl_map.entries[i].ctrl_name); + free(ctrl_map.entries[i].ctrl_path); + free(ctrl_map.entries[i].subsys_name); + } + + free(ctrl_map.entries); + ctrl_map.entries = NULL; + ctrl_map.count = 0; + ctrl_map.capacity = 0; + ctrl_map.initialized = false; +} + +static int find_or_create_subsys_index(const struct nvme_id_ctrl *id_ctrl) +{ + int max_subsys_index = -1; + size_t i; + + for (i = 0; i < ctrl_map.count; i++) { + if (!memcmp(ctrl_map.entries[i].id_ctrl.subnqn, + id_ctrl->subnqn, NVME_NQN_LENGTH)) + return ctrl_map.entries[i].subsys_index; + if (ctrl_map.entries[i].subsys_index > max_subsys_index) + max_subsys_index = ctrl_map.entries[i].subsys_index; + } + + return max_subsys_index + 1; +} + +static int libnvme_ctrl_map_add(const char *ctrl_name, + const WCHAR *ctrl_path, + const struct nvme_id_ctrl *id_ctrl) +{ + char *ctrl_name_copy; + WCHAR *ctrl_path_copy; + int subsys_index; + char *subsys_name; + + if (!ctrl_name || !ctrl_path) + return -EINVAL; + + if (ctrl_map.count == ctrl_map.capacity) { + size_t new_capacity = ctrl_map.capacity ? + ctrl_map.capacity * 2 : 8; + struct ctrl_map_entry *new_map; + + new_map = realloc(ctrl_map.entries, + new_capacity * sizeof(*ctrl_map.entries)); + if (!new_map) + return -ENOMEM; + + ctrl_map.entries = new_map; + ctrl_map.capacity = new_capacity; + } + + ctrl_name_copy = strdup(ctrl_name); + if (!ctrl_name_copy) + return -ENOMEM; + + ctrl_path_copy = _wcsdup(ctrl_path); + if (!ctrl_path_copy) { + free(ctrl_name_copy); + return -ENOMEM; + } + + subsys_index = find_or_create_subsys_index(id_ctrl); + + if (asprintf(&subsys_name, "nvme-subsys%d", subsys_index) < 0) { + free(ctrl_name_copy); + free(ctrl_path_copy); + return -ENOMEM; + } + + ctrl_map.entries[ctrl_map.count].ctrl_name = ctrl_name_copy; + ctrl_map.entries[ctrl_map.count].ctrl_path = ctrl_path_copy; + ctrl_map.entries[ctrl_map.count].id_ctrl = *id_ctrl; + ctrl_map.entries[ctrl_map.count].subsys_index = subsys_index; + ctrl_map.entries[ctrl_map.count].subsys_name = subsys_name; + ctrl_map.count++; + + return 0; +} + +static int ctrl_map_find_index(const char *ctrl_name) +{ + size_t i; + + for (i = 0; i < ctrl_map.count; i++) { + size_t sp_len = strlen(ctrl_map.entries[i].ctrl_name); + const char *suffix; + + /* Skip entry if name does not start with nvmeX format */ + if (strncmp(ctrl_map.entries[i].ctrl_name, + ctrl_name, sp_len)) + continue; + + suffix = ctrl_name + sp_len; + + /* Exact match: nvmeX */ + if (*suffix == '\0') + return (int)i; + + /* Namespace match: nvmeXnY (Y is a positive integer) */ + if (*suffix != 'n' || suffix[1] < '1' || suffix[1] > '9') + continue; + + suffix += 2; + while (*suffix >= '0' && *suffix <= '9') + suffix++; + if (*suffix == '\0') + return (int)i; + } + + return -1; +} + +/* + * Issue an Identify Controller command on the given handle. + * Creates a temporary transport context and handle internally. + * Returns 0 on success with id_ctrl filled in, negative errno on failure. + */ +static int identify_ctrl_from_handle(libnvme_fd_t h, struct nvme_id_ctrl *id_ctrl) +{ + struct libnvme_global_ctx *ctx; + struct libnvme_transport_handle *hdl; + struct libnvme_passthru_cmd cmd; + int ret; + + ctx = libnvme_create_global_ctx(NULL, LIBNVME_DEFAULT_LOGLEVEL); + if (!ctx) + return -ENOMEM; + + hdl = __libnvme_create_transport_handle(ctx); + if (!hdl) { + libnvme_free_global_ctx(ctx); + 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_submit_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); + libnvme_free_global_ctx(ctx); + + 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_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; + + return 0; +} + +int libnvme_ctrl_map_init(void) +{ + HDEVINFO hdev; + PWSTR ctrl_path = NULL; + HANDLE h = INVALID_HANDLE_VALUE; + int ret; + DWORD index; + DWORD nvme_ctrl_index = 0; + STORAGE_BUS_TYPE bus_type; + + if (ctrl_map.initialized) + return 0; + + 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; + char *ctrl_name = NULL; + + h = INVALID_HANDLE_VALUE; + ctrl_path = NULL; + + ret = get_device_interface_path(hdev, index, &ctrl_path); + 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(h, &id_ctrl); + if (ret) + goto next_entry; + + if (asprintf(&ctrl_name, "nvme%lu", nvme_ctrl_index) < 0) + goto enomem; + + ret = libnvme_ctrl_map_add(ctrl_name, ctrl_path, &id_ctrl); + free(ctrl_name); + ctrl_name = NULL; + if (ret < 0) + goto enomem; + nvme_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(const char *ctrl_name) +{ + int idx; + + if (!ctrl_name) + return NULL; + + if (libnvme_ctrl_map_init()) + return NULL; + + idx = ctrl_map_find_index(ctrl_name); + if (idx < 0) + return NULL; + + return &ctrl_map.entries[idx]; +} + +const struct ctrl_map_entry * +libnvme_ctrl_map_lookup_by_physdrive(const char *drive_path) +{ + DWORD target_num; + char *endptr; + const char *num_str; + size_t i; + + 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()) + return NULL; + + for (i = 0; i < ctrl_map.count; i++) { + DWORD *device_numbers = NULL; + int dev_count = 0; + int ret; + int j; + + ret = libnvme_ctrl_map_entry_scan_device_numbers( + &ctrl_map.entries[i], + &device_numbers, &dev_count); + if (ret) + continue; + + for (j = 0; j < dev_count; j++) { + if (device_numbers[j] == target_num) { + free(device_numbers); + return &ctrl_map.entries[i]; + } + } + + 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)); +} diff --git a/libnvme/src/nvme/filters-win.c b/libnvme/src/nvme/filters-win.c index a5e0591438..64b357794d 100644 --- a/libnvme/src/nvme/filters-win.c +++ b/libnvme/src/nvme/filters-win.c @@ -12,6 +12,7 @@ #include #include "private.h" +#include "private-ctrl-map.h" #include "compiler-attributes.h" __libnvme_public int libnvme_scan_subsystems( @@ -29,7 +30,48 @@ __libnvme_public int libnvme_scan_subsystem_namespaces( __libnvme_public int libnvme_scan_ctrls(struct dirent ***ctrls) { - return -ENOTSUP; + struct dirent **entries; + size_t i, count; + int ret; + + if (!ctrls) + return -EINVAL; + + *ctrls = NULL; + + libnvme_ctrl_map_clear(); + ret = libnvme_ctrl_map_init(); + if (ret) + return ret; + + count = libnvme_ctrl_map_get_count(); + if (!count) + return 0; + + entries = calloc(count, sizeof(*entries)); + if (!entries) + goto enomem; + + for (i = 0; i < count; i++) { + entries[i] = calloc(1, sizeof(*entries[i])); + if (!entries[i]) + goto enomem; + snprintf(entries[i]->d_name, + sizeof(entries[i]->d_name), "%s", + libnvme_ctrl_map_get_name(i)); + } + + *ctrls = entries; + return (int)count; + +enomem: + libnvme_ctrl_map_clear(); + if (entries) { + while (i > 0) + free(entries[--i]); + free(entries); + } + return -ENOMEM; } __libnvme_public int libnvme_scan_ctrl_namespace_paths( @@ -42,7 +84,58 @@ __libnvme_public int libnvme_scan_ctrl_namespace_paths( __libnvme_public int libnvme_scan_ctrl_namespaces(libnvme_ctrl_t c, struct dirent ***ns) { - return -ENOTSUP; + struct dirent **entries = NULL; + const struct ctrl_map_entry *ctrl_entry; + DWORD *device_numbers = NULL; + int dev_count = 0; + int ret; + int i; + + if (!c || !ns) + return -EINVAL; + + *ns = NULL; + + ctrl_entry = libnvme_ctrl_map_lookup(c->name); + if (!ctrl_entry) + return 0; + + ret = libnvme_ctrl_map_entry_scan_device_numbers(ctrl_entry, + &device_numbers, + &dev_count); + if (ret) + return ret; + + if (!dev_count) + return 0; + + entries = calloc(dev_count, sizeof(*entries)); + if (!entries) { + free(device_numbers); + return -ENOMEM; + } + + for (i = 0; i < dev_count; i++) { + entries[i] = calloc(1, sizeof(*entries[i])); + if (!entries[i]) + goto enomem; + + snprintf(entries[i]->d_name, + sizeof(entries[i]->d_name), + "\\\\.\\PhysicalDrive%lu", + device_numbers[i]); + } + + free(device_numbers); + *ns = entries; + return dev_count; + +enomem: + while (i > 0) + free(entries[--i]); + free(entries); + free(device_numbers); + return -ENOMEM; } __libnvme_public int libnvme_scan_ns_head_paths( diff --git a/libnvme/src/nvme/lib-win.c b/libnvme/src/nvme/lib-win.c index fb9e9b372a..01c1bba601 100644 --- a/libnvme/src/nvme/lib-win.c +++ b/libnvme/src/nvme/lib-win.c @@ -12,7 +12,12 @@ #include "cleanup.h" #include "compiler-attributes.h" +#include "cleanup.h" +#include "ioctl.h" +#include "lib.h" #include "private.h" +#include "private-ctrl-map.h" +#include "util.h" static bool __is_controller_path(const char *device_path) { @@ -92,9 +97,113 @@ __libnvme_public int libnvme_open(struct libnvme_global_ctx *ctx, const char *name, struct libnvme_transport_handle **hdlp) { - return -ENOTSUP; + struct libnvme_transport_handle *hdl; + __cleanup_free char *mapped_name = NULL; + int ret; + const struct ctrl_map_entry *ctrl_entry; + + if (strstr(name, "/dev/")) + name = libnvme_basename(name); + + ctrl_entry = libnvme_ctrl_map_lookup(name); + if (ctrl_entry) { + const char *n_pos = strchr(name + 4, 'n'); + __u32 nsid; + + if (n_pos && sscanf(n_pos, "n%u", &nsid) == 1) { + ret = libnvme_ctrl_map_entry_map_nsid_to_drive_path( + ctrl_entry, nsid, &mapped_name); + } else { + ret = libnvme_ctrl_map_entry_get_ctrl_path( + ctrl_entry, &mapped_name); + } + if (ret) + return ret; + } + + hdl = __libnvme_create_transport_handle(ctx); + if (!hdl) + return -ENOMEM; + + /* Handle test devices */ + if (!strncmp(name, "NVME_TEST_FD", 12)) { + hdl->type = LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT; + hdl->fd = LIBNVME_TEST_FD; + + if (!strcmp(name, "NVME_TEST_FD64")) + hdl->ioctl_admin_state = IOCTL_STATE_IOCTL64; + + *hdlp = hdl; + return 0; + } + + /* MI transport not supported on Windows */ + if (!strncmp(name, "mctp:", strlen("mctp:"))) { + libnvme_close(hdl); + return -ENOTSUP; + } + + ret = __libnvme_transport_handle_open_direct(hdl, mapped_name ? + mapped_name : name); + + free(mapped_name); + mapped_name = NULL; + + if (ret) { + libnvme_close(hdl); + return ret; + } + + /* For PhysicalDrive names, create the nvmeXnY-style name. */ + ctrl_entry = libnvme_ctrl_map_lookup_by_physdrive(name); + if (ctrl_entry) { + __u32 nsid; + + if (libnvme_get_nsid(hdl, &nsid) == 0 && + asprintf(&mapped_name, "%sn%d", + libnvme_ctrl_map_entry_get_ctrl_name(ctrl_entry), + nsid) > 0) + name = mapped_name; + } + + /* Store the nvmeX or nvmeXnY-style name in hdl->name. */ + hdl->name = strdup(name); + if (!hdl->name) { + free(hdl); + return -ENOMEM; + } + + *hdlp = hdl; + + return 0; } __libnvme_public void libnvme_close(struct libnvme_transport_handle *hdl) { + bool is_test_fd; + + if (!hdl) + return; + + is_test_fd = hdl->type == LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT && + hdl->fd == LIBNVME_TEST_FD && + hdl->name && !strncmp(hdl->name, "NVME_TEST_FD", 12); + + free(hdl->name); + + switch (hdl->type) { + case LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT: + /* Close Windows HANDLE if valid */ + if (!is_test_fd && hdl->fd && hdl->fd != INVALID_HANDLE_VALUE) + CloseHandle(hdl->fd); + free(hdl); + break; + case LIBNVME_TRANSPORT_HANDLE_TYPE_MI: + /* MI not supported on Windows */ + free(hdl); + break; + case LIBNVME_TRANSPORT_HANDLE_TYPE_UNKNOWN: + free(hdl); + break; + } } diff --git a/libnvme/src/nvme/private-ctrl-map.h b/libnvme/src/nvme/private-ctrl-map.h new file mode 100644 index 0000000000..3d585408ed --- /dev/null +++ b/libnvme/src/nvme/private-ctrl-map.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libnvme. + * Copyright (c) 2026 Micron Technology, Inc. + * + * Authors: Brandon Capener + */ +#pragma once + +#if defined(_WIN32) + +#include "nvme-types.h" + +struct ctrl_map_entry; + +/** + * libnvme_ctrl_map_get_count() - Get number of controller map entries + * + * Return: Number of entries in the global controller map + */ +size_t libnvme_ctrl_map_get_count(void); + +/** + * libnvme_ctrl_map_get_name() - Get controller name by index + * @index: Zero-based index into the controller map + * + * Return: controller name string (e.g. "nvme0"), or NULL if index + * is out of range + */ +const char *libnvme_ctrl_map_get_name(size_t index); + +/** + * libnvme_ctrl_map_init() - Initialize the controller map + * + * Enumerates all NVMe controllers via SetupDI and populates the + * global controller map. Safe to call multiple times; returns + * immediately if the map is already populated. + * + * Return: 0 on success, negative errno on failure + */ +int libnvme_ctrl_map_init(void); + +/** + * libnvme_ctrl_map_clear() - Free all controller map entries + * + * Releases all memory associated with the global controller map + * and resets the count to zero. + */ +void libnvme_ctrl_map_clear(void); + +/** + * libnvme_ctrl_map_lookup() - Resolve controller name to map entry + * @ctrl_name: Controller name in nvmeX format + * + * Return: controller map entry on Windows, or NULL if unavailable + */ +struct ctrl_map_entry * +libnvme_ctrl_map_lookup(const char *ctrl_name); + +/** + * libnvme_ctrl_map_lookup_by_physdrive() - Resolve PhysicalDrive path to + * controller entry + * @drive_path: Device path in \\.\PhysicalDriveX format + * + * Scans all NVMe controllers and returns the controller map entry whose + * child disk set contains the given PhysicalDrive number. + * + * Return: controller map entry on match, or NULL if not found + */ +const struct ctrl_map_entry * +libnvme_ctrl_map_lookup_by_physdrive(const char *drive_path); + +/** + * libnvme_ctrl_map_entry_set_id_ctrl() - Set id_ctrl for a entry + * @entry: controller map entry + * @id: Pointer to the new identify controller data + * + * Return: 0 on success, -EINVAL for bad args + */ +int libnvme_ctrl_map_entry_set_id_ctrl( + struct ctrl_map_entry *entry, + const struct nvme_id_ctrl *id); + +/** + * libnvme_ctrl_map_entry_get_ctrl_name() - Get UTF-8 controller name for + * entry + * @entry: controller map entry + * + * Return: UTF-8 controller name string, or NULL if unavailable + */ +const char *libnvme_ctrl_map_entry_get_ctrl_name( + const struct ctrl_map_entry *entry); + +/** + * libnvme_ctrl_map_entry_get_ctrl_path() - Get UTF-8 device path for entry + * @entry: controller map entry + * @ctrl_path: Output UTF-8 path string, allocated on success + * + * Return: 0 on success, or a negative error code + */ +int libnvme_ctrl_map_entry_get_ctrl_path(const struct ctrl_map_entry *entry, + char **ctrl_path); + +/** + * libnvme_ctrl_map_entry_get_pci_address() - Get PCI BDF address for a + * controller + * @entry: controller map entry + * @address: Output BDF string in "DDDD:BB:DD.F" format, allocated on success + * + * Queries the Windows CM API for the PCI bus, device, and function numbers + * and formats them into a Linux-compatible BDF address string. + * + * Return: 0 on success, or a negative error code + */ +int libnvme_ctrl_map_entry_get_pci_address(const struct ctrl_map_entry *entry, + char **address); + +/** + * libnvme_ctrl_map_entry_scan_device_numbers() - Get device numbers for a + * controller + * @entry: controller map entry + * @device_numbers: Output array of device numbers, + * allocated on success (caller frees) + * @count: Output number of entries in @device_numbers + * + * Walks the child devnodes of the controller and enumerates their + * GUID_DEVINTERFACE_DISK interfaces to collect PhysicalDrive device numbers. + * + * Return: 0 on success, or a negative error code + */ +int libnvme_ctrl_map_entry_scan_device_numbers( + const struct ctrl_map_entry *entry, + DWORD **device_numbers, + int *count); + +/** + * libnvme_ctrl_map_entry_map_nsid_to_drive_path() - Map namespace ID to + * PhysicalDrive path + * @entry: controller map entry for the controller + * @nsid: NVMe namespace ID (1-based) + * @drive_path: Output path string in \\.\PhysicalDriveX format (caller frees) + * + * Scans the disks belonging to the given controller and returns the + * device path whose SCSI LUN corresponds to @nsid. + * + * Return: 0 on success, -ENODEV if no matching namespace, or a negative error + * code + */ +int libnvme_ctrl_map_entry_map_nsid_to_drive_path( + const struct ctrl_map_entry *entry, + __u32 nsid, + char **drive_path); + +/** + * libnvme_ctrl_map_entry_get_subsys_name() - Get UTF-8 subsystem name for + * entry + * @entry: controller map entry + * + * Return: UTF-8 subsystem name string, or NULL if unavailable + */ +char *libnvme_ctrl_map_entry_get_subsys_name( + const struct ctrl_map_entry *entry); + +/** + * libnvme_ctrl_map_entry_get_subnqn() - Get UTF-8 subsystem NQN for + * entry + * @entry: controller map entry + * + * Return: UTF-8 subsystem NQN string, or NULL if unavailable + */ +char *libnvme_ctrl_map_entry_get_subnqn(const struct ctrl_map_entry *entry); + +/** + * libnvme_ctrl_map_entry_get_serial() - Get UTF-8 serial number for entry + * @entry: controller map entry + * + * Return: UTF-8 serial number string, or NULL if unavailable + */ +char *libnvme_ctrl_map_entry_get_serial(const struct ctrl_map_entry *entry); + +/** + * libnvme_ctrl_map_entry_get_model() - Get UTF-8 model number for entry + * @entry: controller map entry + * + * Return: UTF-8 model number string, or NULL if unavailable + */ +char *libnvme_ctrl_map_entry_get_model(const struct ctrl_map_entry *entry); + +/** + * libnvme_ctrl_map_entry_get_firmware() - Get UTF-8 firmware revision for + * entry + * @entry: controller map entry + * + * Return: UTF-8 firmware revision string, or NULL if unavailable + */ +char *libnvme_ctrl_map_entry_get_firmware(const struct ctrl_map_entry *entry); + +#endif /* defined(_WIN32) */ diff --git a/libnvme/src/nvme/tree-win.c b/libnvme/src/nvme/tree-win.c index da5bfe0575..4c9937ee81 100644 --- a/libnvme/src/nvme/tree-win.c +++ b/libnvme/src/nvme/tree-win.c @@ -16,29 +16,141 @@ #include #include +#include +#include + #include #include "cleanup.h" #include "private.h" +#include "private-ctrl-map.h" #include "private-tree.h" +#include "util.h" #include "compiler-attributes.h" int libnvme_reconfigure_ctrl(struct libnvme_global_ctx *ctx, libnvme_ctrl_t c, const char *path, const char *name) { - return -ENOTSUP; + struct nvme_id_ctrl id_ctrl; + struct ctrl_map_entry *ctrl_entry; + int ret; + + /* + * It's necessary to release any resources first because a ctrl + * can be reused. + */ + libnvme_ctrl_release_transport_handle(c); + FREE_CTRL_ATTR(c->name); + FREE_CTRL_ATTR(c->sysfs_dir); + FREE_CTRL_ATTR(c->firmware); + FREE_CTRL_ATTR(c->model); + FREE_CTRL_ATTR(c->state); + FREE_CTRL_ATTR(c->numa_node); + FREE_CTRL_ATTR(c->queue_count); + FREE_CTRL_ATTR(c->serial); + FREE_CTRL_ATTR(c->sqsize); + FREE_CTRL_ATTR(c->cntrltype); + FREE_CTRL_ATTR(c->cntlid); + FREE_CTRL_ATTR(c->dctype); + FREE_CTRL_ATTR(c->phy_slot); + + c->hdl = NULL; + c->name = xstrdup(name); + c->sysfs_dir = xstrdup(path); + + if (!libnvme_ctrl_get_transport_handle(c)) + return -ENODEV; + + ret = libnvme_ctrl_identify(c, &id_ctrl); + if (ret != 0) { + libnvme_msg(ctx, LIBNVME_LOG_ERR, + "Failed to identify ctrl %s, error %d\n", + c->name, ret); + return -ENODEV; + } + + ctrl_entry = libnvme_ctrl_map_lookup(c->name); + if (!ctrl_entry) { + libnvme_msg(ctx, LIBNVME_LOG_ERR, + "Failed to find ctrl map entry for ctrl %s\n", + c->name); + return -ENODEV; + } + + ret = libnvme_ctrl_map_entry_set_id_ctrl(ctrl_entry, &id_ctrl); + if (ret != 0) { + libnvme_msg(ctx, LIBNVME_LOG_ERR, + "Failed to update ctrl map for ctrl %s, error %d\n", + c->name, ret); + return -ENODEV; + } + + c->firmware = libnvme_ctrl_map_entry_get_firmware(ctrl_entry); + if (!c->firmware) + return -ENOMEM; + + c->model = libnvme_ctrl_map_entry_get_model(ctrl_entry); + if (!c->model) + return -ENOMEM; + + c->serial = libnvme_ctrl_map_entry_get_serial(ctrl_entry); + if (!c->serial) + return -ENOMEM; + + if (asprintf(&c->cntrltype, "%u", id_ctrl.cntrltype) < 0) + return -errno; + + if (asprintf(&c->cntlid, "%u", le16_to_cpu(id_ctrl.cntlid)) < 0) + return -errno; + + if (asprintf(&c->dctype, "%u", id_ctrl.dctype) < 0) + return -errno; + + return 0; } __libnvme_public int libnvme_get_host(struct libnvme_global_ctx *ctx, const char *hostnqn, const char *hostid, libnvme_host_t *host) { - return -ENOTSUP; + __cleanup_free char *hnqn = NULL; + __cleanup_free char *hid = NULL; + struct libnvme_host *h; + + /* Use provided values or generate defaults */ + if (hostnqn) + hnqn = strdup(hostnqn); + else + hnqn = strdup("nqn.2014-08.org.nvmexpress:uuid:00000000-0000-0000-0000-000000000000"); + + if (!hnqn) + return -ENOMEM; + + if (hostid) + hid = strdup(hostid); + else + hid = strdup("00000000-0000-0000-0000-000000000000"); + + if (!hid) + return -ENOMEM; + + h = libnvme_lookup_host(ctx, hnqn, hid); + if (!h) + return -ENOMEM; + + libnvme_host_set_hostsymname(h, NULL); + + *host = h; + return 0; } __libnvme_public const char *libnvme_ctrl_get_state(libnvme_ctrl_t c) { - return NULL; + char *state = c->state; + + c->state = strdup(""); + free(state); + return c->state; } __libnvme_public int libnvme_init_ctrl(__libnvme_unused libnvme_host_t h, @@ -53,14 +165,122 @@ int libnvme_get_ctrl_transport(__libnvme_unused const char *path, char **traddr, char **addr, char **trsvcid, char **host_traddr, char **host_iface) { - return -ENOTSUP; + const struct ctrl_map_entry *ctrl_entry; + int ret; + + *transport = NULL; + *traddr = NULL; + *addr = NULL; + *trsvcid = NULL; + *host_traddr = NULL; + *host_iface = NULL; + + ctrl_entry = libnvme_ctrl_map_lookup(name); + if (!ctrl_entry) + return -ENODEV; + + *transport = strdup("pcie"); + if (!*transport) + return -ENOMEM; + + ret = libnvme_ctrl_map_entry_get_pci_address(ctrl_entry, addr); + if (ret || !*addr) { + ret = -ENXIO; + goto free_transport; + } + + *traddr = strdup(*addr); + if (!*traddr) { + ret = -ENOMEM; + goto free_transport; + } + return 0; +free_transport: + free(*transport); + *transport = NULL; + free(*addr); + *addr = NULL; + return ret; } +static libnvme_subsystem_t libnvme_lookup_subsystem_windows(libnvme_host_t h, + const struct ctrl_map_entry *ctrl_entry) +{ + libnvme_subsystem_t s; + char *subsysnqn; + char *subsysname; + + subsysnqn = libnvme_ctrl_map_entry_get_subnqn(ctrl_entry); + if (!subsysnqn) + return NULL; + + subsysname = libnvme_ctrl_map_entry_get_subsys_name(ctrl_entry); + if (!subsysname) { + free(subsysnqn); + return NULL; + } + + s = libnvme_lookup_subsystem(h, subsysname, subsysnqn); + free(subsysnqn); + free(subsysname); + if (!s) + return NULL; + + /* Populate subsystem info from first controller */ + if (!s->serial) + s->serial = libnvme_ctrl_map_entry_get_serial(ctrl_entry); + if (!s->model) + s->model = libnvme_ctrl_map_entry_get_model(ctrl_entry); + if (!s->firmware) + s->firmware = libnvme_ctrl_map_entry_get_firmware(ctrl_entry); + + return s; +} __libnvme_public int libnvme_scan_ctrl(struct libnvme_global_ctx *ctx, const char *name, libnvme_ctrl_t *cp) { - return -ENOTSUP; + __cleanup_free char *path = NULL; + const struct ctrl_map_entry *ctrl_entry; + libnvme_host_t h; + libnvme_subsystem_t s; + libnvme_ctrl_t c; + int ret; + + libnvme_msg(ctx, LIBNVME_LOG_DEBUG, "scan controller %s\n", name); + ctrl_entry = libnvme_ctrl_map_lookup(name); + if (!ctrl_entry) + return -ENODEV; + ret = libnvme_ctrl_map_entry_get_ctrl_path(ctrl_entry, &path); + if (ret) + return ret; + + ret = libnvme_get_host(ctx, NULL, NULL, &h); + if (ret) + return ret; + + s = libnvme_lookup_subsystem_windows(h, ctrl_entry); + if (!s) + return -ENOMEM; + + ret = libnvme_ctrl_alloc(ctx, s, path, name, &c); + if (ret) + return ret; + + ret = libnvme_ctrl_scan_paths(ctx, c); + if (ret) { + libnvme_free_ctrl(c); + return ret; + } + + ret = libnvme_ctrl_scan_namespaces(ctx, c); + if (ret) { + libnvme_free_ctrl(c); + return ret; + } + + *cp = c; + return 0; } __libnvme_public char *libnvme_get_subsys_attr( @@ -110,19 +330,130 @@ const char *libnvme_ns_sysfs_dir(void) int libnvme_ns_init(const char *path, struct libnvme_ns *ns) { - return -ENOTSUP; + __cleanup_libnvme_free struct nvme_id_ns *id = NULL; + uint8_t flbas; + int ret; + + id = libnvme_alloc(sizeof(*id)); + if (!id) + return -ENOMEM; + + ret = libnvme_ns_identify(ns, id); + if (ret) + return ret; + + nvme_id_ns_flbas_to_lbaf_inuse(id->flbas, &flbas); + ns->lba_size = 1 << id->lbaf[flbas].ds; + ns->lba_count = le64_to_cpu(id->nsze); + ns->lba_util = le64_to_cpu(id->nuse); + ns->meta_size = le16_to_cpu(id->lbaf[flbas].ms); + + return 0; } int libnvme_ns_open(struct libnvme_global_ctx *ctx, __libnvme_unused const char *sys_path, const char *name, libnvme_ns_t *ns) { - return -ENOTSUP; + const struct ctrl_map_entry *ctrl_entry; + struct libnvme_transport_handle *hdl; + struct libnvme_ns_head *head; + struct libnvme_ns *n; + HANDLE h; + int ret; + + n = calloc(1, sizeof(*n)); + if (!n) + return -ENOMEM; + + head = calloc(1, sizeof(*head)); + if (!head) { + free(n); + return -ENOMEM; + } + + head->n = n; + list_head_init(&head->paths); + + n->ctx = ctx; + n->head = head; + n->hdl = NULL; + + /* Open the device to query the namespace ID */ + h = CreateFileA(name, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (h == INVALID_HANDLE_VALUE) { + ret = -EIO; + goto free_ns; + } + + hdl = __libnvme_create_transport_handle(ctx); + if (!hdl) { + CloseHandle(h); + ret = -ENOMEM; + goto free_ns; + } + + hdl->fd = h; + hdl->type = LIBNVME_TRANSPORT_HANDLE_TYPE_DIRECT; + + ret = libnvme_get_nsid(hdl, &n->nsid); + libnvme_close(hdl); + if (ret) + goto free_ns; + + ctrl_entry = libnvme_ctrl_map_lookup_by_physdrive(name); + if (!ctrl_entry) { + ret = -ENODEV; + goto free_ns; + } + ret = asprintf(&n->name, "%sn%d", + libnvme_ctrl_map_entry_get_ctrl_name(ctrl_entry), + n->nsid); + if (ret < 0) { + ret = -ENOMEM; + goto free_ns; + } + + n->generic_name = strdup(name); + if (!n->generic_name) { + ret = -ENOMEM; + goto free_ns; + } + + ret = libnvme_ns_init(NULL, n); + if (ret) + goto free_ns; + + list_node_init(&n->entry); + + libnvme_ns_release_transport_handle(n); + + *ns = n; + return 0; + +free_ns: + free(n->name); + free(n->generic_name); + free(head); + free(n); + return ret; } int __libnvme_scan_namespace(struct libnvme_global_ctx *ctx, __libnvme_unused const char *sysfs_dir, const char *name, libnvme_ns_t *ns) { - return -ENOTSUP; + struct libnvme_ns *n = NULL; + int ret; + + ret = libnvme_ns_open(ctx, NULL, name, &n); + if (ret) + return ret; + + n->sysfs_dir = strdup(name); /* \\\\.\\PhysicalDriveX */ + + *ns = n; + return 0; } diff --git a/libnvme/src/nvme/tree.c b/libnvme/src/nvme/tree.c index d1bff02330..8c3e5004c1 100644 --- a/libnvme/src/nvme/tree.c +++ b/libnvme/src/nvme/tree.c @@ -565,13 +565,15 @@ static int nvme_subsystem_scan_namespaces(struct libnvme_global_ctx *ctx, static int libnvme_init_subsystem(libnvme_subsystem_t s, const char *name) { - char *path; + char *path = NULL; + const char *sysfs_dir = libnvme_subsys_sysfs_dir(); - if (asprintf(&path, "%s/%s", libnvme_subsys_sysfs_dir(), name) < 0) - return -ENOMEM; + if (sysfs_dir) + if (asprintf(&path, "%s/%s", sysfs_dir, name) < 0) + return -ENOMEM; s->model = libnvme_get_attr(path, "model"); - if (!s->model) + if (path && !s->model) s->model = strdup("undefined"); s->serial = libnvme_get_attr(path, "serial"); s->firmware = libnvme_get_attr(path, "firmware_rev"); @@ -594,17 +596,20 @@ static int libnvme_scan_subsystem(struct libnvme_global_ctx *ctx, { struct libnvme_subsystem *s = NULL, *_s; __cleanup_free char *path = NULL, *subsysnqn = NULL; + const char *sysfs_dir = libnvme_subsys_sysfs_dir(); libnvme_host_t h = NULL; int ret; libnvme_msg(ctx, LIBNVME_LOG_DEBUG, "scan subsystem %s\n", name); - ret = asprintf(&path, "%s/%s", libnvme_subsys_sysfs_dir(), name); - if (ret < 0) - return -ENOMEM; + if (sysfs_dir) { + ret = asprintf(&path, "%s/%s", sysfs_dir, name); + if (ret < 0) + return -ENOMEM; - subsysnqn = libnvme_get_attr(path, "subsysnqn"); - if (!subsysnqn) - return -ENODEV; + subsysnqn = libnvme_get_attr(path, "subsysnqn"); + if (!subsysnqn) + return -ENODEV; + } libnvme_for_each_host(ctx, h) { libnvme_for_each_subsystem(h, _s) { /* diff --git a/meson.build b/meson.build index 3d4c1fc682..a84cea7e47 100644 --- a/meson.build +++ b/meson.build @@ -151,6 +151,8 @@ conf.set('CONFIG_TOP', want_top, description: 'Is nvme top enabled') if host_system == 'windows' kernel32_dep = cc.find_library('kernel32', required: true) bcrypt_dep = cc.find_library('bcrypt', required: true) + setupapi_dep = cc.find_library('setupapi', required: true) + cfgmgr32_dep = cc.find_library('cfgmgr32', required: true) endif conf.set10( @@ -778,6 +780,8 @@ if host_system == 'windows' dep_dict += { 'kernel32': kernel32_dep.found(), 'bcrypt': bcrypt_dep.found(), + 'setupapi': setupapi_dep.found(), + 'cfgmgr32': cfgmgr32_dep.found(), } endif summary(dep_dict, section: 'Dependencies', bool_yn: true)