xref: /minix/minix/lib/libfsdriver/lookup.c (revision 83133719)
1 
2 #include "fsdriver.h"
3 
4 /*
5  * Check whether the given node may be accessed as directory.
6  * Return OK or an appropriate error code.
7  */
8 static int
9 access_as_dir(struct fsdriver_node * __restrict node,
10 	vfs_ucred_t * __restrict ucred)
11 {
12 	mode_t mask;
13 	int i;
14 
15 	/* The file must be a directory to begin with. */
16 	if (!S_ISDIR(node->fn_mode)) return ENOTDIR;
17 
18 	/* The root user may access anything at all. */
19 	if (ucred->vu_uid == ROOT_UID) return OK;
20 
21 	/* Otherwise, the caller must have search access to the directory. */
22 	if (ucred->vu_uid == node->fn_uid) mask = S_IXUSR;
23 	else if (ucred->vu_gid == node->fn_gid) mask = S_IXGRP;
24 	else {
25 		mask = S_IXOTH;
26 
27 		for (i = 0; i < ucred->vu_ngroups; i++) {
28 			if (ucred->vu_sgroups[i] == node->fn_gid) {
29 				mask = S_IXGRP;
30 
31 				break;
32 			}
33 		}
34 	}
35 
36 	return (node->fn_mode & mask) ? OK : EACCES;
37 }
38 
39 /*
40  * Get the next path component from a path.  Return the start and end of the
41  * component into the path, and store its name in a null-terminated buffer.
42  */
43 static int
44 next_name(char ** ptr, char ** start, char * __restrict name, size_t namesize)
45 {
46 	char *p;
47 	unsigned int i;
48 
49 	/* Skip one or more path separator characters; they have no effect. */
50 	for (p = *ptr; *p == '/'; p++);
51 
52 	*start = p;
53 
54 	if (*p) {
55 		/*
56 		 * Copy as much of the name as possible, up to the next path
57 		 * separator.  Return an error if the name does not fit.
58 		 */
59 		for (i = 0; *p && *p != '/' && i < namesize; p++, i++)
60 			name[i] = *p;
61 
62 		if (i >= namesize)
63 			return ENAMETOOLONG;
64 
65 		name[i] = 0;
66 	} else
67 		/* An empty path component implies the current directory. */
68 		strlcpy(name, ".", namesize);
69 
70 	/*
71 	 * Return a pointer to the first character not part of this component.
72 	 * This would typically be either the path separator or a null.
73 	 */
74 	*ptr = p;
75 	return OK;
76 }
77 
78 /*
79  * Given a symbolic link, resolve and return the contents of the link, followed
80  * by the remaining part of the path that has not yet been resolved (the tail).
81  * Note that the tail points into the given destination buffer.
82  */
83 static int
84 resolve_link(const struct fsdriver * __restrict fdp, ino_t ino_nr, char * pptr,
85 	size_t size, char * tail)
86 {
87 	struct fsdriver_data data;
88 	char path[PATH_MAX];
89 	ssize_t r;
90 
91 	data.endpt = SELF;
92 	data.ptr = path;
93 	data.size = sizeof(path) - 1;
94 
95 	/*
96 	 * Let the file system the symbolic link.  Note that the resulting path
97 	 * is not null-terminated.
98 	 */
99 	if ((r = fdp->fdr_rdlink(ino_nr, &data, data.size)) < 0)
100 		return r;
101 
102 	/* Append the remaining part of the original path to be resolved. */
103 	if (r + strlen(tail) >= sizeof(path))
104 		return ENAMETOOLONG;
105 
106 	strlcpy(&path[r], tail, sizeof(path) - r);
107 
108 	/* Copy back the result to the original buffer. */
109 	strlcpy(pptr, path, size);
110 
111 	return OK;
112 }
113 
114 /*
115  * Process a LOOKUP request from VFS.
116  */
117 int
118 fsdriver_lookup(const struct fsdriver * __restrict fdp,
119 	const message * __restrict m_in, message * __restrict m_out)
120 {
121 	ino_t dir_ino_nr, root_ino_nr;
122 	struct fsdriver_node cur_node, next_node;
123 	char path[PATH_MAX], name[NAME_MAX+1];
124 	char *ptr, *last;
125 	cp_grant_id_t path_grant;
126 	vfs_ucred_t ucred;
127 	unsigned int flags;
128 	size_t path_len, path_size;
129 	int r, r2, going_up, is_mountpt, symloop;
130 
131 	if (fdp->fdr_lookup == NULL)
132 		return ENOSYS;
133 
134 	dir_ino_nr = m_in->m_vfs_fs_lookup.dir_ino;
135 	root_ino_nr = m_in->m_vfs_fs_lookup.root_ino;
136 	path_grant = m_in->m_vfs_fs_lookup.grant_path;
137 	path_size = m_in->m_vfs_fs_lookup.path_size;
138 	path_len = m_in->m_vfs_fs_lookup.path_len;
139 	flags = m_in->m_vfs_fs_lookup.flags;
140 
141 	/* Fetch the path name. */
142 	if ((r = fsdriver_getname(m_in->m_source, path_grant, path_len, path,
143 	    sizeof(path), FALSE /*not_empty*/)) != OK)
144 		return r;
145 
146 	/* Fetch the caller's credentials. */
147 	if (flags & PATH_GET_UCRED) {
148 		if (m_in->m_vfs_fs_lookup.ucred_size != sizeof(ucred)) {
149 			printf("fsdriver: bad credential structure\n");
150 
151 			return EINVAL;
152 		}
153 
154 		if ((r = sys_safecopyfrom(m_in->m_source,
155 		    m_in->m_vfs_fs_lookup.grant_ucred, 0, (vir_bytes)&ucred,
156 		    (phys_bytes)m_in->m_vfs_fs_lookup.ucred_size)) != OK)
157 			return r;
158 	} else {
159 		ucred.vu_uid = m_in->m_vfs_fs_lookup.uid;
160 		ucred.vu_gid = m_in->m_vfs_fs_lookup.gid;
161 		ucred.vu_ngroups = 0;
162 	}
163 
164 	/* Start the actual lookup by referencing the starting inode. */
165 	strlcpy(name, ".", sizeof(name)); /* allow a non-const argument */
166 
167 	r = fdp->fdr_lookup(dir_ino_nr, name, &cur_node, &is_mountpt);
168 	if (r != OK)
169 		return r;
170 
171 	symloop = 0;
172 
173 	/* Whenever we leave this loop, 'cur_node' holds a referenced inode. */
174 	for (ptr = last = path; *ptr != 0; ) {
175 		/*
176 		 * Get the next path component. The result is a non-empty
177 		 * string.
178 		 */
179 		if ((r = next_name(&ptr, &last, name, sizeof(name))) != OK)
180 			break;
181 
182 		if (is_mountpt) {
183 			/*
184 			 * If we start off from a mount point, the next path
185 			 * component *must* cause us to go up.  Anything else
186 			 * is a protocol violation.
187 			 */
188 			if (strcmp(name, "..")) {
189 				r = EINVAL;
190 				break;
191 			}
192 		} else {
193 			/*
194 			 * There is more path to process.  That means that the
195 			 * current file is now being accessed as a directory.
196 			 * Check type and permissions.
197 			 */
198 			if ((r = access_as_dir(&cur_node, &ucred)) != OK)
199 				break;
200 		}
201 
202 		/* A single-dot component resolves to the current directory. */
203 		if (!strcmp(name, "."))
204 			continue;
205 
206 		/* A dot-dot component resolves to the parent directory. */
207 		going_up = !strcmp(name, "..");
208 
209 		if (going_up) {
210 			/*
211 			 * The parent of the process's root directory is the
212 			 * same root directory.  All processes have a root
213 			 * directory, so this check also covers the case of
214 			 * going up from the global system root directory.
215 			 */
216 			if (cur_node.fn_ino_nr == root_ino_nr)
217 				continue;
218 
219 			/*
220 			 * Going up from the file system's root directory means
221 			 * crossing mount points.  As indicated, the root file
222 			 * system is already covered by the check above.
223 			 */
224 			if (cur_node.fn_ino_nr == fsdriver_root) {
225 				ptr = last;
226 
227 				r = ELEAVEMOUNT;
228 				break;
229 			}
230 		}
231 
232 		/*
233 		 * Descend into a child node or go up to a parent node, by
234 		 * asking the actual file system to perform a one-step
235 		 * resolution.  The result, if successful, is an open
236 		 * (referenced) inode.
237 		 */
238 		if ((r = fdp->fdr_lookup(cur_node.fn_ino_nr, name, &next_node,
239 		    &is_mountpt)) != OK)
240 			break;
241 
242 		/* Sanity check: a parent node must always be a directory. */
243 		if (going_up && !S_ISDIR(next_node.fn_mode))
244 			panic("fsdriver: ascending into nondirectory");
245 
246 		/*
247 		 * Perform symlink resolution, unless the symlink is the last
248 		 * path component and VFS is asking us not to resolve it.
249 		 */
250 		if (S_ISLNK(next_node.fn_mode) &&
251 		    (*ptr || !(flags & PATH_RET_SYMLINK))) {
252 			/*
253 			 * Resolve the symlink, and append the remaining
254 			 * unresolved part of the path.
255 			 */
256 			if (++symloop < _POSIX_SYMLOOP_MAX)
257 				r = resolve_link(fdp, next_node.fn_ino_nr,
258 				    path, sizeof(path), ptr);
259 			else
260 				r = ELOOP;
261 
262 			fdp->fdr_putnode(next_node.fn_ino_nr, 1);
263 
264 			if (r != OK)
265 				break;
266 
267 			ptr = path;
268 
269 			/* If the symlink is absolute, return it to VFS. */
270 			if (path[0] == '/') {
271 				r = ESYMLINK;
272 				break;
273 			}
274 
275 			continue;
276 		}
277 
278 		/* We have found a new node.  Continue from this node. */
279 		fdp->fdr_putnode(cur_node.fn_ino_nr, 1);
280 
281 		cur_node = next_node;
282 
283 		/*
284 		 * If the new node is a mount point, yield to another file
285 		 * system.
286 		 */
287 		if (is_mountpt) {
288 			r = EENTERMOUNT;
289 			break;
290 		}
291 	}
292 
293 	/* For special redirection errors, we need to return extra details. */
294 	if (r == EENTERMOUNT || r == ELEAVEMOUNT || r == ESYMLINK) {
295 		/* Copy back the path if we resolved at least one symlink. */
296 		if (symloop > 0) {
297 			if ((path_len = strlen(path) + 1) > path_size)
298 				return ENAMETOOLONG;
299 
300 			r2 = sys_safecopyto(m_in->m_source, path_grant, 0,
301 			    (vir_bytes)path, (phys_bytes)path_len);
302 		} else
303 			r2 = OK;
304 
305 		if (r2 == OK) {
306 			m_out->m_fs_vfs_lookup.offset = (int)(ptr - path);
307 			m_out->m_fs_vfs_lookup.symloop = symloop;
308 
309 			if (r == EENTERMOUNT)
310 				m_out->m_fs_vfs_lookup.inode =
311 				    cur_node.fn_ino_nr;
312 		} else
313 			r = r2;
314 	}
315 
316 	/*
317 	 * On success, leave the resulting file open and return its details.
318 	 * If an error occurred, close the file and return error information.
319 	 */
320 	if (r == OK) {
321 		m_out->m_fs_vfs_lookup.inode = cur_node.fn_ino_nr;
322 		m_out->m_fs_vfs_lookup.mode = cur_node.fn_mode;
323 		m_out->m_fs_vfs_lookup.file_size = cur_node.fn_size;
324 		m_out->m_fs_vfs_lookup.uid = cur_node.fn_uid;
325 		m_out->m_fs_vfs_lookup.gid = cur_node.fn_gid;
326 		m_out->m_fs_vfs_lookup.device = cur_node.fn_dev;
327 	} else
328 		fdp->fdr_putnode(cur_node.fn_ino_nr, 1);
329 
330 	return r;
331 }
332