xref: /freebsd/sys/contrib/openzfs/lib/libzutil/zutil_device_path.c (revision 22649d4dba730d46244fd2dff4fd174903c8379f)
1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3  * This file and its contents are supplied under the terms of the
4  * Common Development and Distribution License ("CDDL"), version 1.0.
5  * You may only use this file in accordance with the terms of version
6  * 1.0 of the CDDL.
7  *
8  * A full copy of the text of the CDDL should have accompanied this
9  * source.  A copy of the CDDL is also available via the Internet at
10  * https://opensource.org/license/CDDL-1.0.
11  */
12 
13 /*
14  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
15  */
16 
17 #include <errno.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include <libzutil.h>
24 
25 /* Substring from after the last slash, or the string itself if none */
26 const char *
zfs_basename(const char * path)27 zfs_basename(const char *path)
28 {
29 	const char *bn = strrchr(path, '/');
30 	return (bn ? bn + 1 : path);
31 }
32 
33 /* Return index of last slash or -1 if none */
34 ssize_t
zfs_dirnamelen(const char * path)35 zfs_dirnamelen(const char *path)
36 {
37 	const char *end = strrchr(path, '/');
38 	return (end ? end - path : -1);
39 }
40 
41 /*
42  * Given a shorthand device name check if a file by that name exists in any
43  * of the 'zpool_default_import_path' or ZPOOL_IMPORT_PATH directories.  If
44  * one is found, store its fully qualified path in the 'path' buffer passed
45  * by the caller and return 0, otherwise return an error.
46  */
47 int
zfs_resolve_shortname(const char * name,char * path,size_t len)48 zfs_resolve_shortname(const char *name, char *path, size_t len)
49 {
50 	const char *env = getenv("ZPOOL_IMPORT_PATH");
51 	char resolved_path[PATH_MAX];
52 
53 	if (env) {
54 		for (;;) {
55 			env += strspn(env, ":");
56 			size_t dirlen = strcspn(env, ":");
57 			if (dirlen) {
58 				(void) snprintf(path, len, "%.*s/%s",
59 				    (int)dirlen, env, name);
60 				if (access(path, F_OK) == 0)
61 					return (0);
62 
63 				env += dirlen;
64 			} else
65 				break;
66 		}
67 	} else {
68 		size_t count;
69 		const char *const *zpool_default_import_path =
70 		    zpool_default_search_paths(&count);
71 
72 		for (size_t i = 0; i < count; ++i) {
73 			(void) snprintf(path, len, "%s/%s",
74 			    zpool_default_import_path[i], name);
75 			if (access(path, F_OK) == 0)
76 				return (0);
77 		}
78 	}
79 
80 	/*
81 	 * The user can pass a relative path like ./file1 for the vdev. The path
82 	 * must contain a directory prefix like './file1' or '../file1'.  Simply
83 	 * passing 'file1' is not allowed, as it may match a block device name.
84 	 */
85 	if ((strncmp(name, "./", 2) == 0 || strncmp(name, "../", 3) == 0) &&
86 	    realpath(name, resolved_path) != NULL) {
87 		if (access(resolved_path, F_OK) == 0) {
88 			if (strlen(resolved_path) + 1 <= len) {
89 				if (strlcpy(path, resolved_path, len) < len)
90 					return (0); /* success */
91 			}
92 		}
93 	}
94 	return (errno = ENOENT);
95 }
96 
97 /*
98  * Given a shorthand device name look for a match against 'cmp_name'.  This
99  * is done by checking all prefix expansions using either the default
100  * 'zpool_default_import_paths' or the ZPOOL_IMPORT_PATH environment
101  * variable.  Proper partition suffixes will be appended if this is a
102  * whole disk.  When a match is found 0 is returned otherwise ENOENT.
103  */
104 static int
zfs_strcmp_shortname(const char * name,const char * cmp_name,int wholedisk)105 zfs_strcmp_shortname(const char *name, const char *cmp_name, int wholedisk)
106 {
107 	int path_len, cmp_len, i = 0, error = ENOENT;
108 	char *dir, *env, *envdup = NULL, *tmp = NULL;
109 	char path_name[MAXPATHLEN];
110 	const char *const *zpool_default_import_path = NULL;
111 	size_t count;
112 
113 	cmp_len = strlen(cmp_name);
114 	env = getenv("ZPOOL_IMPORT_PATH");
115 
116 	if (env) {
117 		envdup = strdup(env);
118 		dir = strtok_r(envdup, ":", &tmp);
119 	} else {
120 		zpool_default_import_path = zpool_default_search_paths(&count);
121 		dir = (char *)zpool_default_import_path[i];
122 	}
123 
124 	while (dir) {
125 		/* Trim trailing directory slashes from ZPOOL_IMPORT_PATH */
126 		if (env) {
127 			while ((dir[0] != '\0') && (dir[strlen(dir)-1] == '/'))
128 				dir[strlen(dir)-1] = '\0';
129 		}
130 
131 		path_len = snprintf(path_name, MAXPATHLEN, "%s/%s", dir, name);
132 		if (wholedisk)
133 			path_len = zfs_append_partition(path_name, MAXPATHLEN);
134 
135 		if ((path_len == cmp_len) && strcmp(path_name, cmp_name) == 0) {
136 			error = 0;
137 			break;
138 		}
139 
140 		if (env) {
141 			dir = strtok_r(NULL, ":", &tmp);
142 		} else if (++i < count) {
143 			dir = (char *)zpool_default_import_path[i];
144 		} else {
145 			dir = NULL;
146 		}
147 	}
148 
149 	if (env)
150 		free(envdup);
151 
152 	return (error);
153 }
154 
155 /*
156  * Given either a shorthand or fully qualified path name look for a match
157  * against 'cmp'.  The passed name will be expanded as needed for comparison
158  * purposes and redundant slashes stripped to ensure an accurate match.
159  */
160 int
zfs_strcmp_pathname(const char * name,const char * cmp,int wholedisk)161 zfs_strcmp_pathname(const char *name, const char *cmp, int wholedisk)
162 {
163 	int path_len, cmp_len;
164 	char path_name[MAXPATHLEN];
165 	char cmp_name[MAXPATHLEN];
166 	char *dir, *tmp = NULL;
167 
168 	/* Strip redundant slashes if they exist due to ZPOOL_IMPORT_PATH */
169 	cmp_name[0] = '\0';
170 	(void) strlcpy(path_name, cmp, sizeof (path_name));
171 	for (dir = strtok_r(path_name, "/", &tmp);
172 	    dir != NULL;
173 	    dir = strtok_r(NULL, "/", &tmp)) {
174 		strlcat(cmp_name, "/", sizeof (cmp_name));
175 		strlcat(cmp_name, dir, sizeof (cmp_name));
176 	}
177 
178 	if (name[0] != '/')
179 		return (zfs_strcmp_shortname(name, cmp_name, wholedisk));
180 
181 	(void) strlcpy(path_name, name, MAXPATHLEN);
182 	path_len = strlen(path_name);
183 	cmp_len = strlen(cmp_name);
184 
185 	if (wholedisk) {
186 		path_len = zfs_append_partition(path_name, MAXPATHLEN);
187 		if (path_len == -1)
188 			return (ENOMEM);
189 	}
190 
191 	if ((path_len != cmp_len) || strcmp(path_name, cmp_name))
192 		return (ENOENT);
193 
194 	return (0);
195 }
196