summaryrefslogtreecommitdiff
path: root/minix/lib/libfsdriver/lookup.c
blob: b8e2ae08044aa77338bcadf0cc6543eed7316241 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333

#include "fsdriver.h"

/*
 * Check whether the given node may be accessed as directory.
 * Return OK or an appropriate error code.
 */
static int
access_as_dir(struct fsdriver_node * __restrict node,
	vfs_ucred_t * __restrict ucred)
{
	mode_t mask;
	int i;

	/* The file must be a directory to begin with. */
	if (!S_ISDIR(node->fn_mode)) return ENOTDIR;

	/* The root user may access anything at all. */
	if (ucred->vu_uid == ROOT_UID) return OK;

	/* Otherwise, the caller must have search access to the directory. */
	if (ucred->vu_uid == node->fn_uid) mask = S_IXUSR;
	else if (ucred->vu_gid == node->fn_gid) mask = S_IXGRP;
	else {
		mask = S_IXOTH;

		for (i = 0; i < ucred->vu_ngroups; i++) {
			if (ucred->vu_sgroups[i] == node->fn_gid) {
				mask = S_IXGRP;

				break;
			}
		}
	}

	return (node->fn_mode & mask) ? OK : EACCES;
}

/*
 * Get the next path component from a path.  Return the start and end of the
 * component into the path, and store its name in a null-terminated buffer.
 */
static int
next_name(char ** ptr, char ** start, char * __restrict name, size_t namesize)
{
	char *p;
	unsigned int i;

	/* Skip one or more path separator characters; they have no effect. */
	for (p = *ptr; *p == '/'; p++);

	*start = p;

	if (*p) {
		/*
		 * Copy as much of the name as possible, up to the next path
		 * separator.  Return an error if the name does not fit.
		 */
		for (i = 0; *p && *p != '/' && i < namesize; p++, i++)
			name[i] = *p;

		if (i >= namesize)
			return ENAMETOOLONG;

		name[i] = 0;
	} else
		/* An empty path component implies the current directory. */
		strlcpy(name, ".", namesize);

	/*
	 * Return a pointer to the first character not part of this component.
	 * This would typically be either the path separator or a null.
	 */
	*ptr = p;
	return OK;
}

/*
 * Given a symbolic link, resolve and return the contents of the link, followed
 * by the remaining part of the path that has not yet been resolved (the tail).
 * Note that the tail points into the given destination buffer.
 */
static int
resolve_link(const struct fsdriver * __restrict fdp, ino_t ino_nr, char * pptr,
	size_t size, char * tail)
{
	struct fsdriver_data data;
	char path[PATH_MAX];
	ssize_t r;

	data.endpt = SELF;
	data.ptr = path;
	data.size = sizeof(path) - 1;

	/*
	 * Let the file system the symbolic link.  Note that the resulting path
	 * is not null-terminated.
	 */
	if ((r = fdp->fdr_rdlink(ino_nr, &data, data.size)) < 0)
		return r;

	/* Append the remaining part of the original path to be resolved. */
	if (r + strlen(tail) >= sizeof(path))
		return ENAMETOOLONG;

	strlcpy(&path[r], tail, sizeof(path) - r);

	/* Copy back the result to the original buffer. */
	strlcpy(pptr, path, size);

	return OK;
}

/*
 * Process a LOOKUP request from VFS.
 */
