1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 23 /* 24 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 * Copyright 2017 Nexenta Systems, Inc. 27 */ 28 29 #include <unistd.h> 30 #include <stdlib.h> 31 #include <errno.h> 32 #include <ftw.h> 33 #include <string.h> 34 #include <thread.h> 35 #include <synch.h> 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <fcntl.h> 39 #include <sys/modctl.h> 40 #include <strings.h> 41 42 #include <libdevinfo.h> 43 #include "libdevid.h" 44 45 /* 46 * Get Device Id from an open file descriptor 47 */ 48 int 49 devid_get(int fd, ddi_devid_t *devidp) 50 { 51 int len = 0; 52 dev_t dev; 53 struct stat statb; 54 ddi_devid_t mydevid; 55 56 if (fstat(fd, &statb) != 0) 57 return (-1); 58 59 /* If not char or block device, then error */ 60 if (!S_ISCHR(statb.st_mode) && !S_ISBLK(statb.st_mode)) 61 return (-1); 62 63 /* Get the device id size */ 64 dev = statb.st_rdev; 65 if (modctl(MODSIZEOF_DEVID, dev, &len) != 0) 66 return (-1); 67 68 /* Allocate space to return device id */ 69 if ((mydevid = (ddi_devid_t)malloc(len)) == NULL) 70 return (-1); 71 72 /* Get the device id */ 73 if (modctl(MODGETDEVID, dev, len, mydevid) != 0) { 74 free(mydevid); 75 return (-1); 76 } 77 78 /* Return the device id copy */ 79 *devidp = mydevid; 80 return (0); 81 } 82 83 /* 84 * Get the minor name 85 */ 86 int 87 devid_get_minor_name(int fd, char **minor_namep) 88 { 89 int len = 0; 90 dev_t dev; 91 int spectype; 92 char *myminor_name; 93 struct stat statb; 94 95 if (fstat(fd, &statb) != 0) 96 return (-1); 97 98 /* If not a char or block device, then return an error */ 99 if (!S_ISCHR(statb.st_mode) && !S_ISBLK(statb.st_mode)) 100 return (-1); 101 102 spectype = statb.st_mode & S_IFMT; 103 dev = statb.st_rdev; 104 105 /* Get the minor name size */ 106 if (modctl(MODSIZEOF_MINORNAME, dev, spectype, &len) != 0) 107 return (-1); 108 109 /* Allocate space for the minor name */ 110 if ((myminor_name = (char *)malloc(len)) == NULL) 111 return (-1); 112 113 /* Get the minor name */ 114 if (modctl(MODGETMINORNAME, dev, spectype, len, myminor_name) != 0) { 115 free(myminor_name); 116 return (-1); 117 } 118 119 /* return the minor name copy */ 120 *minor_namep = myminor_name; 121 return (0); 122 } 123 124 char * 125 devid_str_from_path(const char *path) 126 { 127 int fd; 128 ddi_devid_t devid; 129 char *minor, *ret = NULL; 130 131 if ((fd = open(path, O_RDONLY)) < 0) 132 return (NULL); 133 134 if (devid_get(fd, &devid) == 0) { 135 if (devid_get_minor_name(fd, &minor) != 0) 136 minor = NULL; 137 ret = devid_str_encode(devid, minor); 138 if (minor != NULL) 139 devid_str_free(minor); 140 devid_free(devid); 141 } 142 (void) close(fd); 143 144 return (ret); 145 } 146 147 /* list element of devid_nmlist_t information */ 148 struct nmlist { 149 struct nmlist *nl_next; 150 char *nl_devname; 151 dev_t nl_dev; 152 }; 153 154 /* add list element to end of nmlist headed by *nlhp */ 155 struct nmlist * 156 nmlist_add(struct nmlist **nlhp, char *path) 157 { 158 struct stat statb; 159 dev_t dev; 160 struct nmlist *nl; 161 162 /* stat and get the devt for char or block */ 163 if ((stat(path, &statb) == 0) && 164 (S_ISCHR(statb.st_mode) || S_ISBLK(statb.st_mode))) 165 dev = statb.st_rdev; 166 else 167 dev = NODEV; 168 169 /* find the end of the list */ 170 for (; (nl = *nlhp) != NULL; nlhp = &nl->nl_next) 171 ; 172 173 /* allocate and initialize new entry */ 174 if ((nl = malloc(sizeof (*nl))) == NULL) 175 return (NULL); 176 177 if ((nl->nl_devname = strdup(path)) == NULL) { 178 free(nl); 179 return (NULL); 180 } 181 nl->nl_next = NULL; 182 nl->nl_dev = dev; 183 184 /* link new entry at end */ 185 *nlhp = nl; 186 return (nl); 187 } 188 189 /* information needed by devlink_callback to call nmlist_add */ 190 struct devlink_cbinfo { 191 struct nmlist **cbi_nlhp; 192 char *cbi_search_path; 193 int cbi_error; 194 }; 195 196 /* di_devlink callback to add a /dev entry to nmlist */ 197 static int 198 devlink_callback(di_devlink_t dl, void *arg) 199 { 200 struct devlink_cbinfo *cbip = (struct devlink_cbinfo *)arg; 201 char *devpath = (char *)di_devlink_path(dl); 202 203 if (strncmp(devpath, cbip->cbi_search_path, 204 strlen(cbip->cbi_search_path)) == 0) { 205 if (nmlist_add(cbip->cbi_nlhp, devpath) == NULL) { 206 cbip->cbi_error = 1; 207 return (DI_WALK_TERMINATE); 208 } 209 } 210 return (DI_WALK_CONTINUE); 211 } 212 213 /* 214 * Resolve /dev names to DI_PRIMARY_LINK, DI_SECONDARY_LINK, or both. 215 * The default is to resolve to just the DI_PRIMARY_LINK. 216 */ 217 int devid_deviceid_to_nmlist_link = DI_PRIMARY_LINK; 218 219 /* 220 * Options for the devid_deviceid_to_nmlist implementation: 221 * 222 * DEVICEID_NMLIST_SLINK - reduce overhead by reuse the previous 223 * di_devlink_init. 224 */ 225 #define DEVICEID_NMLIST_SLINK 1 226 int devid_deviceid_to_nmlist_flg = 0; 227 static di_devlink_handle_t devid_deviceid_to_nmlist_dlh = NULL; /* SLINK */ 228 229 #define DEVICEID_NMLIST_NRETRY 10 230 231 /* 232 * Convert the specified devid/minor_name into a devid_nmlist_t array 233 * with names that resolve into /devices or /dev depending on search_path. 234 * 235 * The man page indicates that: 236 * 237 * This function traverses the file tree, starting at search_path. 238 * 239 * This is not true, we reverse engineer the paths relative to 240 * the specified search path to avoid attaching all devices. 241 */ 242 int 243 devid_deviceid_to_nmlist(char *search_path, ddi_devid_t devid, char *minor_name, 244 devid_nmlist_t **retlist) 245 { 246 char *cp; 247 int dev; 248 char *paths = NULL; 249 char *path; 250 int lens; 251 di_devlink_handle_t dlh = NULL; 252 int ret = -1; 253 struct devlink_cbinfo cbi; 254 struct nmlist *nlh = NULL; 255 struct nmlist *nl; 256 devid_nmlist_t *rl; 257 int nret; 258 int nagain = 0; 259 int err = 0; 260 261 *retlist = NULL; 262 263 /* verify valid search path starts with "/devices" or "/dev" */ 264 if ((strcmp(search_path, "/devices") == 0) || 265 (strncmp(search_path, "/devices/", 9) == 0)) 266 dev = 0; 267 else if ((strcmp(search_path, "/dev") == 0) || 268 (strncmp(search_path, "/dev/", 5) == 0)) 269 dev = 1; 270 else { 271 errno = EINVAL; 272 return (-1); 273 } 274 275 276 /* translate devid/minor_name to /devices paths */ 277 again: if (modctl(MODDEVID2PATHS, devid, minor_name, 0, &lens, NULL) != 0) 278 goto out; 279 if ((paths = (char *)malloc(lens)) == NULL) 280 goto out; 281 if (modctl(MODDEVID2PATHS, devid, minor_name, 0, &lens, paths) != 0) { 282 if ((errno == EAGAIN) && (nagain++ < DEVICEID_NMLIST_NRETRY)) { 283 free(paths); 284 paths = NULL; 285 goto again; 286 } 287 goto out; 288 } 289 290 /* 291 * initialize for /devices path to /dev path translation. To reduce 292 * overhead we reuse the last snapshot if DEVICEID_NMLIST_SLINK is set. 293 */ 294 if (dev) { 295 dlh = devid_deviceid_to_nmlist_dlh; 296 if (dlh && 297 !(devid_deviceid_to_nmlist_flg & DEVICEID_NMLIST_SLINK)) { 298 (void) di_devlink_fini(&dlh); 299 dlh = devid_deviceid_to_nmlist_dlh = NULL; 300 } 301 if ((dlh == NULL) && 302 ((dlh = di_devlink_init(NULL, 0)) == NULL)) 303 goto out; 304 } 305 306 /* 307 * iterate over all the devtspectype resolutions of the devid and 308 * convert them into the appropriate path form and add items to return 309 * to the nmlist list; 310 */ 311 for (path = paths; *path; path += strlen(path) + 1) { 312 if (dev) { 313 /* add /dev entries */ 314 cbi.cbi_nlhp = &nlh; 315 cbi.cbi_search_path = search_path; 316 cbi.cbi_error = 0; 317 318 (void) di_devlink_walk(dlh, NULL, path, 319 devid_deviceid_to_nmlist_link, 320 (void *)&cbi, devlink_callback); 321 if (cbi.cbi_error) 322 goto out; 323 } else { 324 /* add /devices entry */ 325 cp = malloc(strlen("/devices") + strlen(path) + 1); 326 (void) strcpy(cp, "/devices"); 327 (void) strcat(cp, path); 328 if (strncmp(cp, search_path, 329 strlen(search_path)) == 0) { 330 if (nmlist_add(&nlh, cp) == NULL) { 331 free(cp); 332 goto out; 333 } 334 } 335 free(cp); 336 } 337 } 338 339 /* convert from nmlist to retlist array */ 340 for (nl = nlh, nret = 0; nl; nl = nl->nl_next) 341 nret++; 342 if (nret == 0) { 343 err = ENODEV; 344 goto out; 345 } 346 if ((*retlist = calloc(nret + 1, sizeof (devid_nmlist_t))) == NULL) { 347 err = ENOMEM; 348 goto out; 349 } 350 for (nl = nlh, rl = *retlist; nl; nl = nl->nl_next, rl++) { 351 rl->devname = nl->nl_devname; 352 rl->dev = nl->nl_dev; 353 } 354 rl->devname = NULL; 355 rl->dev = NODEV; 356 357 ret = 0; 358 359 out: 360 while ((nl = nlh) != NULL) { /* free the nmlist */ 361 nlh = nl->nl_next; 362 free(nl); 363 } 364 if (paths) 365 free(paths); 366 if (dlh) { 367 if ((ret == 0) && 368 (devid_deviceid_to_nmlist_flg & DEVICEID_NMLIST_SLINK)) 369 devid_deviceid_to_nmlist_dlh = dlh; 370 else 371 (void) di_devlink_fini(&dlh); 372 } 373 if (ret && *retlist) 374 free(*retlist); 375 if (ret && err != 0) 376 errno = err; 377 return (ret); 378 } 379 380 /* 381 * Free Device Id Name List 382 */ 383 void 384 devid_free_nmlist(devid_nmlist_t *list) 385 { 386 devid_nmlist_t *p = list; 387 388 if (list == NULL) 389 return; 390 391 /* Free all the device names */ 392 while (p->devname != NULL) { 393 free(p->devname); 394 p++; 395 } 396 397 /* Free the array */ 398 free(list); 399 } 400