1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2026 Oxide Computer Company 14 */ 15 16 /* 17 * Utility functions common to amdzen commands. 18 */ 19 20 #include <err.h> 21 #include <errno.h> 22 #include <fcntl.h> 23 #include <libdevinfo.h> 24 #include <limits.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <unistd.h> 29 #include <sys/types.h> 30 #include <sys/stat.h> 31 32 #include "amdzen_common.h" 33 34 /* 35 * Traditionally we expected to be given the path to a character device file 36 * somewhere under /devices, where the device minor referred to a specific 37 * data fabric (DF) instance. It is often more convenient to be able to give 38 * a higher-level identifier instead, such as a DF number, optionally 39 * prefixed with the driver name. This function distinguishes between the 40 * two cases by attempting to parse the argument as a DF identifier. If that 41 * succeeds, we map it to the corresponding minor of the named driver and 42 * open that, otherwise the argument is treated as a device path and opened 43 * directly. Either way the caller receives an open file descriptor, or -1 44 * after a warning has been issued. 45 */ 46 int 47 amdzen_open_device(const char *driver, const char *arg, int oflag) 48 { 49 struct stat st; 50 di_node_t root, node; 51 di_minor_t minor; 52 const char *errstr, *id; 53 char mname[64]; 54 char *bpath, *path; 55 size_t drvlen; 56 int df, fd; 57 58 /* If there is a driver prefix provided, accept and remove it */ 59 id = arg; 60 drvlen = strlen(driver); 61 if (strncmp(id, driver, drvlen) == 0) 62 id += drvlen; 63 64 df = (int)strtonumx(id, 0, INT_MAX, &errstr, 0); 65 if (errstr != NULL) { 66 /* Not a DF identifier, so treat it as a device path */ 67 if ((fd = open(arg, oflag)) < 0) { 68 warn("failed to open %s", arg); 69 return (-1); 70 } 71 if (fstat(fd, &st) != 0) { 72 warn("failed to stat %s", arg); 73 (void) close(fd); 74 return (-1); 75 } 76 if (!S_ISCHR(st.st_mode)) { 77 warnx("%s is not a character device", arg); 78 (void) close(fd); 79 return (-1); 80 } 81 return (fd); 82 } 83 84 root = di_init_driver(driver, DINFOSUBTREE | DINFOMINOR); 85 if (root == DI_NODE_NIL) { 86 warn("failed to take a devinfo snapshot with %s attached", 87 driver); 88 return (-1); 89 } 90 91 /* These are all single-instance drivers with one minor per DF */ 92 node = di_drv_first_node(driver, root); 93 if (node == DI_NODE_NIL) { 94 warnx("failed to find a devinfo node for %s", driver); 95 di_fini(root); 96 return (-1); 97 } 98 99 (void) snprintf(mname, sizeof (mname), "%s.%d", driver, df); 100 minor = DI_MINOR_NIL; 101 while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) { 102 if (strcmp(di_minor_name(minor), mname) == 0) 103 break; 104 } 105 if (minor == DI_MINOR_NIL) { 106 warnx("failed to find minor %s on %s%d -- no such DF?", 107 mname, di_driver_name(node), di_instance(node)); 108 di_fini(root); 109 return (-1); 110 } 111 112 bpath = di_devfs_minor_path(minor); 113 if (bpath == NULL) { 114 warn("failed to get minor path for %s%d:%s", 115 di_driver_name(node), di_instance(node), 116 di_minor_name(minor)); 117 di_fini(root); 118 return (-1); 119 } 120 if (asprintf(&path, "/devices%s", bpath) < 0) { 121 warn("failed to construct full path for %s", bpath); 122 di_devfs_path_free(bpath); 123 di_fini(root); 124 return (-1); 125 } 126 di_devfs_path_free(bpath); 127 di_fini(root); 128 129 if ((fd = open(path, oflag)) < 0) 130 warn("failed to open %s", path); 131 free(path); 132 133 return (fd); 134 } 135