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
amdzen_open_device(const char * driver,const char * arg,int oflag)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