int
fsdriver_lookup(const struct fsdriver * __restrict fdp,
	const message * __restrict m_in, message * __restrict m_out)
{
	ino_t dir_ino_nr, root_ino_nr;
	struct fsdriver_node cur_node, next_node;
	char path[PATH_MAX], name[NAME_MAX+1];
	char *ptr, *last;
	cp_grant_id_t path_grant;
	vfs_ucred_t ucred;
	unsigned int flags;
	size_t path_len, path_size;
	int r, r2, going_up, is_mountpt, symloop;

	if (fdp->fdr_lookup == NULL)
		return ENOSYS;

	dir_ino_nr = m_in->m_vfs_fs_lookup.dir_ino;
	root_ino_nr = m_in->m_vfs_fs_lookup.root_ino;
	path_grant = m_in->m_vfs_fs_lookup.grant_path;
	path_size = m_in->m_vfs_fs_lookup.path_size;
	path_len = m_in->m_vfs_fs_lookup.path_len;
	flags = m_in->m_vfs_fs_lookup.flags;

	/* Fetch the path name. */
	if ((r = fsdriver_getname(m_in->m_source, path_grant, path_len, path,
	    sizeof(path), FALSE /*not_empty*/)) != OK)
		return r;

	/* Fetch the caller's credentials. */
	if (flags & PATH_GET_UCRED) {
		if (m_in->m_vfs_fs_lookup.ucred_size != sizeof(ucred)) {
			printf("fsdriver: bad credential structure\n");

			return EINVAL;
		}

		if ((r = sys_safecopyfrom(m_in->m_source,
		    m_in->m_vfs_fs_lookup.grant_ucred, 0, (vir_bytes)&ucred,
		    (phys_bytes)m_in->m_vfs_fs_lookup.ucred_size)) != OK)
			return r;
	} else {
		ucred.vu_uid = m_in->m_vfs_fs_lookup.uid;
		ucred.vu_gid = m_in->m_vfs_fs_lookup.gid;
		ucred.vu_ngroups = 0;
	}

	/* Start the actual lookup by referencing the starting inode. */
	strlcpy(name, ".", sizeof(name)); /* allow a non-const argument */

	r = fdp->fdr_lookup(dir_ino_nr, name, &cur_node, &is_mountpt);
	if (r != OK)
		return r;

	symloop = 0;

	/* Whenever we leave this loop, 'cur_node' holds a referenced inode. */
	for (ptr = last = path; *ptr != 0; ) {
		/*
		 * Get the next path component. The result is a non-empty
		 * string.
		 */
		if ((r = next_name(&ptr, &last, name, sizeof(name))) != OK)
			break;

		if (is_mountpt) {
			/*
			 * If we start off from a mount point, the next path
			 * component *must* cause us to go up.  Anything else
			 * is a protocol violation.
			 */
			if (strcmp(name, "..")) {
				r = EINVAL;
				break;
			}
		} else {
			/*
			 * There is more path to process.  That means that the
			 * current file is now being accessed as a directory.
			 * Check type and permissions.
			 */
			if ((r = access_as_dir(&cur_node, &ucred)) != OK)
				break;
		}

		/* A single-dot component resolves to the current directory. */
		if (!strcmp(name, "."))
			continue;

		/* A dot-dot component resolves to the parent directory. */
		going_up = !strcmp(name, "..");

		if (going_up) {
			/*
			 * The parent of the process's root directory is the
			 * same root directory.  All processes have a root
			 * directory, so this check also covers the case of
			 * going up from the global system root directory.
			 */
			if (cur_node.fn_ino_nr == root_ino_nr)
				continue;

			/*
			 * Going up from the file system's root directory means
			 * crossing mount points.  As indicated, the root file
			 * system is already covered by the check above.
			 */
			if (cur_node.fn_ino_nr == fsdriver_root) {
				ptr = last;

				r = ELEAVEMOUNT;
				break;
			}
		}

		/*
		 * Descend into a child node or go up to a parent node, by
		 * asking the actual file system to perform a one-step
		 * resolution.  The result, if successful, is an open
		 * (referenced) inode.
		 */
		if ((r = fdp->fdr_lookup(cur_node.fn_ino_nr, name, &next_node,
		    &is_mountpt)) != OK)
			break;

		/* Sanity check: a parent node must always be a directory. */
		if (going_up && !S_ISDIR(next_node.fn_mode))
			panic("fsdriver: ascending into nondirectory");

		/*
		 * Perform symlink resolution, unless the symlink is the last
		 * path component and VFS is asking us not to resolve it.
		 */
		if (S_ISLNK(next_node.fn_mode) &&
		    (*ptr || !(flags & PATH_RET_SYMLINK))) {
			/*
			 * Resolve the symlink, and append the remaining
			 * unresolved part of the path.
			 */
			if (++symloop < _POSIX_SYMLOOP_MAX)
				r = resolve_link(fdp, next_node.fn_ino_nr,
				    path, sizeof(path), ptr);
			else
				r = ELOOP;

			if (fdp->fdr_putnode != NULL)
				fdp->fdr_putnode(next_node.fn_ino_nr, 1);

			if (r != OK)
				break;

			ptr = path;

			/* If the symlink is absolute, return it to VFS. */
			if (path[0] == '/') {
				r = ESYMLINK;
				break;
			}

			continue;
		}

		/* We have found a new node.  Continue from this node. */
		if (fdp->fdr_putnode != NULL)
			fdp->fdr_putnode(cur_node.fn_ino_nr, 1);

		cur_node = next_node;

		/*
		 * If the new node is a mount point, yield to another file
		 * system.
		 */
		if (is_mountpt) {
			r = EENTERMOUNT;
			break;
		}
	}

	/* For special redirection errors, we need to return extra details. */
	if (r == EENTERMOUNT || r == ELEAVEMOUNT || r == ESYMLINK) {
		/* Copy back the path if we resolved at least one symlink. */
		if (symloop > 0) {
			if ((path_len = strlen(path) + 1) > path_size)
				return ENAMETOOLONG;

			r2 = sys_safecopyto(m_in->m_source, path_grant, 0,
			    (vir_bytes)path, (phys_bytes)path_len);
		} else
			r2 = OK;

		if (r2 == OK) {
			m_out->m_fs_vfs_lookup.offset = (int)(ptr - path);
			m_out->m_fs_vfs_lookup.symloop = symloop;

			if (r == EENTERMOUNT)
				m_out->m_fs_vfs_lookup.inode =
				    cur_node.fn_ino_nr;
		} else
			r = r2;
	}

	/*
	 * On success, leave the resulting file open and return its details.
	 * If an error occurred, close the file and return error information.
	 */
	if (r == OK) {
		m_out->m_fs_vfs_lookup.inode = cur_node.fn_ino_nr;
		m_out->m_fs_vfs_lookup.mode = cur_node.fn_mode;
		m_out->m_fs_vfs_lookup.file_size = cur_node.fn_size;
		m_out->m_fs_vfs_lookup.uid = cur_node.fn_uid;
		m_out->m_fs_vfs_lookup.gid = cur_node.fn_gid;
		m_out->m_fs_vfs_lookup.device = cur_node.fn_dev;
	} else if (fdp->fdr_putnode != NULL)
		fdp->fdr_putnode(cur_node.fn_ino_nr, 1);

	return r;
}