xref: /dragonfly/lib/libefivar/efivar-dp-xlate.c (revision d21d24f1)
1 /*-
2  * Copyright (c) 2017 Netflix, Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  *
25  * $FreeBSD: head/lib/libefivar/efivar-dp-xlate.c 355546 2019-12-09 01:32:18Z imp $
26  */
27 
28 #include <sys/param.h>
29 #include <sys/ucred.h>
30 #include <sys/mount.h>
31 #include <sys/sysctl.h>
32 
33 #undef MAX
34 #undef MIN
35 
36 #include <assert.h>
37 #include <efivar.h>
38 #include <errno.h>
39 #include <paths.h>
40 #include <stdio.h>
41 #include <string.h>
42 
43 #include "libefivar_int.h"
44 
45 #include "efi-osdep.h"
46 #include "efivar-dp.h"
47 
48 #include "uefi-dplib.h"
49 
50 #include "map.h"
51 #include "gpt.h"
52 
53 #define MAX_DP_SANITY	4096		/* Biggest device path in bytes */
54 #define MAX_DP_TEXT_LEN	4096		/* Longest string rep of dp */
55 
56 #define ValidLen(dp) (DevicePathNodeLength(dp) >= sizeof(EFI_DEVICE_PATH_PROTOCOL) && \
57 	    DevicePathNodeLength(dp) < MAX_DP_SANITY)
58 
59 #if 0
60 #define	G_PART	"PART"
61 #define	G_LABEL "LABEL"
62 #define G_DISK	"DISK"
63 #endif
64 
65 static int
find_slice_by_efimedia(char * buf,char ** dev)66 find_slice_by_efimedia(char *buf, char **dev)
67 {
68 	char disks[1024], efimedia[128], dev_path[256], part[256];
69 	char *disk, *disk_ptr, *s;
70 	size_t len;
71 	int fd;
72 	map_t *m;
73 	uuid_t guid;
74 	struct gpt_ent *ent;
75 
76 	len = 1024;
77 	if (sysctlbyname("kern.disks", disks, &len, NULL, 0) < 0)
78 		return -1;
79 	disk_ptr = disks;
80 	while ((disk = strsep(&disk_ptr, " ")) != NULL) {
81 		if (disk[0] == '\0')
82 			continue;
83 		if (strncmp(disk, "md", 2) == 0 ||
84 		    strncmp(disk, "cd", 2) == 0 ||
85 		    strncmp(disk, "acd", 3) == 0 ||
86 		    strncmp(disk, "fd", 2) == 0)
87 			continue;
88 		snprintf(dev_path, sizeof(dev_path), "%s%s", _PATH_DEV, disk);
89 		fd = gpt_open(dev_path);
90 		if (fd == -1)
91 			continue;
92 		for (m = map_first(); m != NULL; m = m->map_next) {
93 			if (m->map_index == NOENTRY ||
94 			    m->map_type != MAP_TYPE_GPT_PART)
95 				continue;
96 			ent = m->map_data;
97 			s = NULL;
98 			le_uuid_dec(&ent->ent_uuid, &guid);
99 			uuid_to_string(&guid, &s, NULL);
100 			snprintf(efimedia, sizeof(efimedia),
101 			    "HD(%d,GPT,%s,%#jx,%#jx)", m->map_index + 1, s,
102 			    (uintmax_t)m->map_start, (uintmax_t)m->map_size);
103 			free(s);
104 			if (strcasecmp(efimedia, buf) == 0) {
105 				gpt_close(fd);
106 				snprintf(part, sizeof(part), "%s%ss%d",
107 				    _PATH_DEV, disk, m->map_index);
108 				*dev = strdup(part);
109 				return 0;
110 			}
111 		}
112 		gpt_close(fd);
113 	}
114 	return -1;
115 }
116 
117 static int
efi_hd_to_unix(const_efidp dp,char ** dev,char ** relpath,char ** abspath)118 efi_hd_to_unix(const_efidp dp, char **dev, char **relpath, char **abspath)
119 {
120 	int error, rv = 0, n, i;
121 	const_efidp media, file, walker;
122 	size_t len, mntlen;
123 	char buf[MAX_DP_TEXT_LEN];
124 	char *pwalk;
125 	struct statfs *mnt;
126 
127 	walker = media = dp;
128 
129 	/*
130 	 * Now, we can either have a filepath node next, or the end.
131 	 * Otherwise, it's an error.
132 	 */
133 	if (!ValidLen(walker))
134 		return (EINVAL);
135 	walker = (const_efidp)NextDevicePathNode(walker);
136 	if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
137 		return (EINVAL);
138 	if (DevicePathType(walker) ==  MEDIA_DEVICE_PATH &&
139 	    DevicePathSubType(walker) == MEDIA_FILEPATH_DP)
140 		file = walker;
141 	else if (DevicePathType(walker) == MEDIA_DEVICE_PATH &&
142 	    DevicePathType(walker) == END_DEVICE_PATH_TYPE)
143 		file = NULL;
144 	else
145 		return (EINVAL);
146 
147 	/*
148 	 * Format this node. We're going to look for it as a efimedia
149 	 * attribute of some geom node. Once we find that node, we use it
150 	 * as the device it comes from, at least provisionally.
151 	 */
152 	len = efidp_format_device_path_node(buf, sizeof(buf), media);
153 	if (len > sizeof(buf))
154 		return (EINVAL);
155 
156 	error = find_slice_by_efimedia(buf, dev);
157 	if (error == -1) {
158 		rv = ENOENT;
159 		goto errout;
160 	}
161 
162 	if (*dev == NULL) {
163 		rv = ENOMEM;
164 		goto errout;
165 	}
166 
167 	/*
168 	 * No file specified, just return the device. Don't even look
169 	 * for a mountpoint. XXX Sane?
170 	 */
171 	if (file == NULL)
172 		goto errout;
173 
174 	/*
175 	 * Now extract the relative path. The next node in the device path should
176 	 * be a filesystem node. If not, we have issues.
177 	 */
178 	*relpath = efidp_extract_file_path(file);
179 	if (*relpath == NULL) {
180 		rv = ENOMEM;
181 		goto errout;
182 	}
183 	for (pwalk = *relpath; *pwalk; pwalk++)
184 		if (*pwalk == '\\')
185 			*pwalk = '/';
186 
187 	/*
188 	 * To find the absolute path, we have to look for where we're mounted.
189 	 * We only look a little hard, since looking too hard can come up with
190 	 * false positives (imagine a graid, one of whose devices is *dev).
191 	 */
192 	n = getfsstat(NULL, 0, MNT_NOWAIT) + 1;
193 	if (n < 0) {
194 		rv = errno;
195 		goto errout;
196 	}
197 	mntlen = sizeof(struct statfs) * n;
198 	mnt = malloc(mntlen);
199 	n = getfsstat(mnt, mntlen, MNT_NOWAIT);
200 	if (n < 0) {
201 		rv = errno;
202 		goto errout;
203 	}
204 	for (i = 0; i < n; i++) {
205 		/*
206 		 * Skip all pseudo filesystems. This also skips the real filesytsem
207 		 * of ZFS. There's no EFI designator for ZFS in the standard, so
208 		 * we'll need to invent one, but its decoding will be handled in
209 		 * a separate function.
210 		 */
211 		if (mnt[i].f_mntfromname[0] != '/')
212 			continue;
213 
214 		/*
215 		 * First see if it is directly attached
216 		 */
217 		if (strcmp(*dev, mnt[i].f_mntfromname) == 0)
218 			break;
219 
220 #if 0 /* XXX swildner */
221 		/*
222 		 * Next see if it is attached via one of the physical disk's
223 		 * labels.
224 		 */
225 		LIST_FOREACH(cp, &provider->lg_consumers, lg_consumer) {
226 			pp = cp->lg_provider;
227 			if (strcmp(pp->lg_geom->lg_class->lg_name, G_LABEL) != 0)
228 				continue;
229 			if (strcmp(g_device_path(pp->lg_name), mnt[i].f_mntfromname) == 0)
230 				goto break2;
231 		}
232 		/* Not the one, try the next mount point */
233 #endif
234 	}
235 #if 0
236 break2:
237 #endif
238 
239 	/*
240 	 * No mountpoint found, no absolute path possible
241 	 */
242 	if (i >= n)
243 		goto errout;
244 
245 	/*
246 	 * Construct absolute path and we're finally done.
247 	 */
248 	if (strcmp(mnt[i].f_mntonname, "/") == 0)
249 		asprintf(abspath, "/%s", *relpath);
250 	else
251 		asprintf(abspath, "%s/%s", mnt[i].f_mntonname, *relpath);
252 
253 errout:
254 	if (rv != 0) {
255 		free(*dev);
256 		*dev = NULL;
257 		free(*relpath);
258 		*relpath = NULL;
259 	}
260 	return (rv);
261 }
262 
263 /*
264  * Translate the passed in device_path to a unix path via the following
265  * algorithm.
266  *
267  * If dp, dev or path NULL, return EDOOFUS. XXX wise?
268  *
269  * Set *path = NULL; *dev = NULL;
270  *
271  * Walk through the device_path until we find either a media device path.
272  * Return EINVAL if not found. Return EINVAL if walking dp would
273  * land us more than sanity size away from the start (4k).
274  *
275  * If we find a media descriptor, we search through the GPTs to see if we
276  * can find a matching node. If no match is found in the GPTs that matches,
277  * return ENXIO.
278  *
279  * Once we find a matching node, we search to see if there is a filesystem
280  * mounted on it. If we find nothing, then search each of the devices that are
281  * mounted to see if we can work up the geom tree to find the matching node. if
282  * we still can't find anything, *dev = sprintf("/dev/%s", slice_name
283  * of the original node we found), but return ENOTBLK.
284  *
285  * Record the dev of the mountpoint in *dev.
286  *
287  * Once we find something, check to see if the next node in the device path is
288  * the end of list. If so, return the mountpoint.
289  *
290  * If the next node isn't a File path node, return EFTYPE.
291  *
292  * Extract the path from the File path node(s). translate any \ file separators
293  * to /. Append the result to the mount point. Copy the resulting path into
294  * *path.  Stat that path. If it is not found, return the errorr from stat.
295  *
296  * Finally, check to make sure the resulting path is still on the same
297  * device. If not, return ENODEV.
298  *
299  * Otherwise return 0.
300  *
301  * The dev or full path that's returned is malloced, so needs to be freed when
302  * the caller is done about it. Unlike many other functions, we can return data
303  * with an error code, so pay attention.
304  */
305 int
efivar_device_path_to_unix_path(const_efidp dp,char ** dev,char ** relpath,char ** abspath)306 efivar_device_path_to_unix_path(const_efidp dp, char **dev, char **relpath, char **abspath)
307 {
308 	const_efidp walker;
309 	int rv = 0;
310 
311 	/*
312 	 * Sanity check args, fail early
313 	 */
314 	if (dp == NULL || dev == NULL || relpath == NULL || abspath == NULL)
315 		return (EDOOFUS);
316 
317 	*dev = NULL;
318 	*relpath = NULL;
319 	*abspath = NULL;
320 
321 	/*
322 	 * Find the first media device path we can. If we go too far,
323 	 * assume the passed in device path is bogus. If we hit the end
324 	 * then we didn't find a media device path, so signal that error.
325 	 */
326 	walker = dp;
327 	if (!ValidLen(walker))
328 		return (EINVAL);
329 	while (DevicePathType(walker) != MEDIA_DEVICE_PATH &&
330 	    DevicePathType(walker) != END_DEVICE_PATH_TYPE) {
331 		walker = (const_efidp)NextDevicePathNode(walker);
332 		if ((uintptr_t)walker - (uintptr_t)dp > MAX_DP_SANITY)
333 			return (EINVAL);
334 		if (!ValidLen(walker))
335 			return (EINVAL);
336 	}
337 	if (DevicePathType(walker) !=  MEDIA_DEVICE_PATH)
338 		return (EINVAL);
339 
340 	/*
341 	 * There's several types of media paths. We're only interested in the
342 	 * hard disk path, as it's really the only relevant one to booting. The
343 	 * CD path just might also be relevant, and would be easy to add, but
344 	 * isn't supported. A file path too is relevant, but at this stage, it's
345 	 * premature because we're trying to translate a specification for a device
346 	 * and path on that device into a unix path, or at the very least, a
347 	 * device : path-on-device.
348 	 */
349 	rv = EINVAL;
350 	if (DevicePathSubType(walker) == MEDIA_HARDDRIVE_DP)
351 		rv = efi_hd_to_unix(walker, dev, relpath, abspath);
352 #ifdef notyet
353 	else if (is_cdrom_device(walker))
354 		rv = efi_cdrom_to_unix(walker, dev, relpath, abspath);
355 	else if (is_floppy_device(walker))
356 		rv = efi_floppy_to_unix(walker, dev, relpath, abspath);
357 	else if (is_zpool_device(walker))
358 		rv = efi_zpool_to_unix(walker, dev, relpath, abspath);
359 #endif
360 
361 	return (rv);
362 }
363 
364 /*
365  * Construct the EFI path to a current unix path as follows.
366  *
367  * The path may be of one of three forms:
368  *	1) /path/to/file -- full path to a file. The file need not be present,
369  *		but /path/to must be. It must reside on a local filesystem
370  *		mounted on a GPT or MBR partition.
371  *	2) //path/to/file -- Shorthand for 'On the EFI partition, \path\to\file'
372  *		where 'The EFI Partition' is a partiton that's type is 'efi'
373  *		on the same disk that / is mounted from. If there are multiple
374  *		or no 'efi' parittions on that disk, or / isn't on a disk that
375  *		we can trace back to a physical device, an error will result
376  *	3) [/dev/]geom-name:/path/to/file -- Use the specified partition
377  *		(and it must be a GPT or MBR partition) with the specified
378  *		path. The latter is not authenticated.
379  * all path forms translate any \ characters to / before further processing.
380  * When a file path node is created, all / characters are translated back
381  * to \.
382  *
383  * For paths of the first form:
384  *	find where the filesystem is mount (either the file directly, or
385  *		its parent directory).
386  *	translate any logical device name (eg lable) to a physical one
387  *	If not possible, return ENXIO
388  *	If the physical path is unsupported (Eg not on a GPT or MBR disk),
389  *		return ENXIO
390  *	Create a media device path node.
391  *	append the relative path from the mountpoint to the media device node
392  * 		as a file path.
393  *
394  * For paths matching the second form:
395  *	find the EFI partition corresponding to the root fileystem.
396  *	If none found, return ENXIO
397  *	Create a media device path node for the found partition
398  *	Append a File Path to the end for the rest of the file.
399  *
400  * For paths of the third form
401  *	Translate the geom-name passed in into a physical partition
402  *		name.
403  *	Return ENXIO if the translation fails
404  *	Make a media device path for it
405  *	append the part after the : as a File path node.
406  */
407 
408 static char *
path_to_file_dp(const char * relpath)409 path_to_file_dp(const char *relpath)
410 {
411 	char *rv;
412 
413 	asprintf(&rv, "File(%s)", relpath);
414 	return rv;
415 }
416 
417 static char *
find_geom_efi_on_root(void)418 find_geom_efi_on_root(void)
419 {
420 	/* Didn't return anything but NULL in FreeBSD */
421 	return (NULL);
422 }
423 
424 
425 static char *
find_slice_efimedia(const char * slice)426 find_slice_efimedia(const char *slice)
427 {
428 	char disk_path[1024], disks[1024], efimedia[128];
429 	char *disk, *disk_ptr, *ep, *s;
430 	int fd;
431 	u_long sliceno;
432 	size_t len;
433 	map_t *m;
434 	uuid_t guid;
435 	struct gpt_ent *ent;
436 
437 	len = 1024;
438 	if (sysctlbyname("kern.disks", disks, &len, NULL, 0) < 0)
439 		return (NULL);
440 	disk_ptr = disks;
441 	while ((disk = strsep(&disk_ptr, " ")) != NULL) {
442 		if (disk[0] == '\0')
443 			continue;
444 		if (strncmp(disk, "md", 2) == 0 ||
445 		    strncmp(disk, "cd", 2) == 0 ||
446 		    strncmp(disk, "acd", 3) == 0 ||
447 		    strncmp(disk, "fd", 2) == 0)
448 			continue;
449 		if (strncmp(slice, disk, strlen(disk)) == 0) {
450 			snprintf(disk_path, sizeof(disk_path), "%s%s",
451 			    _PATH_DEV, disk);
452 			/* XXX should base be 36 here? */
453 			sliceno = strtoul(slice + sizeof(disk) + 1, &ep, 10);
454 			if (*ep != 0)
455 				return (NULL);
456 			break;
457 		}
458 	}
459 	if (disk == NULL)
460 		return (NULL);
461 	fd = gpt_open(disk_path);
462 	if (fd == -1)
463 		return (NULL);
464 	for (m = map_first(); m != NULL; m = m->map_next) {
465 		if (m->map_index == NOENTRY ||
466 		    m->map_type != MAP_TYPE_GPT_PART)
467 			continue;
468 		if (m->map_index == sliceno) {
469 			ent = m->map_data;
470 			s = NULL;
471 			le_uuid_dec(&ent->ent_uuid, &guid);
472 			uuid_to_string(&guid, &s, NULL);
473 			snprintf(efimedia, sizeof(efimedia),
474 			    "HD(%d,GPT,%s,%#jx,%#jx)", m->map_index + 1, s,
475 			    (uintmax_t)m->map_start, (uintmax_t)m->map_size);
476 			free(s);
477 			gpt_close(fd);
478 			return strdup(efimedia);
479 		}
480 	}
481 	gpt_close(fd);
482 	return (NULL);
483 }
484 
485 static int
build_dp(const char * efimedia,const char * relpath,efidp * dp)486 build_dp(const char *efimedia, const char *relpath, efidp *dp)
487 {
488 	char *fp, *dptxt = NULL, *cp, *rp;
489 	int rv = 0;
490 	efidp out = NULL;
491 	size_t len;
492 
493 	rp = strdup(relpath);
494 	for (cp = rp; *cp; cp++)
495 		if (*cp == '/')
496 			*cp = '\\';
497 	fp = path_to_file_dp(rp);
498 	free(rp);
499 	if (fp == NULL) {
500 		rv = ENOMEM;
501 		goto errout;
502 	}
503 
504 	asprintf(&dptxt, "%s/%s", efimedia, fp);
505 	out = malloc(8192);
506 	len = efidp_parse_device_path(dptxt, out, 8192);
507 	if (len > 8192) {
508 		rv = ENOMEM;
509 		goto errout;
510 	}
511 	if (len == 0) {
512 		rv = EINVAL;
513 		goto errout;
514 	}
515 
516 	*dp = out;
517 errout:
518 	if (rv) {
519 		free(out);
520 	}
521 	free(dptxt);
522 	free(fp);
523 
524 	return rv;
525 }
526 
527 /* Handles //path/to/file */
528 /*
529  * Which means: find the disk that has /. Then look for a EFI partition
530  * and use that for the efimedia and /path/to/file as relative to that.
531  * Not sure how ZFS will work here since we can't easily make the leap
532  * to the geom from the zpool.
533  */
534 static int
efipart_to_dp(char * path,efidp * dp)535 efipart_to_dp(char *path, efidp *dp)
536 {
537 	char *efimedia = NULL;
538 	int rv;
539 
540 	efimedia = find_geom_efi_on_root();
541 #ifdef notyet
542 	if (efimedia == NULL)
543 		efimedia = find_efi_on_zfsroot(dev);
544 #endif
545 	if (efimedia == NULL) {
546 		rv = ENOENT;
547 		goto errout;
548 	}
549 
550 	rv = build_dp(efimedia, path + 1, dp);
551 errout:
552 	free(efimedia);
553 
554 	return rv;
555 }
556 
557 /* Handles [/dev/]geom:[/]path/to/file */
558 /* Handles zfs-dataset:[/]path/to/file (this may include / ) */
559 static int
dev_path_to_dp(char * path,efidp * dp)560 dev_path_to_dp(char *path, efidp *dp)
561 {
562 	char *relpath, *dev, *efimedia = NULL;
563 	int rv = 0;
564 
565 	relpath = strchr(path, ':');
566 	assert(relpath != NULL);
567 	*relpath++ = '\0';
568 
569 	dev = path;
570 	if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
571 		dev += sizeof(_PATH_DEV) -1;
572 
573 	efimedia = find_slice_efimedia(dev);
574 #ifdef notyet
575 	if (efimedia == NULL)
576 		find_zfs_efi_media(dev);
577 #endif
578 	if (efimedia == NULL) {
579 		rv = ENOENT;
580 		goto errout;
581 	}
582 	rv = build_dp(efimedia, relpath, dp);
583 errout:
584 	free(efimedia);
585 
586 	return rv;
587 }
588 
589 /* Handles /path/to/file */
590 static int
path_to_dp(char * path,efidp * dp)591 path_to_dp(char *path, efidp *dp)
592 {
593 	struct statfs buf;
594 	char *rp = NULL, *ep, *dev, *efimedia = NULL;
595 	int rv = 0;
596 
597 	rp = realpath(path, NULL);
598 	if (rp == NULL) {
599 		rv = errno;
600 		goto errout;
601 	}
602 
603 	if (statfs(rp, &buf) != 0) {
604 		rv = errno;
605 		goto errout;
606 	}
607 
608 	dev = buf.f_mntfromname;
609 	if (strncmp(dev, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
610 		dev += sizeof(_PATH_DEV) -1;
611 	ep = rp + strlen(buf.f_mntonname);
612 
613 	efimedia = find_slice_efimedia(dev);
614 #ifdef notyet
615 	if (efimedia == NULL)
616 		find_zfs_efi_media(dev);
617 #endif
618 	if (efimedia == NULL) {
619 		rv = ENOENT;
620 		goto errout;
621 	}
622 
623 	rv = build_dp(efimedia, ep, dp);
624 errout:
625 	free(efimedia);
626 	free(rp);
627 	if (rv != 0) {
628 		free(*dp);
629 		*dp = NULL;
630 	}
631 
632 	return (rv);
633 }
634 
635 int
efivar_unix_path_to_device_path(const char * path,efidp * dp)636 efivar_unix_path_to_device_path(const char *path, efidp *dp)
637 {
638 	char *modpath = NULL, *cp;
639 	int rv = ENOMEM;
640 
641 	/*
642 	 * Fail early for clearly bogus things
643 	 */
644 	if (path == NULL || dp == NULL)
645 		return (EDOOFUS);
646 
647 	/*
648 	 * Convert all \ to /. We'll convert them back again when
649 	 * we encode the file. Boot loaders are expected to cope.
650 	 */
651 	modpath = strdup(path);
652 	if (modpath == NULL)
653 		goto out;
654 	for (cp = modpath; *cp; cp++)
655 		if (*cp == '\\')
656 			*cp = '/';
657 
658 	if (modpath[0] == '/' && modpath[1] == '/')	/* Handle //foo/bar/baz */
659 		rv = efipart_to_dp(modpath, dp);
660 	else if (strchr(modpath, ':'))			/* Handle dev:/bar/baz */
661 		rv = dev_path_to_dp(modpath, dp);
662 	else						/* Handle /a/b/c */
663 		rv = path_to_dp(modpath, dp);
664 
665 out:
666 	free(modpath);
667 
668 	return (rv);
669 }
670