| File: | .build-ci/../tests/cli/libmock_nvme.c |
| Warning: | line 950, column 12 Potential leak of memory pointed to by 'payload' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | |||
| 2 | /* | |||
| 3 | * This file is part of nvme-cli. | |||
| 4 | * | |||
| 5 | * Copyright (c) 2026 SUSE LLC | |||
| 6 | * | |||
| 7 | * Authors: Daniel Wagner <dwagner@suse.com> | |||
| 8 | */ | |||
| 9 | ||||
| 10 | /* | |||
| 11 | * LD_PRELOAD shim for the CLI-driven, hardware-free tests under tests/cli/. | |||
| 12 | * | |||
| 13 | * Intercepts open()/write()/ioctl()/close() on /dev/nvme-fabrics, | |||
| 14 | * /dev/nvme<N> (controller char device), and /dev/nvme<N>n<M> (namespace | |||
| 15 | * char device). This lets any nvme-cli command run against a fake target, | |||
| 16 | * with no kernel nvme-fabrics module, no real hardware, and no root. | |||
| 17 | * | |||
| 18 | * Also intercepts the host network queries the default --nr-io-queues | |||
| 19 | * computation makes: the NETLINK_ROUTE egress route lookup, the | |||
| 20 | * ETHTOOL_GCHANNELS ioctl() and get_nprocs(). So the test, not the host | |||
| 21 | * running it, decides the NIC and CPU setup. | |||
| 22 | * | |||
| 23 | * Every intercepted write() (fabrics connect args) or ioctl() goes over a | |||
| 24 | * Unix socket to a Python IPC server. Admin and I/O passthru commands are | |||
| 25 | * decoded here, so the server sees the command fields. Any other ioctl() | |||
| 26 | * is forwarded raw: its decoded request number and argument bytes. So is | |||
| 27 | * getpass(), so a test can answer password prompts. The server sends back | |||
| 28 | * the response. This way, the Python test file controls the whole | |||
| 29 | * scenario, not this shim. | |||
| 30 | */ | |||
| 31 | ||||
| 32 | /* | |||
| 33 | * This file defines open()/open64()/openat()/openat64()/fstat()/fstat64() | |||
| 34 | * as distinct, separately-interposed symbols, so _FILE_OFFSET_BITS must | |||
| 35 | * stay undefined here: otherwise glibc's LFS headers would "#define open | |||
| 36 | * open64" etc. and silently rename this file's own open() definition into | |||
| 37 | * a second open64(), colliding with the explicit one. On the 32-bit | |||
| 38 | * architectures Debian/Ubuntu have migrated to 64-bit time_t (e.g. armhf), | |||
| 39 | * the cross-gcc implicitly sets _TIME_BITS=64, which glibc's headers | |||
| 40 | * refuse to pair with an unset/non-64 _FILE_OFFSET_BITS -- so that has to | |||
| 41 | * come undefined too, for the same "let this TU pick its own copy of these | |||
| 42 | * macros, independent of the project-wide -D_FILE_OFFSET_BITS=64" reason. | |||
| 43 | */ | |||
| 44 | #undef _FILE_OFFSET_BITS | |||
| 45 | #undef _TIME_BITS | |||
| 46 | #ifndef _GNU_SOURCE1 | |||
| 47 | #define _GNU_SOURCE1 | |||
| 48 | #endif | |||
| 49 | ||||
| 50 | #include <arpa/inet.h> | |||
| 51 | #include <dlfcn.h> | |||
| 52 | #include <endian.h> | |||
| 53 | #include <errno(*__errno_location ()).h> | |||
| 54 | #include <fcntl.h> | |||
| 55 | #include <linux1/ethtool.h> | |||
| 56 | #include <linux1/netlink.h> | |||
| 57 | #include <linux1/rtnetlink.h> | |||
| 58 | #include <linux1/sockios.h> | |||
| 59 | #include <linux1/types.h> | |||
| 60 | #include <net/if.h> | |||
| 61 | #include <stdarg.h> | |||
| 62 | #include <stdbool.h> | |||
| 63 | #include <stdint.h> | |||
| 64 | #include <stdio.h> | |||
| 65 | #include <stdlib.h> | |||
| 66 | #include <string.h> | |||
| 67 | #include <sys/ioctl.h> | |||
| 68 | #include <sys/socket.h> | |||
| 69 | #include <sys/stat.h> | |||
| 70 | #include <sys/sysinfo.h> | |||
| 71 | #include <sys/types.h> | |||
| 72 | #include <sys/un.h> | |||
| 73 | #include <unistd.h> | |||
| 74 | ||||
| 75 | #include <shared/cleanup-util.h> | |||
| 76 | #include <shared/compiler-attributes-util.h> | |||
| 77 | ||||
| 78 | struct linux_passthru_cmd32 { | |||
| 79 | __u8 opcode; | |||
| 80 | __u8 flags; | |||
| 81 | __u16 rsvd1; | |||
| 82 | __u32 nsid; | |||
| 83 | __u32 cdw2; | |||
| 84 | __u32 cdw3; | |||
| 85 | __u64 metadata; | |||
| 86 | __u64 addr; | |||
| 87 | __u32 metadata_len; | |||
| 88 | __u32 data_len; | |||
| 89 | __u32 cdw10; | |||
| 90 | __u32 cdw11; | |||
| 91 | __u32 cdw12; | |||
| 92 | __u32 cdw13; | |||
| 93 | __u32 cdw14; | |||
| 94 | __u32 cdw15; | |||
| 95 | __u32 timeout_ms; | |||
| 96 | __u32 result; | |||
| 97 | }; | |||
| 98 | ||||
| 99 | struct linux_passthru_cmd64 { | |||
| 100 | __u8 opcode; | |||
| 101 | __u8 flags; | |||
| 102 | __u16 rsvd1; | |||
| 103 | __u32 nsid; | |||
| 104 | __u32 cdw2; | |||
| 105 | __u32 cdw3; | |||
| 106 | __u64 metadata; | |||
| 107 | __u64 addr; | |||
| 108 | __u32 metadata_len; | |||
| 109 | __u32 data_len; | |||
| 110 | __u32 cdw10; | |||
| 111 | __u32 cdw11; | |||
| 112 | __u32 cdw12; | |||
| 113 | __u32 cdw13; | |||
| 114 | __u32 cdw14; | |||
| 115 | __u32 cdw15; | |||
| 116 | __u32 timeout_ms; | |||
| 117 | __u32 rsvd2; | |||
| 118 | __u64 result; | |||
| 119 | }; | |||
| 120 | ||||
| 121 | #define LIBNVME_IOCTL_ADMIN_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x41)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) _IOWR('N', 0x41, struct linux_passthru_cmd32)(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x41)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) | |||
| 122 | #define LIBNVME_IOCTL_IO_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x43)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) _IOWR('N', 0x43, struct linux_passthru_cmd32)(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x43)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) | |||
| 123 | #define LIBNVME_IOCTL_ADMIN64_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x47)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8))) _IOWR('N', 0x47, struct linux_passthru_cmd64)(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x47)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8))) | |||
| 124 | #define LIBNVME_IOCTL_IO64_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x48)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8))) _IOWR('N', 0x48, struct linux_passthru_cmd64)(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x48)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8))) | |||
| 125 | ||||
| 126 | enum ipc_type { | |||
| 127 | IPC_TYPE_WRITE = 1, /* write() on /dev/nvme-fabrics */ | |||
| 128 | IPC_TYPE_IOCTL = 2, /* admin or I/O passthru ioctl() */ | |||
| 129 | IPC_TYPE_RAW_IOCTL = 3, /* other ioctl() on a mocked device */ | |||
| 130 | IPC_TYPE_GETPASS = 4, /* getpass(), payload is the prompt */ | |||
| 131 | IPC_TYPE_ROUTE = 5, /* route lookup, "daddr\0saddr" */ | |||
| 132 | IPC_TYPE_NIC_QUEUES = 6, /* ETHTOOL_GCHANNELS, the ifname */ | |||
| 133 | IPC_TYPE_NPROCS = 7, /* get_nprocs() */ | |||
| 134 | }; | |||
| 135 | ||||
| 136 | /* Direction of a raw ioctl, independent of the arch's _IOC_* values. */ | |||
| 137 | enum ipc_ioc_dir { | |||
| 138 | IPC_IOC_WRITE = 1 << 0, /* the caller passes data in */ | |||
| 139 | IPC_IOC_READ = 1 << 1, /* the caller gets data back */ | |||
| 140 | }; | |||
| 141 | ||||
| 142 | /* | |||
| 143 | * What a passthru request carries. A command which transfers data to the | |||
| 144 | * controller sends its data, then its metadata, as the payload. | |||
| 145 | */ | |||
| 146 | enum ipc_cmd_flags { | |||
| 147 | IPC_CMD_ADMIN = 1 << 0, /* admin, not I/O, queue */ | |||
| 148 | IPC_CMD_DATA = 1 << 1, /* payload has the data */ | |||
| 149 | IPC_CMD_METADATA = 1 << 2, /* payload has the metadata */ | |||
| 150 | }; | |||
| 151 | ||||
| 152 | /* Wire format shared with the Python IPC server -- keep both sides in sync. */ | |||
| 153 | struct __attribute__((packed)) ipc_request { | |||
| 154 | uint32_t type; /* enum ipc_type */ | |||
| 155 | uint32_t fd; /* instance number for IOCTL */ | |||
| 156 | uint32_t data_len; /* payload length (follows immediately) */ | |||
| 157 | uint32_t request; /* ioctl request code */ | |||
| 158 | uint8_t opcode; /* nvme opcode */ | |||
| 159 | uint8_t rsvd[3]; | |||
| 160 | uint32_t nsid; | |||
| 161 | uint32_t cdw10; | |||
| 162 | uint32_t cdw11; | |||
| 163 | uint32_t cdw12; | |||
| 164 | uint32_t cdw13; | |||
| 165 | uint32_t cdw14; | |||
| 166 | uint32_t cdw15; | |||
| 167 | uint32_t req_len; /* command data length */ | |||
| 168 | /* raw ioctl only: the decoded request number */ | |||
| 169 | uint8_t ioc_dir; /* enum ipc_ioc_dir */ | |||
| 170 | uint8_t ioc_type; | |||
| 171 | uint8_t ioc_nr; | |||
| 172 | uint8_t big_endian; /* byte order of the payload */ | |||
| 173 | uint32_t ioc_size; | |||
| 174 | /* passthru only: the rest of the command */ | |||
| 175 | uint8_t flags; | |||
| 176 | uint8_t cmd_flags; /* enum ipc_cmd_flags */ | |||
| 177 | uint8_t rsvd2[2]; | |||
| 178 | uint32_t cdw2; | |||
| 179 | uint32_t cdw3; | |||
| 180 | uint32_t metadata_len; | |||
| 181 | uint32_t timeout_ms; | |||
| 182 | }; | |||
| 183 | ||||
| 184 | struct __attribute__((packed)) ipc_response { | |||
| 185 | int32_t status; /* 0 for success, -1 for a real syscall/errno failure */ | |||
| 186 | int32_t errno_val; /* errno to return on error (status == -1 only) */ | |||
| 187 | int32_t sc_status; /* NVMe completion status */ | |||
| 188 | uint32_t result; /* cmd->result */ | |||
| 189 | uint32_t data_len; /* payload length (follows immediately) */ | |||
| 190 | }; | |||
| 191 | ||||
| 192 | #ifndef MOCK_FUZZ | |||
| 193 | /* | |||
| 194 | * This process may be running as real target-arch code (a cross build | |||
| 195 | * under qemu-user), while nvme_mock_ipc.py on the other end of the socket | |||
| 196 | * is always native host-arch Python. Those are only the same "native" byte | |||
| 197 | * order on a non-cross run, so the wire format is explicit little-endian | |||
| 198 | * (matching nvme_mock_ipc.py's "<" struct formats) rather than whatever | |||
| 199 | * this process's native order happens to be. | |||
| 200 | */ | |||
| 201 | static void ipc_request_encode(struct ipc_request *r) | |||
| 202 | { | |||
| 203 | r->type = htole32(r->type)__uint32_identity (r->type); | |||
| 204 | r->fd = htole32(r->fd)__uint32_identity (r->fd); | |||
| 205 | r->data_len = htole32(r->data_len)__uint32_identity (r->data_len); | |||
| 206 | r->request = htole32(r->request)__uint32_identity (r->request); | |||
| 207 | r->nsid = htole32(r->nsid)__uint32_identity (r->nsid); | |||
| 208 | r->cdw10 = htole32(r->cdw10)__uint32_identity (r->cdw10); | |||
| 209 | r->cdw11 = htole32(r->cdw11)__uint32_identity (r->cdw11); | |||
| 210 | r->cdw12 = htole32(r->cdw12)__uint32_identity (r->cdw12); | |||
| 211 | r->cdw13 = htole32(r->cdw13)__uint32_identity (r->cdw13); | |||
| 212 | r->cdw14 = htole32(r->cdw14)__uint32_identity (r->cdw14); | |||
| 213 | r->cdw15 = htole32(r->cdw15)__uint32_identity (r->cdw15); | |||
| 214 | r->req_len = htole32(r->req_len)__uint32_identity (r->req_len); | |||
| 215 | r->ioc_size = htole32(r->ioc_size)__uint32_identity (r->ioc_size); | |||
| 216 | r->cdw2 = htole32(r->cdw2)__uint32_identity (r->cdw2); | |||
| 217 | r->cdw3 = htole32(r->cdw3)__uint32_identity (r->cdw3); | |||
| 218 | r->metadata_len = htole32(r->metadata_len)__uint32_identity (r->metadata_len); | |||
| 219 | r->timeout_ms = htole32(r->timeout_ms)__uint32_identity (r->timeout_ms); | |||
| 220 | } | |||
| 221 | ||||
| 222 | static void ipc_response_decode(struct ipc_response *r) | |||
| 223 | { | |||
| 224 | r->status = (int32_t)le32toh((uint32_t)r->status)__uint32_identity ((uint32_t)r->status); | |||
| 225 | r->errno_val = (int32_t)le32toh((uint32_t)r->errno_val)__uint32_identity ((uint32_t)r->errno_val); | |||
| 226 | r->sc_status = (int32_t)le32toh((uint32_t)r->sc_status)__uint32_identity ((uint32_t)r->sc_status); | |||
| 227 | r->result = le32toh(r->result)__uint32_identity (r->result); | |||
| 228 | r->data_len = le32toh(r->data_len)__uint32_identity (r->data_len); | |||
| 229 | } | |||
| 230 | #endif | |||
| 231 | ||||
| 232 | typedef int (*orig_open_t)(const char *pathname, int flags, ...); | |||
| 233 | typedef int (*orig_open64_t)(const char *pathname, int flags, ...); | |||
| 234 | typedef int (*orig_openat_t)(int dirfd, const char *pathname, int flags, ...); | |||
| 235 | typedef int (*orig_openat64_t)(int dirfd, const char *pathname, int flags, ...); | |||
| 236 | typedef ssize_t (*orig_write_t)(int fd, const void *buf, size_t count); | |||
| 237 | typedef ssize_t (*orig_read_t)(int fd, void *buf, size_t count); | |||
| 238 | typedef int (*orig_ioctl_t)(int fd, unsigned long request, ...); | |||
| 239 | typedef int (*orig_close_t)(int fd); | |||
| 240 | typedef int (*orig_fstat_t)(int fd, struct stat *buf); | |||
| 241 | typedef int (*orig_fstat64_t)(int fd, struct stat64 *buf); | |||
| 242 | typedef int (*orig_fstat64_time64_t)(int fd, void *buf); | |||
| 243 | typedef char *(*orig_getpass_t)(const char *prompt); | |||
| 244 | typedef int (*orig_socket_t)(int domain, int type, int protocol); | |||
| 245 | typedef ssize_t (*orig_sendmsg_t)(int fd, const struct msghdr *msg, int flags); | |||
| 246 | typedef ssize_t (*orig_recvmsg_t)(int fd, struct msghdr *msg, int flags); | |||
| 247 | typedef char *(*orig_if_indextoname_t)(unsigned int ifindex, char *ifname); | |||
| 248 | typedef int (*orig_get_nprocs_t)(void); | |||
| 249 | ||||
| 250 | static orig_open_t orig_open; | |||
| 251 | static orig_open64_t orig_open64; | |||
| 252 | static orig_openat_t orig_openat; | |||
| 253 | static orig_openat64_t orig_openat64; | |||
| 254 | static orig_write_t orig_write; | |||
| 255 | #ifndef MOCK_FUZZ | |||
| 256 | static orig_read_t orig_read; | |||
| 257 | #endif | |||
| 258 | static orig_ioctl_t orig_ioctl; | |||
| 259 | static orig_ioctl_t orig_ioctl_time64; | |||
| 260 | static orig_close_t orig_close; | |||
| 261 | static orig_fstat_t orig_fstat; | |||
| 262 | static orig_fstat64_t orig_fstat64; | |||
| 263 | static orig_fstat64_time64_t orig_fstat64_time64; | |||
| 264 | static orig_getpass_t orig_getpass; | |||
| 265 | static orig_socket_t orig_socket; | |||
| 266 | static orig_sendmsg_t orig_sendmsg; | |||
| 267 | static orig_sendmsg_t orig_sendmsg64; | |||
| 268 | static orig_recvmsg_t orig_recvmsg; | |||
| 269 | static orig_recvmsg_t orig_recvmsg64; | |||
| 270 | static orig_if_indextoname_t orig_if_indextoname; | |||
| 271 | static orig_get_nprocs_t orig_get_nprocs; | |||
| 272 | ||||
| 273 | static int mock_fabrics_fd = -1; | |||
| 274 | static int mock_fabrics_manager_fd = -1; | |||
| 275 | static bool_Bool mock_debug; | |||
| 276 | ||||
| 277 | #define MAX_MOCK_CTRLS16 16 | |||
| 278 | static struct { | |||
| 279 | int fd; | |||
| 280 | int instance; | |||
| 281 | bool_Bool is_block; | |||
| 282 | } mock_ctrl_fds[MAX_MOCK_CTRLS16]; | |||
| 283 | static int num_mock_ctrls; | |||
| 284 | ||||
| 285 | /* | |||
| 286 | * The route lookup reply: the egress interface the IPC server picked, | |||
| 287 | * or the errno it failed with. The interface gets a made-up ifindex, | |||
| 288 | * which if_indextoname() maps back to the name. | |||
| 289 | */ | |||
| 290 | #define MOCK_IFINDEX0x4d4f434b 0x4d4f434b | |||
| 291 | static int mock_netlink_fd = -1; | |||
| 292 | static struct { | |||
| 293 | bool_Bool pending; | |||
| 294 | struct nlmsghdr req; | |||
| 295 | unsigned char family; | |||
| 296 | int error; | |||
| 297 | } mock_route_reply; | |||
| 298 | static char mock_ifname[IF_NAMESIZE16]; | |||
| 299 | ||||
| 300 | #define mock_dbg(fmt, ...)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " fmt, ... ); } while (0) \ | |||
| 301 | do { \ | |||
| 302 | if (mock_debug) \ | |||
| 303 | fprintf(stderrstderr, "[MOCK_DEBUG] " fmt, ##__VA_ARGS__); \ | |||
| 304 | } while (0) | |||
| 305 | ||||
| 306 | static void __shr_constructor__attribute__((constructor)) mock_init(void) | |||
| 307 | { | |||
| 308 | mock_debug = !!getenv("MOCK_DEBUG"); | |||
| 309 | mock_dbg("libmock_nvme loaded, sizeof(ipc_request)=%zu, sizeof(ipc_response)=%zu\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "libmock_nvme loaded, sizeof(ipc_request)=%zu, sizeof(ipc_response)=%zu\n" , sizeof(struct ipc_request), sizeof(struct ipc_response)); } while (0) | |||
| 310 | sizeof(struct ipc_request), sizeof(struct ipc_response))do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "libmock_nvme loaded, sizeof(ipc_request)=%zu, sizeof(ipc_response)=%zu\n" , sizeof(struct ipc_request), sizeof(struct ipc_response)); } while (0); | |||
| 311 | } | |||
| 312 | ||||
| 313 | static void init_orig_functions(void) | |||
| 314 | { | |||
| 315 | if (orig_open) | |||
| 316 | return; | |||
| 317 | ||||
| 318 | orig_open = (orig_open_t)dlsym(RTLD_NEXT((void *) -1l), "open"); | |||
| 319 | orig_open64 = (orig_open64_t)dlsym(RTLD_NEXT((void *) -1l), "open64"); | |||
| 320 | orig_openat = (orig_openat_t)dlsym(RTLD_NEXT((void *) -1l), "openat"); | |||
| 321 | orig_openat64 = (orig_openat64_t)dlsym(RTLD_NEXT((void *) -1l), "openat64"); | |||
| 322 | orig_write = (orig_write_t)dlsym(RTLD_NEXT((void *) -1l), "write"); | |||
| 323 | #ifndef MOCK_FUZZ | |||
| 324 | orig_read = (orig_read_t)dlsym(RTLD_NEXT((void *) -1l), "read"); | |||
| 325 | #endif | |||
| 326 | orig_ioctl = (orig_ioctl_t)dlsym(RTLD_NEXT((void *) -1l), "ioctl"); | |||
| 327 | orig_ioctl_time64 = (orig_ioctl_t)dlsym(RTLD_NEXT((void *) -1l), "__ioctl_time64"); | |||
| 328 | orig_close = (orig_close_t)dlsym(RTLD_NEXT((void *) -1l), "close"); | |||
| 329 | orig_fstat = (orig_fstat_t)dlsym(RTLD_NEXT((void *) -1l), "fstat"); | |||
| 330 | orig_fstat64 = (orig_fstat64_t)dlsym(RTLD_NEXT((void *) -1l), "fstat64"); | |||
| 331 | orig_fstat64_time64 = (orig_fstat64_time64_t)dlsym(RTLD_NEXT((void *) -1l), "__fstat64_time64"); | |||
| 332 | orig_getpass = (orig_getpass_t)dlsym(RTLD_NEXT((void *) -1l), "getpass"); | |||
| 333 | orig_socket = (orig_socket_t)dlsym(RTLD_NEXT((void *) -1l), "socket"); | |||
| 334 | orig_sendmsg = (orig_sendmsg_t)dlsym(RTLD_NEXT((void *) -1l), "sendmsg"); | |||
| 335 | orig_sendmsg64 = (orig_sendmsg_t)dlsym(RTLD_NEXT((void *) -1l), "__sendmsg64"); | |||
| 336 | orig_recvmsg = (orig_recvmsg_t)dlsym(RTLD_NEXT((void *) -1l), "recvmsg"); | |||
| 337 | orig_recvmsg64 = (orig_recvmsg_t)dlsym(RTLD_NEXT((void *) -1l), "__recvmsg64"); | |||
| 338 | orig_if_indextoname = (orig_if_indextoname_t)dlsym(RTLD_NEXT((void *) -1l), | |||
| 339 | "if_indextoname"); | |||
| 340 | orig_get_nprocs = (orig_get_nprocs_t)dlsym(RTLD_NEXT((void *) -1l), "get_nprocs"); | |||
| 341 | } | |||
| 342 | ||||
| 343 | /* | |||
| 344 | * Built into a fuzz target (tests/fuzz/fuzz-device.c), the shim has no | |||
| 345 | * IPC server: the fuzz input answers every command instead. A "socket" | |||
| 346 | * is a /dev/null fd, so the callers can close it as usual. | |||
| 347 | * | |||
| 348 | * Both builds define each function once, at the same line, and keep | |||
| 349 | * MOCK_FUZZ inside the body: gcovr merges the coverage of both and | |||
| 350 | * fails on a function it finds on two lines. | |||
| 351 | */ | |||
| 352 | static int connect_ipc_socket(void) | |||
| 353 | { | |||
| 354 | #ifdef MOCK_FUZZ | |||
| 355 | return orig_open("/dev/null", O_RDWR02); | |||
| 356 | #else | |||
| 357 | const char *sock_path = getenv("MOCK_IPC_SOCK"); | |||
| 358 | struct sockaddr_un addr; | |||
| 359 | int fd; | |||
| 360 | ||||
| 361 | if (!sock_path) { | |||
| 362 | mock_dbg("MOCK_IPC_SOCK env var not set!\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "MOCK_IPC_SOCK env var not set!\n" ); } while (0); | |||
| 363 | return -1; | |||
| 364 | } | |||
| 365 | ||||
| 366 | fd = orig_socket(AF_UNIX1, SOCK_STREAMSOCK_STREAM, 0); | |||
| 367 | if (fd < 0) { | |||
| 368 | mock_dbg("failed to create socket: %s\n", strerror(errno))do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "failed to create socket: %s\n" , strerror((*__errno_location ()))); } while (0); | |||
| 369 | return -1; | |||
| 370 | } | |||
| 371 | ||||
| 372 | memset(&addr, 0, sizeof(addr)); | |||
| 373 | addr.sun_family = AF_UNIX1; | |||
| 374 | strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1); | |||
| 375 | if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { | |||
| 376 | mock_dbg("connect failed to '%s': %s\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "connect failed to '%s': %s\n" , sock_path, strerror((*__errno_location ()))); } while (0) | |||
| 377 | sock_path, strerror(errno))do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "connect failed to '%s': %s\n" , sock_path, strerror((*__errno_location ()))); } while (0); | |||
| 378 | orig_close(fd); | |||
| 379 | return -1; | |||
| 380 | } | |||
| 381 | ||||
| 382 | return fd; | |||
| 383 | #endif | |||
| 384 | } | |||
| 385 | ||||
| 386 | const char *default_opts = | |||
| 387 | "instance,cntlid,concat,ctrl_loss_tmo,data_digest," | |||
| 388 | "dhchap_ctrl_secret,dhchap_secret,disable_sqflow," | |||
| 389 | "discovery,duplicate_connect,fast_io_fail_tmo," | |||
| 390 | "hdr_digest,host_iface,host_traddr,hostid,hostnqn," | |||
| 391 | "keep_alive_tmo,keyring,nqn,nr_io_queues," | |||
| 392 | "nr_poll_queues,nr_write_queues,queue_size," | |||
| 393 | "reconnect_delay,tls,tls_key,tos,traddr,transport," | |||
| 394 | "trsvcid\n"; | |||
| 395 | ||||
| 396 | static int handle_open_intercept(const char *pathname, int flags) | |||
| 397 | { | |||
| 398 | int instance, real_fd, sv[2]; | |||
| 399 | bool_Bool is_block; | |||
| 400 | char *endp; | |||
| 401 | ||||
| 402 | if (!pathname) | |||
| 403 | return -2; | |||
| 404 | ||||
| 405 | if (!strcmp(pathname, "/dev/nvme-fabrics")) { | |||
| 406 | if (socketpair(AF_UNIX1, SOCK_STREAMSOCK_STREAM, 0, sv) < 0) | |||
| 407 | return -1; | |||
| 408 | ||||
| 409 | mock_fabrics_manager_fd = sv[0]; | |||
| 410 | mock_fabrics_fd = sv[1]; | |||
| 411 | ||||
| 412 | mock_dbg("intercepted /dev/nvme-fabrics open\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "intercepted /dev/nvme-fabrics open\n" ); } while (0); | |||
| 413 | ||||
| 414 | if ((flags & O_ACCMODE0003) == O_RDONLY00) { | |||
| 415 | const char *opts = getenv("MOCK_SUPPORTED_OPTIONS"); | |||
| 416 | ||||
| 417 | if (!opts) | |||
| 418 | opts = default_opts; | |||
| 419 | orig_write(mock_fabrics_manager_fd, opts, strlen(opts)); | |||
| 420 | orig_close(mock_fabrics_manager_fd); | |||
| 421 | mock_fabrics_manager_fd = -1; | |||
| 422 | } | |||
| 423 | ||||
| 424 | return mock_fabrics_fd; | |||
| 425 | } | |||
| 426 | ||||
| 427 | if (strncmp(pathname, "/dev/nvme", 9) || | |||
| 428 | pathname[9] < '0' || | |||
| 429 | pathname[9] > '9') | |||
| 430 | return -2; | |||
| 431 | ||||
| 432 | instance = strtol(pathname + 9, &endp, 10); | |||
| 433 | is_block = (*endp == 'n'); | |||
| 434 | if (is_block) { | |||
| 435 | char *nsp; | |||
| 436 | ||||
| 437 | strtol(endp + 1, &nsp, 10); | |||
| 438 | if (nsp == endp + 1 || *nsp != '\0') | |||
| 439 | return -2; | |||
| 440 | } else if (*endp != '\0') { | |||
| 441 | return -2; | |||
| 442 | } | |||
| 443 | ||||
| 444 | real_fd = orig_open("/dev/null", flags); | |||
| 445 | if (real_fd >= 0) { | |||
| 446 | if (num_mock_ctrls < MAX_MOCK_CTRLS16) { | |||
| 447 | mock_ctrl_fds[num_mock_ctrls].fd = real_fd; | |||
| 448 | mock_ctrl_fds[num_mock_ctrls].instance = instance; | |||
| 449 | mock_ctrl_fds[num_mock_ctrls].is_block = is_block; | |||
| 450 | num_mock_ctrls++; | |||
| 451 | } | |||
| 452 | mock_dbg("intercepted /dev/nvme%d open (mapped to fd %d)\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "intercepted /dev/nvme%d open (mapped to fd %d)\n" , instance, real_fd); } while (0) | |||
| 453 | instance, real_fd)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "intercepted /dev/nvme%d open (mapped to fd %d)\n" , instance, real_fd); } while (0); | |||
| 454 | } | |||
| 455 | ||||
| 456 | return real_fd; | |||
| 457 | } | |||
| 458 | ||||
| 459 | static mode_t open_mode_arg(int flags, va_list args) | |||
| 460 | { | |||
| 461 | if (flags & (O_CREAT0100 | O_TMPFILE(020000000 | 0200000))) | |||
| 462 | return va_arg(args, mode_t)__builtin_va_arg(args, mode_t); | |||
| 463 | ||||
| 464 | return 0; | |||
| 465 | } | |||
| 466 | ||||
| 467 | int open(const char *pathname, int flags, ...) | |||
| 468 | { | |||
| 469 | va_list args; | |||
| 470 | mode_t mode; | |||
| 471 | int fd; | |||
| 472 | ||||
| 473 | init_orig_functions(); | |||
| 474 | fd = handle_open_intercept(pathname, flags); | |||
| 475 | if (fd != -2) | |||
| 476 | return fd; | |||
| 477 | ||||
| 478 | va_start(args, flags)__builtin_va_start(args, flags); | |||
| 479 | mode = open_mode_arg(flags, args); | |||
| 480 | va_end(args)__builtin_va_end(args); | |||
| 481 | ||||
| 482 | return orig_open(pathname, flags, mode); | |||
| 483 | } | |||
| 484 | ||||
| 485 | int open64(const char *pathname, int flags, ...) | |||
| 486 | { | |||
| 487 | va_list args; | |||
| 488 | mode_t mode; | |||
| 489 | int fd; | |||
| 490 | ||||
| 491 | init_orig_functions(); | |||
| 492 | fd = handle_open_intercept(pathname, flags); | |||
| 493 | if (fd != -2) | |||
| 494 | return fd; | |||
| 495 | ||||
| 496 | va_start(args, flags)__builtin_va_start(args, flags); | |||
| 497 | mode = open_mode_arg(flags, args); | |||
| 498 | va_end(args)__builtin_va_end(args); | |||
| 499 | ||||
| 500 | return orig_open64(pathname, flags, mode); | |||
| 501 | } | |||
| 502 | ||||
| 503 | int openat(int dirfd, const char *pathname, int flags, ...) | |||
| 504 | { | |||
| 505 | va_list args; | |||
| 506 | mode_t mode; | |||
| 507 | int fd; | |||
| 508 | ||||
| 509 | init_orig_functions(); | |||
| 510 | fd = handle_open_intercept(pathname, flags); | |||
| 511 | if (fd != -2) | |||
| 512 | return fd; | |||
| 513 | ||||
| 514 | va_start(args, flags)__builtin_va_start(args, flags); | |||
| 515 | mode = open_mode_arg(flags, args); | |||
| 516 | va_end(args)__builtin_va_end(args); | |||
| 517 | ||||
| 518 | return orig_openat(dirfd, pathname, flags, mode); | |||
| 519 | } | |||
| 520 | ||||
| 521 | int openat64(int dirfd, const char *pathname, int flags, ...) | |||
| 522 | { | |||
| 523 | va_list args; | |||
| 524 | mode_t mode; | |||
| 525 | int fd; | |||
| 526 | ||||
| 527 | init_orig_functions(); | |||
| 528 | fd = handle_open_intercept(pathname, flags); | |||
| 529 | if (fd != -2) | |||
| 530 | return fd; | |||
| 531 | ||||
| 532 | va_start(args, flags)__builtin_va_start(args, flags); | |||
| 533 | mode = open_mode_arg(flags, args); | |||
| 534 | va_end(args)__builtin_va_end(args); | |||
| 535 | ||||
| 536 | return orig_openat64(dirfd, pathname, flags, mode); | |||
| 537 | } | |||
| 538 | ||||
| 539 | int __open_2(const char *pathname, int flags) | |||
| 540 | { | |||
| 541 | int fd; | |||
| 542 | ||||
| 543 | init_orig_functions(); | |||
| 544 | fd = handle_open_intercept(pathname, flags); | |||
| 545 | if (fd != -2) | |||
| 546 | return fd; | |||
| 547 | ||||
| 548 | return orig_open(pathname, flags, 0); | |||
| 549 | } | |||
| 550 | ||||
| 551 | int __open64_2(const char *pathname, int flags) | |||
| 552 | { | |||
| 553 | int fd; | |||
| 554 | ||||
| 555 | init_orig_functions(); | |||
| 556 | fd = handle_open_intercept(pathname, flags); | |||
| 557 | if (fd != -2) | |||
| 558 | return fd; | |||
| 559 | ||||
| 560 | return orig_open64(pathname, flags, 0); | |||
| 561 | } | |||
| 562 | ||||
| 563 | int __openat_2(int dirfd, const char *pathname, int flags) | |||
| 564 | { | |||
| 565 | int fd; | |||
| 566 | ||||
| 567 | init_orig_functions(); | |||
| 568 | fd = handle_open_intercept(pathname, flags); | |||
| 569 | if (fd != -2) | |||
| 570 | return fd; | |||
| 571 | ||||
| 572 | return orig_openat(dirfd, pathname, flags, 0); | |||
| 573 | } | |||
| 574 | ||||
| 575 | int __openat64_2(int dirfd, const char *pathname, int flags) | |||
| 576 | { | |||
| 577 | int fd; | |||
| 578 | ||||
| 579 | init_orig_functions(); | |||
| 580 | fd = handle_open_intercept(pathname, flags); | |||
| 581 | if (fd != -2) | |||
| 582 | return fd; | |||
| 583 | ||||
| 584 | return orig_openat64(dirfd, pathname, flags, 0); | |||
| 585 | } | |||
| 586 | ||||
| 587 | #ifdef MOCK_FUZZ | |||
| 588 | /* | |||
| 589 | * The kernel rejects a transfer larger than the controller takes. And a | |||
| 590 | * fuzzed log length must not have the mock serve, and nvme-cli print, | |||
| 591 | * gigabytes: the data of all commands of a run is limited too. A | |||
| 592 | * megabyte of log entries already prints as tens of megabytes of text, | |||
| 593 | * which takes seconds with the sanitizers. FUZZ_DATA_LIMIT sets another | |||
| 594 | * limit in bytes: most of the data structures fit a page, and with less | |||
| 595 | * data an input runs faster. | |||
| 596 | */ | |||
| 597 | #define FUZZ_MAX_TRANSFER (2 * 1024 * 1024) | |||
| 598 | #define FUZZ_MAX_TOTAL (1 * 1024 * 1024) | |||
| 599 | ||||
| 600 | static const uint8_t *fuzz_data; | |||
| 601 | static size_t fuzz_size; | |||
| 602 | static size_t fuzz_total; | |||
| 603 | static size_t fuzz_limit; | |||
| 604 | ||||
| 605 | static size_t fuzz_data_limit(void) | |||
| 606 | { | |||
| 607 | unsigned long long limit; | |||
| 608 | const char *env; | |||
| 609 | char *end; | |||
| 610 | ||||
| 611 | if (fuzz_limit) | |||
| 612 | return fuzz_limit; | |||
| 613 | fuzz_limit = FUZZ_MAX_TOTAL; | |||
| 614 | env = getenv("FUZZ_DATA_LIMIT"); | |||
| 615 | if (!env) | |||
| 616 | return fuzz_limit; | |||
| 617 | errno(*__errno_location ()) = 0; | |||
| 618 | limit = strtoull(env, &end, 10); | |||
| 619 | if (errno(*__errno_location ()) || end == env || *end || !limit || | |||
| 620 | (size_t)limit != limit) { | |||
| 621 | fprintf(stderrstderr, "FUZZ_DATA_LIMIT=%s: not a size in bytes\n", | |||
| 622 | env); | |||
| 623 | exit(EXIT_FAILURE1); | |||
| 624 | } | |||
| 625 | fuzz_limit = limit; | |||
| 626 | return fuzz_limit; | |||
| 627 | } | |||
| 628 | ||||
| 629 | /* The bytes the following commands get their answers from */ | |||
| 630 | void mock_fuzz_input(const uint8_t *data, size_t size) | |||
| 631 | { | |||
| 632 | fuzz_data = data; | |||
| 633 | fuzz_size = size; | |||
| 634 | fuzz_total = 0; | |||
| 635 | } | |||
| 636 | ||||
| 637 | /* The next bytes of the input, zeros once it is used up */ | |||
| 638 | static void fuzz_read(void *buf, size_t len) | |||
| 639 | { | |||
| 640 | size_t n = len < fuzz_size ? len : fuzz_size; | |||
| 641 | ||||
| 642 | memcpy(buf, fuzz_data, n); | |||
| 643 | memset((uint8_t *)buf + n, 0, len - n); | |||
| 644 | fuzz_data += n; | |||
| 645 | fuzz_size -= n; | |||
| 646 | } | |||
| 647 | ||||
| 648 | /* | |||
| 649 | * Answer a command from the fuzz input: a control byte, the result | |||
| 650 | * dword, and then the data the command reads. Most control bytes mean | |||
| 651 | * success, the rest an NVMe status (from the next two bytes) or a | |||
| 652 | * failed ioctl(). Everything that is no device command fails, so the | |||
| 653 | * real function takes over where there is one. | |||
| 654 | */ | |||
| 655 | static void *fuzz_ipc_call(struct ipc_request *req, | |||
| 656 | struct ipc_response *resp) | |||
| 657 | { | |||
| 658 | uint8_t ctl; | |||
| 659 | uint16_t sc; | |||
| 660 | void *buf; | |||
| 661 | ||||
| 662 | memset(resp, 0, sizeof(*resp)); | |||
| 663 | ||||
| 664 | switch (req->type) { | |||
| 665 | case IPC_TYPE_IOCTL: | |||
| 666 | resp->data_len = req->req_len; | |||
| 667 | break; | |||
| 668 | case IPC_TYPE_RAW_IOCTL: | |||
| 669 | if (req->ioc_dir & IPC_IOC_READ) | |||
| 670 | resp->data_len = req->ioc_size; | |||
| 671 | break; | |||
| 672 | default: | |||
| 673 | resp->status = -1; | |||
| 674 | resp->errno_val = EOPNOTSUPP95; | |||
| 675 | return NULL((void*)0); | |||
| 676 | } | |||
| 677 | ||||
| 678 | if (resp->data_len > FUZZ_MAX_TRANSFER) { | |||
| 679 | resp->status = -1; | |||
| 680 | resp->errno_val = EINVAL22; | |||
| 681 | return NULL((void*)0); | |||
| 682 | } | |||
| 683 | if (resp->data_len > fuzz_data_limit() - fuzz_total) { | |||
| 684 | resp->status = -1; | |||
| 685 | resp->errno_val = EIO5; | |||
| 686 | return NULL((void*)0); | |||
| 687 | } | |||
| 688 | fuzz_total += resp->data_len; | |||
| 689 | ||||
| 690 | fuzz_read(&ctl, sizeof(ctl)); | |||
| 691 | fuzz_read(&resp->result, sizeof(resp->result)); | |||
| 692 | resp->result = le32toh(resp->result)__uint32_identity (resp->result); | |||
| 693 | if (ctl >= 0xf8) { | |||
| 694 | resp->status = -1; | |||
| 695 | resp->errno_val = EIO5; | |||
| 696 | return NULL((void*)0); | |||
| 697 | } | |||
| 698 | if (ctl >= 0xe0) { | |||
| 699 | fuzz_read(&sc, sizeof(sc)); | |||
| 700 | resp->sc_status = le16toh(sc)__uint16_identity (sc) & 0x7fff; | |||
| 701 | return NULL((void*)0); | |||
| 702 | } | |||
| 703 | ||||
| 704 | if (!resp->data_len) | |||
| 705 | return NULL((void*)0); | |||
| 706 | buf = malloc(resp->data_len); | |||
| 707 | if (!buf) { | |||
| 708 | resp->status = -1; | |||
| 709 | resp->errno_val = ENOMEM12; | |||
| 710 | return NULL((void*)0); | |||
| 711 | } | |||
| 712 | fuzz_read(buf, resp->data_len); | |||
| 713 | ||||
| 714 | return buf; | |||
| 715 | } | |||
| 716 | #else | |||
| 717 | static bool_Bool read_full(int fd, void *buf, size_t len) | |||
| 718 | { | |||
| 719 | size_t total_read = 0; | |||
| 720 | ||||
| 721 | while (total_read < len) { | |||
| 722 | ssize_t n = orig_read(fd, (char *)buf + total_read, | |||
| 723 | len - total_read); | |||
| 724 | if (n < 0 && errno(*__errno_location ()) == EINTR4) | |||
| 725 | continue; | |||
| 726 | if (n <= 0) | |||
| 727 | return false0; | |||
| 728 | total_read += n; | |||
| 729 | } | |||
| 730 | ||||
| 731 | return true1; | |||
| 732 | } | |||
| 733 | ||||
| 734 | static bool_Bool write_full(int fd, const void *buf, size_t len) | |||
| 735 | { | |||
| 736 | size_t total_written = 0; | |||
| 737 | ||||
| 738 | while (total_written < len) { | |||
| 739 | ssize_t n = orig_write(fd, (const char *)buf + total_written, | |||
| 740 | len - total_written); | |||
| 741 | if (n < 0 && errno(*__errno_location ()) == EINTR4) | |||
| 742 | continue; | |||
| 743 | if (n <= 0) | |||
| 744 | return false0; | |||
| 745 | total_written += n; | |||
| 746 | } | |||
| 747 | ||||
| 748 | return true1; | |||
| 749 | } | |||
| 750 | ||||
| 751 | static void *read_ipc_response(int ipc_fd, struct ipc_response *resp) | |||
| 752 | { | |||
| 753 | char *payload; | |||
| 754 | ||||
| 755 | memset(resp, 0, sizeof(*resp)); | |||
| 756 | if (!read_full(ipc_fd, resp, sizeof(*resp))) { | |||
| 757 | mock_dbg("failed to read full IPC response header\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "failed to read full IPC response header\n" ); } while (0); | |||
| 758 | resp->status = -1; | |||
| 759 | resp->errno_val = EIO5; | |||
| 760 | return NULL((void*)0); | |||
| 761 | } | |||
| 762 | ipc_response_decode(resp); | |||
| 763 | ||||
| 764 | if (!resp->data_len) | |||
| 765 | return NULL((void*)0); | |||
| 766 | ||||
| 767 | payload = malloc(resp->data_len); | |||
| 768 | if (!payload) | |||
| 769 | return NULL((void*)0); | |||
| 770 | ||||
| 771 | if (!read_full(ipc_fd, payload, resp->data_len)) { | |||
| 772 | mock_dbg("failed to read full IPC response payload\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "failed to read full IPC response payload\n" ); } while (0); | |||
| 773 | free(payload); | |||
| 774 | resp->status = -1; | |||
| 775 | resp->errno_val = EIO5; | |||
| 776 | return NULL((void*)0); | |||
| 777 | } | |||
| 778 | ||||
| 779 | return payload; | |||
| 780 | } | |||
| 781 | ||||
| 782 | #endif | |||
| 783 | ||||
| 784 | /* | |||
| 785 | * Send a request and its data_len bytes of data, and read the response. | |||
| 786 | * A failed send is reported like a failed read. | |||
| 787 | */ | |||
| 788 | static void *ipc_call(int ipc_fd, struct ipc_request *req, const void *data, | |||
| 789 | struct ipc_response *resp) | |||
| 790 | { | |||
| 791 | #ifdef MOCK_FUZZ | |||
| 792 | (void)ipc_fd; | |||
| 793 | (void)data; | |||
| 794 | return fuzz_ipc_call(req, resp); | |||
| 795 | #else | |||
| 796 | size_t len = req->data_len; | |||
| 797 | ||||
| 798 | ipc_request_encode(req); | |||
| 799 | if (!write_full(ipc_fd, req, sizeof(*req)) || | |||
| 800 | (len && !write_full(ipc_fd, data, len))) { | |||
| 801 | mock_dbg("failed to send IPC request\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "failed to send IPC request\n" ); } while (0); | |||
| 802 | memset(resp, 0, sizeof(*resp)); | |||
| 803 | resp->status = -1; | |||
| 804 | resp->errno_val = EIO5; | |||
| 805 | return NULL((void*)0); | |||
| 806 | } | |||
| 807 | ||||
| 808 | return read_ipc_response(ipc_fd, resp); | |||
| 809 | #endif | |||
| 810 | } | |||
| 811 | ||||
| 812 | ssize_t write(int fd, const void *buf, size_t count) | |||
| 813 | { | |||
| 814 | struct ipc_response resp; | |||
| 815 | char *resp_data; | |||
| 816 | int ipc_fd; | |||
| 817 | ||||
| 818 | init_orig_functions(); | |||
| 819 | ||||
| 820 | if (fd != mock_fabrics_fd) | |||
| 821 | return orig_write(fd, buf, count); | |||
| 822 | ||||
| 823 | mock_dbg("intercepted connect args on fabrics fd %d\n", fd)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "intercepted connect args on fabrics fd %d\n" , fd); } while (0); | |||
| 824 | ||||
| 825 | ipc_fd = connect_ipc_socket(); | |||
| 826 | if (ipc_fd < 0) { | |||
| 827 | mock_dbg("could not connect to IPC socket\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "could not connect to IPC socket\n" ); } while (0); | |||
| 828 | return orig_write(fd, buf, count); | |||
| 829 | } | |||
| 830 | ||||
| 831 | struct ipc_request req = { | |||
| 832 | .type = IPC_TYPE_WRITE, | |||
| 833 | .fd = fd, | |||
| 834 | .data_len = count, | |||
| 835 | }; | |||
| 836 | ||||
| 837 | resp_data = ipc_call(ipc_fd, &req, buf, &resp); | |||
| 838 | mock_dbg("connect IPC response status %d, data_len %u\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "connect IPC response status %d, data_len %u\n" , resp.status, resp.data_len); } while (0) | |||
| 839 | resp.status, resp.data_len)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "connect IPC response status %d, data_len %u\n" , resp.status, resp.data_len); } while (0); | |||
| 840 | ||||
| 841 | if (resp.status == -1) { | |||
| 842 | free(resp_data); | |||
| 843 | orig_close(ipc_fd); | |||
| 844 | errno(*__errno_location ()) = resp.errno_val; | |||
| 845 | return -1; | |||
| 846 | } | |||
| 847 | ||||
| 848 | if (resp_data && mock_fabrics_manager_fd >= 0) | |||
| 849 | orig_write(mock_fabrics_manager_fd, resp_data, resp.data_len); | |||
| 850 | free(resp_data); | |||
| 851 | orig_close(ipc_fd); | |||
| 852 | ||||
| 853 | return count; | |||
| 854 | } | |||
| 855 | ||||
| 856 | static int mock_ctrl_instance(int fd) | |||
| 857 | { | |||
| 858 | for (int i = 0; i < num_mock_ctrls; i++) | |||
| 859 | if (mock_ctrl_fds[i].fd == fd) | |||
| 860 | return mock_ctrl_fds[i].instance; | |||
| 861 | ||||
| 862 | return -1; | |||
| 863 | } | |||
| 864 | ||||
| 865 | static bool_Bool mock_ctrl_is_block(int fd, bool_Bool *is_block) | |||
| 866 | { | |||
| 867 | for (int i = 0; i < num_mock_ctrls; i++) { | |||
| 868 | if (mock_ctrl_fds[i].fd == fd) { | |||
| 869 | *is_block = mock_ctrl_fds[i].is_block; | |||
| 870 | return true1; | |||
| 871 | } | |||
| 872 | } | |||
| 873 | ||||
| 874 | return false0; | |||
| 875 | } | |||
| 876 | ||||
| 877 | /* | |||
| 878 | * Bits 1:0 of an opcode give the direction of its data transfer. Like | |||
| 879 | * the kernel, treat a bidirectional command as a write. | |||
| 880 | */ | |||
| 881 | static bool_Bool opcode_is_write(uint8_t opcode) | |||
| 882 | { | |||
| 883 | return opcode & 1; | |||
| 884 | } | |||
| 885 | ||||
| 886 | static int handle_passthru_ioctl(int instance, unsigned long request, | |||
| 887 | struct linux_passthru_cmd64 *cmd) | |||
| 888 | { | |||
| 889 | bool_Bool admin = request == LIBNVME_IOCTL_ADMIN_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x41)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) || | |||
| 890 | request == LIBNVME_IOCTL_ADMIN64_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x47)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8))); | |||
| 891 | bool_Bool write = opcode_is_write(cmd->opcode); | |||
| 892 | uint32_t data_len = 0, metadata_len = 0; | |||
| 893 | __cleanup_free__attribute__((cleanup(shr_freep))) void *payload = NULL((void*)0); | |||
| 894 | struct ipc_response resp; | |||
| 895 | void *resp_data; | |||
| 896 | int ipc_fd; | |||
| 897 | ||||
| 898 | mock_dbg("ioctl intercepted on instance %d (request 0x%lx)\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl intercepted on instance %d (request 0x%lx)\n" , instance, request); } while (0) | |||
| 899 | instance, request)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl intercepted on instance %d (request 0x%lx)\n" , instance, request); } while (0); | |||
| 900 | ||||
| 901 | ||||
| 902 | ipc_fd = connect_ipc_socket(); | |||
| 903 | if (ipc_fd < 0) { | |||
| 904 | mock_dbg("ioctl could not connect to IPC socket\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl could not connect to IPC socket\n" ); } while (0); | |||
| 905 | return -2; | |||
| 906 | } | |||
| 907 | ||||
| 908 | if (write && cmd->addr) | |||
| 909 | data_len = cmd->data_len; | |||
| 910 | if (write
| |||
| 911 | metadata_len = cmd->metadata_len; | |||
| 912 | if (data_len + metadata_len) { | |||
| 913 | payload = malloc(data_len + metadata_len); | |||
| 914 | if (!payload) { | |||
| 915 | orig_close(ipc_fd); | |||
| 916 | errno(*__errno_location ()) = ENOMEM12; | |||
| 917 | return -1; | |||
| 918 | } | |||
| 919 | if (data_len
| |||
| 920 | memcpy(payload, (void *)(uintptr_t)cmd->addr, data_len); | |||
| 921 | if (metadata_len
| |||
| 922 | memcpy((uint8_t *)payload + data_len, | |||
| 923 | (void *)(uintptr_t)cmd->metadata, metadata_len); | |||
| 924 | } | |||
| 925 | ||||
| 926 | struct ipc_request req = { | |||
| 927 | .type = IPC_TYPE_IOCTL, | |||
| 928 | .fd = instance, /* controller instance, not a real fd */ | |||
| 929 | .data_len = data_len + metadata_len, | |||
| 930 | .request = request, | |||
| 931 | .opcode = cmd->opcode, | |||
| 932 | .nsid = cmd->nsid, | |||
| 933 | .cdw10 = cmd->cdw10, | |||
| 934 | .cdw11 = cmd->cdw11, | |||
| 935 | .cdw12 = cmd->cdw12, | |||
| 936 | .cdw13 = cmd->cdw13, | |||
| 937 | .cdw14 = cmd->cdw14, | |||
| 938 | .cdw15 = cmd->cdw15, | |||
| 939 | .req_len = cmd->data_len, | |||
| 940 | .flags = cmd->flags, | |||
| 941 | .cmd_flags = (admin
| |||
| 942 | (data_len
| |||
| 943 | (metadata_len
| |||
| 944 | .cdw2 = cmd->cdw2, | |||
| 945 | .cdw3 = cmd->cdw3, | |||
| 946 | .metadata_len = cmd->metadata_len, | |||
| 947 | .timeout_ms = cmd->timeout_ms, | |||
| 948 | }; | |||
| 949 | ||||
| 950 | resp_data = ipc_call(ipc_fd, &req, payload, &resp); | |||
| ||||
| 951 | mock_dbg("ioctl received IPC response status %d, sc_status %d, result %u, data_len %u\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl received IPC response status %d, sc_status %d, result %u, data_len %u\n" , resp.status, resp.sc_status, resp.result, resp.data_len); } while (0) | |||
| 952 | resp.status, resp.sc_status, resp.result, resp.data_len)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl received IPC response status %d, sc_status %d, result %u, data_len %u\n" , resp.status, resp.sc_status, resp.result, resp.data_len); } while (0); | |||
| 953 | ||||
| 954 | if (resp.status == -1) { | |||
| 955 | free(resp_data); | |||
| 956 | orig_close(ipc_fd); | |||
| 957 | errno(*__errno_location ()) = resp.errno_val; | |||
| 958 | return -1; | |||
| 959 | } | |||
| 960 | ||||
| 961 | if (request == LIBNVME_IOCTL_ADMIN_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x41)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) || | |||
| 962 | request == LIBNVME_IOCTL_IO_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x43)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8)))) { | |||
| 963 | struct linux_passthru_cmd32 *cmd32 = | |||
| 964 | (struct linux_passthru_cmd32 *)cmd; | |||
| 965 | ||||
| 966 | cmd32->result = resp.result; | |||
| 967 | } else { | |||
| 968 | cmd->result = resp.result; | |||
| 969 | } | |||
| 970 | ||||
| 971 | /* | |||
| 972 | * like the kernel, which transfers no data without a buffer, and | |||
| 973 | * none back to the caller for a write | |||
| 974 | */ | |||
| 975 | if (resp_data && cmd->addr && !write) { | |||
| 976 | if (resp.data_len > cmd->data_len) { | |||
| 977 | mock_dbg("ioctl response data_len %u exceeds caller buffer %u, truncating\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl response data_len %u exceeds caller buffer %u, truncating\n" , resp.data_len, cmd->data_len); } while (0) | |||
| 978 | resp.data_len, cmd->data_len)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ioctl response data_len %u exceeds caller buffer %u, truncating\n" , resp.data_len, cmd->data_len); } while (0); | |||
| 979 | resp.data_len = cmd->data_len; | |||
| 980 | } | |||
| 981 | memcpy((void *)(uintptr_t)cmd->addr, resp_data, resp.data_len); | |||
| 982 | } | |||
| 983 | free(resp_data); | |||
| 984 | orig_close(ipc_fd); | |||
| 985 | ||||
| 986 | return resp.sc_status; | |||
| 987 | } | |||
| 988 | ||||
| 989 | /* | |||
| 990 | * Forward an ioctl() the shim doesn't decode itself. The argument bytes | |||
| 991 | * are sent as they are, in this process's byte order, and are copied | |||
| 992 | * back for an ioctl that returns data. The server answers an ioctl it | |||
| 993 | * doesn't handle with ENOTTY, like the /dev/null fallback used to. | |||
| 994 | */ | |||
| 995 | static int handle_raw_ioctl(int instance, unsigned long request, void *argp) | |||
| 996 | { | |||
| 997 | uint32_t size = _IOC_SIZE(request)(((request) >> ((0 +8)+8)) & ((1 << 14)-1)); | |||
| 998 | struct ipc_response resp; | |||
| 999 | void *resp_data; | |||
| 1000 | uint8_t dir = 0; | |||
| 1001 | int ipc_fd; | |||
| 1002 | ||||
| 1003 | if (_IOC_DIR(request)(((request) >> (((0 +8)+8)+14)) & ((1 << 2)-1 )) & _IOC_WRITE1U) | |||
| 1004 | dir |= IPC_IOC_WRITE; | |||
| 1005 | if (_IOC_DIR(request)(((request) >> (((0 +8)+8)+14)) & ((1 << 2)-1 )) & _IOC_READ2U) | |||
| 1006 | dir |= IPC_IOC_READ; | |||
| 1007 | if (!dir || !argp) | |||
| 1008 | size = 0; | |||
| 1009 | ||||
| 1010 | mock_dbg("raw ioctl intercepted on instance %d (request 0x%lx)\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "raw ioctl intercepted on instance %d (request 0x%lx)\n" , instance, request); } while (0) | |||
| 1011 | instance, request)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "raw ioctl intercepted on instance %d (request 0x%lx)\n" , instance, request); } while (0); | |||
| 1012 | ||||
| 1013 | ipc_fd = connect_ipc_socket(); | |||
| 1014 | if (ipc_fd < 0) { | |||
| 1015 | mock_dbg("raw ioctl could not connect to IPC socket\n")do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "raw ioctl could not connect to IPC socket\n" ); } while (0); | |||
| 1016 | return -2; | |||
| 1017 | } | |||
| 1018 | ||||
| 1019 | struct ipc_request req = { | |||
| 1020 | .type = IPC_TYPE_RAW_IOCTL, | |||
| 1021 | .fd = instance, /* controller instance, not a real fd */ | |||
| 1022 | .data_len = (dir & IPC_IOC_WRITE) ? size : 0, | |||
| 1023 | .request = request, | |||
| 1024 | .ioc_dir = dir, | |||
| 1025 | .ioc_type = _IOC_TYPE(request)(((request) >> (0 +8)) & ((1 << 8)-1)), | |||
| 1026 | .ioc_nr = _IOC_NR(request)(((request) >> 0) & ((1 << 8)-1)), | |||
| 1027 | .big_endian = __BYTE_ORDER1234 == __BIG_ENDIAN4321, | |||
| 1028 | .ioc_size = size, | |||
| 1029 | }; | |||
| 1030 | ||||
| 1031 | resp_data = ipc_call(ipc_fd, &req, argp, &resp); | |||
| 1032 | mock_dbg("raw ioctl received IPC response status %d, errno %d, sc_status %d, data_len %u\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "raw ioctl received IPC response status %d, errno %d, sc_status %d, data_len %u\n" , resp.status, resp.errno_val, resp.sc_status, resp.data_len) ; } while (0) | |||
| 1033 | resp.status, resp.errno_val, resp.sc_status, resp.data_len)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "raw ioctl received IPC response status %d, errno %d, sc_status %d, data_len %u\n" , resp.status, resp.errno_val, resp.sc_status, resp.data_len) ; } while (0); | |||
| 1034 | orig_close(ipc_fd); | |||
| 1035 | ||||
| 1036 | if (resp.status == -1) { | |||
| 1037 | free(resp_data); | |||
| 1038 | errno(*__errno_location ()) = resp.errno_val; | |||
| 1039 | return -1; | |||
| 1040 | } | |||
| 1041 | ||||
| 1042 | if (resp_data && dir & IPC_IOC_READ) | |||
| 1043 | memcpy(argp, resp_data, resp.data_len < size ? | |||
| 1044 | resp.data_len : size); | |||
| 1045 | free(resp_data); | |||
| 1046 | ||||
| 1047 | return resp.sc_status; | |||
| 1048 | } | |||
| 1049 | ||||
| 1050 | /* | |||
| 1051 | * Ask the IPC server for the h/w queues of the interface in an | |||
| 1052 | * ETHTOOL_GCHANNELS request. Any other ethtool command goes to the real | |||
| 1053 | * ioctl(). | |||
| 1054 | */ | |||
| 1055 | static int handle_ethtool_ioctl(struct ifreq *ifr) | |||
| 1056 | { | |||
| 1057 | struct ethtool_channels *chan = (void *)ifr->ifr_dataifr_ifru.ifru_data; | |||
| 1058 | struct ipc_response resp; | |||
| 1059 | uint32_t *counts; | |||
| 1060 | int ipc_fd; | |||
| 1061 | ||||
| 1062 | if (!chan || chan->cmd != ETHTOOL_GCHANNELS0x0000003c) | |||
| 1063 | return -2; | |||
| 1064 | ||||
| 1065 | ipc_fd = connect_ipc_socket(); | |||
| 1066 | if (ipc_fd < 0) | |||
| 1067 | return -2; | |||
| 1068 | ||||
| 1069 | mock_dbg("ETHTOOL_GCHANNELS intercepted on %s\n", ifr->ifr_name)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "ETHTOOL_GCHANNELS intercepted on %s\n" , ifr->ifr_ifrn.ifrn_name); } while (0); | |||
| 1070 | ||||
| 1071 | struct ipc_request req = { | |||
| 1072 | .type = IPC_TYPE_NIC_QUEUES, | |||
| 1073 | .data_len = strnlen(ifr->ifr_nameifr_ifrn.ifrn_name, sizeof(ifr->ifr_nameifr_ifrn.ifrn_name)), | |||
| 1074 | }; | |||
| 1075 | ||||
| 1076 | counts = ipc_call(ipc_fd, &req, ifr->ifr_nameifr_ifrn.ifrn_name, &resp); | |||
| 1077 | orig_close(ipc_fd); | |||
| 1078 | ||||
| 1079 | if (resp.status == -1) { | |||
| 1080 | free(counts); | |||
| 1081 | errno(*__errno_location ()) = resp.errno_val; | |||
| 1082 | return -1; | |||
| 1083 | } | |||
| 1084 | ||||
| 1085 | if (!counts || resp.data_len < 3 * sizeof(*counts)) { | |||
| 1086 | free(counts); | |||
| 1087 | errno(*__errno_location ()) = EIO5; | |||
| 1088 | return -1; | |||
| 1089 | } | |||
| 1090 | ||||
| 1091 | chan->combined_count = le32toh(counts[0])__uint32_identity (counts[0]); | |||
| 1092 | chan->tx_count = le32toh(counts[1])__uint32_identity (counts[1]); | |||
| 1093 | chan->rx_count = le32toh(counts[2])__uint32_identity (counts[2]); | |||
| 1094 | free(counts); | |||
| 1095 | ||||
| 1096 | return 0; | |||
| 1097 | } | |||
| 1098 | ||||
| 1099 | static int mock_ioctl(int fd, unsigned long request, void *argp, orig_ioctl_t fallback) | |||
| 1100 | { | |||
| 1101 | int instance = mock_ctrl_instance(fd); | |||
| 1102 | int ret; | |||
| 1103 | ||||
| 1104 | if (request == SIOCETHTOOL0x8946 && argp) { | |||
| 1105 | ret = handle_ethtool_ioctl(argp); | |||
| 1106 | if (ret != -2) | |||
| 1107 | return ret; | |||
| 1108 | } | |||
| 1109 | ||||
| 1110 | if (instance < 0) | |||
| 1111 | return fallback(fd, request, argp); | |||
| 1112 | ||||
| 1113 | if (request == LIBNVME_IOCTL_ADMIN_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x41)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) || | |||
| 1114 | request == LIBNVME_IOCTL_ADMIN64_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x47)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8))) || | |||
| 1115 | request == LIBNVME_IOCTL_IO_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x43)) << 0) | ((((sizeof(struct linux_passthru_cmd32 )))) << ((0 +8)+8))) || | |||
| 1116 | request == LIBNVME_IOCTL_IO64_CMD(((2U|1U) << (((0 +8)+8)+14)) | ((('N')) << (0 +8 )) | (((0x48)) << 0) | ((((sizeof(struct linux_passthru_cmd64 )))) << ((0 +8)+8)))) | |||
| 1117 | ret = handle_passthru_ioctl(instance, request, argp); | |||
| 1118 | else | |||
| 1119 | ret = handle_raw_ioctl(instance, request, argp); | |||
| 1120 | if (ret != -2) | |||
| 1121 | return ret; | |||
| 1122 | ||||
| 1123 | return fallback(fd, request, argp); | |||
| 1124 | } | |||
| 1125 | ||||
| 1126 | int ioctl(int fd, unsigned long request, ...) | |||
| 1127 | { | |||
| 1128 | va_list args; | |||
| 1129 | void *argp; | |||
| 1130 | ||||
| 1131 | init_orig_functions(); | |||
| 1132 | ||||
| 1133 | va_start(args, request)__builtin_va_start(args, request); | |||
| 1134 | argp = va_arg(args, void *)__builtin_va_arg(args, void *); | |||
| 1135 | va_end(args)__builtin_va_end(args); | |||
| 1136 | ||||
| 1137 | return mock_ioctl(fd, request, argp, orig_ioctl); | |||
| 1138 | } | |||
| 1139 | ||||
| 1140 | int __ioctl(int fd, unsigned long request, ...) | |||
| 1141 | { | |||
| 1142 | va_list args; | |||
| 1143 | void *argp; | |||
| 1144 | ||||
| 1145 | va_start(args, request)__builtin_va_start(args, request); | |||
| 1146 | argp = va_arg(args, void *)__builtin_va_arg(args, void *); | |||
| 1147 | va_end(args)__builtin_va_end(args); | |||
| 1148 | ||||
| 1149 | return ioctl(fd, request, argp); | |||
| 1150 | } | |||
| 1151 | ||||
| 1152 | int __ioctl_time64(int fd, unsigned long request, ...) | |||
| 1153 | { | |||
| 1154 | va_list args; | |||
| 1155 | void *argp; | |||
| 1156 | ||||
| 1157 | init_orig_functions(); | |||
| 1158 | ||||
| 1159 | va_start(args, request)__builtin_va_start(args, request); | |||
| 1160 | argp = va_arg(args, void *)__builtin_va_arg(args, void *); | |||
| 1161 | va_end(args)__builtin_va_end(args); | |||
| 1162 | ||||
| 1163 | return mock_ioctl(fd, request, argp, orig_ioctl_time64); | |||
| ||||
| 1164 | } | |||
| 1165 | ||||
| 1166 | int close(int fd) | |||
| 1167 | { | |||
| 1168 | init_orig_functions(); | |||
| 1169 | ||||
| 1170 | if (fd == mock_fabrics_fd) | |||
| 1171 | mock_fabrics_fd = -1; | |||
| 1172 | if (fd == mock_fabrics_manager_fd) | |||
| 1173 | mock_fabrics_manager_fd = -1; | |||
| 1174 | if (fd == mock_netlink_fd) { | |||
| 1175 | mock_netlink_fd = -1; | |||
| 1176 | mock_route_reply.pending = false0; | |||
| 1177 | } | |||
| 1178 | ||||
| 1179 | for (int i = 0; i < num_mock_ctrls; i++) { | |||
| 1180 | if (mock_ctrl_fds[i].fd != fd) | |||
| 1181 | continue; | |||
| 1182 | for (int j = i; j < num_mock_ctrls - 1; j++) | |||
| 1183 | mock_ctrl_fds[j] = mock_ctrl_fds[j + 1]; | |||
| 1184 | num_mock_ctrls--; | |||
| 1185 | break; | |||
| 1186 | } | |||
| 1187 | ||||
| 1188 | return orig_close(fd); | |||
| 1189 | } | |||
| 1190 | ||||
| 1191 | int fstat(int fd, struct stat *buf) | |||
| 1192 | { | |||
| 1193 | bool_Bool is_block; | |||
| 1194 | ||||
| 1195 | init_orig_functions(); | |||
| 1196 | ||||
| 1197 | if (mock_ctrl_is_block(fd, &is_block)) { | |||
| 1198 | memset(buf, 0, sizeof(*buf)); | |||
| 1199 | buf->st_mode = (is_block ? S_IFBLK0060000 : S_IFCHR0020000) | 0600; | |||
| 1200 | return 0; | |||
| 1201 | } | |||
| 1202 | ||||
| 1203 | return orig_fstat(fd, buf); | |||
| 1204 | } | |||
| 1205 | ||||
| 1206 | int fstat64(int fd, struct stat64 *buf) | |||
| 1207 | { | |||
| 1208 | bool_Bool is_block; | |||
| 1209 | ||||
| 1210 | init_orig_functions(); | |||
| 1211 | ||||
| 1212 | if (mock_ctrl_is_block(fd, &is_block)) { | |||
| 1213 | memset(buf, 0, sizeof(*buf)); | |||
| 1214 | buf->st_mode = (is_block ? S_IFBLK0060000 : S_IFCHR0020000) | 0600; | |||
| 1215 | return 0; | |||
| 1216 | } | |||
| 1217 | ||||
| 1218 | return orig_fstat64(fd, buf); | |||
| 1219 | } | |||
| 1220 | ||||
| 1221 | int __fstat64_time64(int fd, void *buf) | |||
| 1222 | { | |||
| 1223 | bool_Bool is_block; | |||
| 1224 | int ret; | |||
| 1225 | ||||
| 1226 | init_orig_functions(); | |||
| 1227 | ||||
| 1228 | /* | |||
| 1229 | * buf's real layout is unknown here, so unlike fstat()/fstat64() this | |||
| 1230 | * can't memset() the whole thing and fabricate it from scratch. Let the | |||
| 1231 | * real call fill in a correctly-sized, correctly-shaped struct, then | |||
| 1232 | * only patch st_mode -- its offset is unaffected by the time64 | |||
| 1233 | * transition. | |||
| 1234 | */ | |||
| 1235 | ret = orig_fstat64_time64(fd, buf); | |||
| 1236 | if (ret == 0 && mock_ctrl_is_block(fd, &is_block)) | |||
| 1237 | ((struct stat *)buf)->st_mode = (is_block ? S_IFBLK0060000 : S_IFCHR0020000) | 0600; | |||
| 1238 | ||||
| 1239 | return ret; | |||
| 1240 | } | |||
| 1241 | ||||
| 1242 | /* | |||
| 1243 | * Ask the IPC server for the answer to a password prompt, instead of | |||
| 1244 | * reading the terminal. Without an IPC server, fall back to the real | |||
| 1245 | * getpass(). | |||
| 1246 | */ | |||
| 1247 | char *getpass(const char *prompt) | |||
| 1248 | { | |||
| 1249 | static char pass[128]; | |||
| 1250 | struct ipc_response resp; | |||
| 1251 | char *resp_data; | |||
| 1252 | size_t len; | |||
| 1253 | int ipc_fd; | |||
| 1254 | ||||
| 1255 | init_orig_functions(); | |||
| 1256 | ||||
| 1257 | ipc_fd = connect_ipc_socket(); | |||
| 1258 | if (ipc_fd < 0) | |||
| 1259 | return orig_getpass(prompt); | |||
| 1260 | ||||
| 1261 | struct ipc_request req = { | |||
| 1262 | .type = IPC_TYPE_GETPASS, | |||
| 1263 | .data_len = strlen(prompt), | |||
| 1264 | }; | |||
| 1265 | ||||
| 1266 | resp_data = ipc_call(ipc_fd, &req, prompt, &resp); | |||
| 1267 | orig_close(ipc_fd); | |||
| 1268 | ||||
| 1269 | if (resp.status == -1) { | |||
| 1270 | free(resp_data); | |||
| 1271 | errno(*__errno_location ()) = resp.errno_val; | |||
| 1272 | return NULL((void*)0); | |||
| 1273 | } | |||
| 1274 | ||||
| 1275 | len = resp.data_len < sizeof(pass) - 1 ? | |||
| 1276 | resp.data_len : sizeof(pass) - 1; | |||
| 1277 | if (resp_data) | |||
| 1278 | memcpy(pass, resp_data, len); | |||
| 1279 | pass[len] = '\0'; | |||
| 1280 | free(resp_data); | |||
| 1281 | ||||
| 1282 | return pass; | |||
| 1283 | } | |||
| 1284 | ||||
| 1285 | /* | |||
| 1286 | * Fake the NETLINK_ROUTE socket the egress route lookup uses. The fd is | |||
| 1287 | * a real (unconnected) socket, so close() and friends still work, but | |||
| 1288 | * sendmsg()/recvmsg() on it never reach the kernel. | |||
| 1289 | */ | |||
| 1290 | int socket(int domain, int type, int protocol) | |||
| 1291 | { | |||
| 1292 | int fd; | |||
| 1293 | ||||
| 1294 | init_orig_functions(); | |||
| 1295 | ||||
| 1296 | if (domain != AF_NETLINK16 || protocol != NETLINK_ROUTE0 || | |||
| 1297 | !getenv("MOCK_IPC_SOCK")) | |||
| 1298 | return orig_socket(domain, type, protocol); | |||
| 1299 | ||||
| 1300 | fd = orig_socket(AF_UNIX1, SOCK_DGRAMSOCK_DGRAM, 0); | |||
| 1301 | if (fd < 0) | |||
| 1302 | return fd; | |||
| 1303 | ||||
| 1304 | mock_netlink_fd = fd; | |||
| 1305 | mock_route_reply.pending = false0; | |||
| 1306 | mock_dbg("intercepted NETLINK_ROUTE socket (mapped to fd %d)\n", fd)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "intercepted NETLINK_ROUTE socket (mapped to fd %d)\n" , fd); } while (0); | |||
| 1307 | ||||
| 1308 | return fd; | |||
| 1309 | } | |||
| 1310 | ||||
| 1311 | static void rtattr_addr_to_str(struct rtattr *rta, unsigned char family, | |||
| 1312 | char *buf, size_t len) | |||
| 1313 | { | |||
| 1314 | size_t alen = family == AF_INET2 ? sizeof(struct in_addr) : | |||
| 1315 | sizeof(struct in6_addr); | |||
| 1316 | ||||
| 1317 | if (RTA_PAYLOAD(rta)((int)((rta)->rta_len) - (( ((sizeof(struct rtattr))+4U -1 ) & ~(4U -1) ) + (0))) < alen || | |||
| 1318 | !inet_ntop(family, RTA_DATA(rta)((void*)(((char*)(rta)) + (( ((sizeof(struct rtattr))+4U -1) & ~(4U -1) ) + (0)))), buf, len)) | |||
| 1319 | buf[0] = '\0'; | |||
| 1320 | } | |||
| 1321 | ||||
| 1322 | /* | |||
| 1323 | * Decode an RTM_GETROUTE request and ask the IPC server for the egress | |||
| 1324 | * interface. Its answer is returned by the next recvmsg(). | |||
| 1325 | */ | |||
| 1326 | static ssize_t mock_route_sendmsg(const struct msghdr *msg) | |||
| 1327 | { | |||
| 1328 | char daddr[INET6_ADDRSTRLEN46] = "", saddr[INET6_ADDRSTRLEN46] = ""; | |||
| 1329 | char payload[2 * INET6_ADDRSTRLEN46]; | |||
| 1330 | struct ipc_response resp; | |||
| 1331 | struct nlmsghdr *nlh; | |||
| 1332 | struct rtmsg *rtm; | |||
| 1333 | struct rtattr *rta; | |||
| 1334 | char *ifname; | |||
| 1335 | int attrlen, ipc_fd; | |||
| 1336 | size_t len; | |||
| 1337 | ||||
| 1338 | if (!msg->msg_iovlen || | |||
| 1339 | msg->msg_iov[0].iov_len < NLMSG_LENGTH(sizeof(*rtm))((sizeof(*rtm)) + ((int) ( ((sizeof(struct nlmsghdr))+4U -1) & ~(4U -1) )))) { | |||
| 1340 | errno(*__errno_location ()) = EINVAL22; | |||
| 1341 | return -1; | |||
| 1342 | } | |||
| 1343 | ||||
| 1344 | nlh = msg->msg_iov[0].iov_base; | |||
| 1345 | if (nlh->nlmsg_type != RTM_GETROUTERTM_GETROUTE || | |||
| 1346 | nlh->nlmsg_len > msg->msg_iov[0].iov_len) { | |||
| 1347 | errno(*__errno_location ()) = EOPNOTSUPP95; | |||
| 1348 | return -1; | |||
| 1349 | } | |||
| 1350 | ||||
| 1351 | rtm = NLMSG_DATA(nlh)((void *)(((char *)nlh) + ((int) ( ((sizeof(struct nlmsghdr)) +4U -1) & ~(4U -1) )))); | |||
| 1352 | rta = RTM_RTA(rtm)((struct rtattr*)(((char*)(rtm)) + ( ((sizeof(struct rtmsg))+ 4U -1) & ~(4U -1) ))); | |||
| 1353 | attrlen = RTM_PAYLOAD(nlh)((nlh)->nlmsg_len - ( (((((sizeof(struct rtmsg))) + ((int) ( ((sizeof(struct nlmsghdr))+4U -1) & ~(4U -1) ))))+4U - 1) & ~(4U -1) )); | |||
| 1354 | for (; RTA_OK(rta, attrlen)((attrlen) >= (int)sizeof(struct rtattr) && (rta)-> rta_len >= sizeof(struct rtattr) && (rta)->rta_len <= (attrlen)); rta = RTA_NEXT(rta, attrlen)((attrlen) -= ( (((rta)->rta_len)+4U -1) & ~(4U -1) ), (struct rtattr*)(((char*)(rta)) + ( (((rta)->rta_len)+4U - 1) & ~(4U -1) )))) { | |||
| 1355 | if (rta->rta_type == RTA_DST) | |||
| 1356 | rtattr_addr_to_str(rta, rtm->rtm_family, daddr, | |||
| 1357 | sizeof(daddr)); | |||
| 1358 | else if (rta->rta_type == RTA_SRC) | |||
| 1359 | rtattr_addr_to_str(rta, rtm->rtm_family, saddr, | |||
| 1360 | sizeof(saddr)); | |||
| 1361 | } | |||
| 1362 | ||||
| 1363 | mock_dbg("route lookup intercepted, daddr '%s', saddr '%s'\n",do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "route lookup intercepted, daddr '%s', saddr '%s'\n" , daddr, saddr); } while (0) | |||
| 1364 | daddr, saddr)do { if (mock_debug) fprintf(stderr, "[MOCK_DEBUG] " "route lookup intercepted, daddr '%s', saddr '%s'\n" , daddr, saddr); } while (0); | |||
| 1365 | ||||
| 1366 | mock_route_reply.req = *nlh; | |||
| 1367 | mock_route_reply.family = rtm->rtm_family; | |||
| 1368 | mock_route_reply.error = 0; | |||
| 1369 | mock_route_reply.pending = true1; | |||
| 1370 | ||||
| 1371 | ipc_fd = connect_ipc_socket(); | |||
| 1372 | if (ipc_fd < 0) { | |||
| 1373 | mock_route_reply.error = EIO5; | |||
| 1374 | return nlh->nlmsg_len; | |||
| 1375 | } | |||
| 1376 | ||||
| 1377 | len = strlen(daddr) + 1; | |||
| 1378 | memcpy(payload, daddr, len); | |||
| 1379 | memcpy(payload + len, saddr, strlen(saddr)); | |||
| 1380 | len += strlen(saddr); | |||
| 1381 | ||||
| 1382 | struct ipc_request req = { | |||
| 1383 | .type = IPC_TYPE_ROUTE, | |||
| 1384 | .data_len = len, | |||
| 1385 | }; | |||
| 1386 | ||||
| 1387 | ifname = ipc_call(ipc_fd, &req, payload, &resp); | |||
| 1388 | orig_close(ipc_fd); | |||
| 1389 | ||||
| 1390 | if (resp.status == -1) { | |||
| 1391 | mock_route_reply.error = resp.errno_val; | |||
| 1392 | } else if (!ifname || !resp.data_len || | |||
| 1393 | resp.data_len >= sizeof(mock_ifname)) { | |||
| 1394 | mock_route_reply.error = EIO5; | |||
| 1395 | } else { | |||
| 1396 | memcpy(mock_ifname, ifname, resp.data_len); | |||
| 1397 | mock_ifname[resp.data_len] = '\0'; | |||
| 1398 | } | |||
| 1399 | free(ifname); | |||
| 1400 | ||||
| 1401 | return nlh->nlmsg_len; | |||
| 1402 | } | |||
| 1403 | ||||
| 1404 | /* | |||
| 1405 | * Build the kernel's answer to the pending RTM_GETROUTE request: an | |||
| 1406 | * RTM_NEWROUTE with the egress interface in RTA_OIF, or an NLMSG_ERROR. | |||
| 1407 | */ | |||
| 1408 | static ssize_t mock_route_recvmsg(struct msghdr *msg) | |||
| 1409 | { | |||
| 1410 | struct nlmsghdr *nlh; | |||
| 1411 | size_t len; | |||
| 1412 | ||||
| 1413 | if (!mock_route_reply.pending) { | |||
| 1414 | errno(*__errno_location ()) = EAGAIN11; | |||
| 1415 | return -1; | |||
| 1416 | } | |||
| 1417 | mock_route_reply.pending = false0; | |||
| 1418 | ||||
| 1419 | if (mock_route_reply.error) | |||
| 1420 | len = NLMSG_LENGTH(sizeof(struct nlmsgerr))((sizeof(struct nlmsgerr)) + ((int) ( ((sizeof(struct nlmsghdr ))+4U -1) & ~(4U -1) ))); | |||
| 1421 | else | |||
| 1422 | len = NLMSG_LENGTH(sizeof(struct rtmsg))((sizeof(struct rtmsg)) + ((int) ( ((sizeof(struct nlmsghdr)) +4U -1) & ~(4U -1) ))) + | |||
| 1423 | RTA_SPACE(sizeof(int))( (((( ((sizeof(struct rtattr))+4U -1) & ~(4U -1) ) + (sizeof (int))))+4U -1) & ~(4U -1) ); | |||
| 1424 | ||||
| 1425 | if (!msg->msg_iovlen || msg->msg_iov[0].iov_len < len) { | |||
| 1426 | errno(*__errno_location ()) = ENOBUFS105; | |||
| 1427 | return -1; | |||
| 1428 | } | |||
| 1429 | ||||
| 1430 | nlh = msg->msg_iov[0].iov_base; | |||
| 1431 | memset(nlh, 0, len); | |||
| 1432 | nlh->nlmsg_len = len; | |||
| 1433 | nlh->nlmsg_seq = mock_route_reply.req.nlmsg_seq; | |||
| 1434 | ||||
| 1435 | if (mock_route_reply.error) { | |||
| 1436 | struct nlmsgerr *err = NLMSG_DATA(nlh)((void *)(((char *)nlh) + ((int) ( ((sizeof(struct nlmsghdr)) +4U -1) & ~(4U -1) )))); | |||
| 1437 | ||||
| 1438 | nlh->nlmsg_type = NLMSG_ERROR0x2; | |||
| 1439 | err->error = -mock_route_reply.error; | |||
| 1440 | err->msg = mock_route_reply.req; | |||
| 1441 | } else { | |||
| 1442 | struct rtmsg *rtm = NLMSG_DATA(nlh)((void *)(((char *)nlh) + ((int) ( ((sizeof(struct nlmsghdr)) +4U -1) & ~(4U -1) )))); | |||
| 1443 | struct rtattr *rta = RTM_RTA(rtm)((struct rtattr*)(((char*)(rtm)) + ( ((sizeof(struct rtmsg))+ 4U -1) & ~(4U -1) ))); | |||
| 1444 | int ifindex = MOCK_IFINDEX0x4d4f434b; | |||
| 1445 | ||||
| 1446 | nlh->nlmsg_type = RTM_NEWROUTERTM_NEWROUTE; | |||
| 1447 | rtm->rtm_family = mock_route_reply.family; | |||
| 1448 | rta->rta_type = RTA_OIF; | |||
| 1449 | rta->rta_len = RTA_LENGTH(sizeof(ifindex))(( ((sizeof(struct rtattr))+4U -1) & ~(4U -1) ) + (sizeof (ifindex))); | |||
| 1450 | memcpy(RTA_DATA(rta)((void*)(((char*)(rta)) + (( ((sizeof(struct rtattr))+4U -1) & ~(4U -1) ) + (0)))), &ifindex, sizeof(ifindex)); | |||
| 1451 | } | |||
| 1452 | ||||
| 1453 | return len; | |||
| 1454 | } | |||
| 1455 | ||||
| 1456 | ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) | |||
| 1457 | { | |||
| 1458 | init_orig_functions(); | |||
| 1459 | ||||
| 1460 | if (fd == mock_netlink_fd) | |||
| 1461 | return mock_route_sendmsg(msg); | |||
| 1462 | ||||
| 1463 | return orig_sendmsg(fd, msg, flags); | |||
| 1464 | } | |||
| 1465 | ||||
| 1466 | ssize_t __sendmsg64(int fd, const struct msghdr *msg, int flags) | |||
| 1467 | { | |||
| 1468 | init_orig_functions(); | |||
| 1469 | ||||
| 1470 | if (fd == mock_netlink_fd) | |||
| 1471 | return mock_route_sendmsg(msg); | |||
| 1472 | ||||
| 1473 | return orig_sendmsg64(fd, msg, flags); | |||
| 1474 | } | |||
| 1475 | ||||
| 1476 | ssize_t recvmsg(int fd, struct msghdr *msg, int flags) | |||
| 1477 | { | |||
| 1478 | init_orig_functions(); | |||
| 1479 | ||||
| 1480 | if (fd == mock_netlink_fd) | |||
| 1481 | return mock_route_recvmsg(msg); | |||
| 1482 | ||||
| 1483 | return orig_recvmsg(fd, msg, flags); | |||
| 1484 | } | |||
| 1485 | ||||
| 1486 | ssize_t __recvmsg64(int fd, struct msghdr *msg, int flags) | |||
| 1487 | { | |||
| 1488 | init_orig_functions(); | |||
| 1489 | ||||
| 1490 | if (fd == mock_netlink_fd) | |||
| 1491 | return mock_route_recvmsg(msg); | |||
| 1492 | ||||
| 1493 | return orig_recvmsg64(fd, msg, flags); | |||
| 1494 | } | |||
| 1495 | ||||
| 1496 | char *if_indextoname(unsigned int ifindex, char ifname[IF_NAMESIZE16]) | |||
| 1497 | { | |||
| 1498 | init_orig_functions(); | |||
| 1499 | ||||
| 1500 | if (ifindex != MOCK_IFINDEX0x4d4f434b || !mock_ifname[0]) | |||
| 1501 | return orig_if_indextoname(ifindex, ifname); | |||
| 1502 | ||||
| 1503 | memcpy(ifname, mock_ifname, sizeof(mock_ifname)); | |||
| 1504 | ||||
| 1505 | return ifname; | |||
| 1506 | } | |||
| 1507 | ||||
| 1508 | /* | |||
| 1509 | * Ask the IPC server for the number of online CPUs. Without an IPC | |||
| 1510 | * server, or if it doesn't set one, fall back to the real get_nprocs(). | |||
| 1511 | */ | |||
| 1512 | int get_nprocs(void) | |||
| 1513 | { | |||
| 1514 | struct ipc_response resp; | |||
| 1515 | void *resp_data; | |||
| 1516 | int ipc_fd; | |||
| 1517 | ||||
| 1518 | init_orig_functions(); | |||
| 1519 | ||||
| 1520 | ipc_fd = connect_ipc_socket(); | |||
| 1521 | if (ipc_fd < 0) | |||
| 1522 | return orig_get_nprocs(); | |||
| 1523 | ||||
| 1524 | struct ipc_request req = { | |||
| 1525 | .type = IPC_TYPE_NPROCS, | |||
| 1526 | }; | |||
| 1527 | ||||
| 1528 | resp_data = ipc_call(ipc_fd, &req, NULL((void*)0), &resp); | |||
| 1529 | orig_close(ipc_fd); | |||
| 1530 | free(resp_data); | |||
| 1531 | ||||
| 1532 | if (resp.status == -1) | |||
| 1533 | return orig_get_nprocs(); | |||
| 1534 | ||||
| 1535 | return resp.result; | |||
| 1536 | } |