custom message type for VM_INFO
[minix3.git] / lib / libpuffs / path.c
blobeece52592e896a9fd4becf83c2a3cf5db4ba2b97
1 /*
2 * This file contains the procedures that look up path names in the directory
3 * system and determine the pnode number that goes with a given path name.
5 * Created (based on MFS):
6 * June 2011 (Evgeniy Ivanov)
7 */
9 #include "fs.h"
11 #include <sys/cdefs.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
15 #include <assert.h>
16 #include <errno.h>
17 #include <puffs.h>
18 #include <stdlib.h>
19 #include <string.h>
21 #include <minix/endpoint.h>
22 #include <minix/vfsif.h>
23 #include <minix/libminixfs.h>
25 #include "puffs.h"
26 #include "puffs_priv.h"
28 char dot2[3] = ".."; /* permissions for . and .. */
30 static char *get_name(char *name, char string[NAME_MAX+1]);
31 static int ltraverse(struct puffs_node *pn, char *suffix);
32 static int parse_path(ino_t dir_ino, ino_t root_ino, int flags, struct
33 puffs_node **res_inop, size_t *offsetp, int *symlinkp);
35 /*===========================================================================*
36 * fs_lookup *
37 *===========================================================================*/
38 int fs_lookup(void)
40 cp_grant_id_t grant;
41 int r, r1, flags, symlinks;
42 unsigned int len;
43 size_t offset = 0, path_size;
44 ino_t dir_ino, root_ino;
45 struct puffs_node *pn;
47 grant = fs_m_in.m_vfs_fs_lookup.grant_path;
48 path_size = fs_m_in.m_vfs_fs_lookup.path_size; /* Size of the buffer */
49 len = fs_m_in.m_vfs_fs_lookup.path_len; /* including terminating nul */
50 dir_ino = fs_m_in.m_vfs_fs_lookup.dir_ino;
51 root_ino = fs_m_in.m_vfs_fs_lookup.root_ino;
52 flags = fs_m_in.m_vfs_fs_lookup.flags;
54 /* Check length. */
55 if (len > sizeof(user_path)) return(E2BIG); /* too big for buffer */
56 if (len == 0) return(EINVAL); /* too small */
58 /* Copy the pathname and set up caller's user and group id */
59 r = sys_safecopyfrom(VFS_PROC_NR, grant, /*offset*/ 0,
60 (vir_bytes) user_path, (size_t) len);
61 if (r != OK) return(r);
63 /* Verify this is a null-terminated path. */
64 if (user_path[len - 1] != '\0') return(EINVAL);
66 memset(&credentials, 0, sizeof(credentials));
67 if(!(flags & PATH_GET_UCRED)) { /* Do we have to copy uid/gid credentials? */
68 caller_uid = fs_m_in.m_vfs_fs_lookup.uid;
69 caller_gid = fs_m_in.m_vfs_fs_lookup.gid;
70 } else {
71 if((r=fs_lookup_credentials(&credentials,
72 &caller_uid, &caller_gid,
73 fs_m_in.m_vfs_fs_lookup.grant_ucred,
74 fs_m_in.m_vfs_fs_lookup.ucred_size)) != OK)
75 return r;
79 /* Lookup pnode */
80 pn = NULL;
81 r = parse_path(dir_ino, root_ino, flags, &pn, &offset, &symlinks);
83 if (symlinks != 0 && (r == ELEAVEMOUNT || r == EENTERMOUNT || r == ESYMLINK)){
84 len = strlen(user_path)+1;
85 if (len > path_size) return(ENAMETOOLONG);
87 r1 = sys_safecopyto(VFS_PROC_NR, grant, (vir_bytes) 0,
88 (vir_bytes) user_path, (size_t) len);
89 if (r1 != OK) return(r1);
92 if (r == ELEAVEMOUNT || r == ESYMLINK) {
93 /* Report offset and the error */
94 fs_m_out.m_fs_vfs_lookup.offset = offset;
95 fs_m_out.m_fs_vfs_lookup.symloop = symlinks;
97 return(r);
100 if (r != OK && r != EENTERMOUNT) {
101 return(r);
104 if (r == OK) {
105 /* Open pnode */
106 pn->pn_count++;
109 fs_m_out.m_fs_vfs_lookup.inode = pn->pn_va.va_fileid;
110 fs_m_out.m_fs_vfs_lookup.mode = pn->pn_va.va_mode;
111 fs_m_out.m_fs_vfs_lookup.file_size = pn->pn_va.va_size;
112 fs_m_out.m_fs_vfs_lookup.symloop = symlinks;
113 fs_m_out.m_fs_vfs_lookup.uid = pn->pn_va.va_uid;
114 fs_m_out.m_fs_vfs_lookup.gid = pn->pn_va.va_gid;
116 /* This is only valid for block and character specials. But it doesn't
117 * cause any harm to always set the device field. */
118 fs_m_out.m_fs_vfs_lookup.device = pn->pn_va.va_rdev;
120 if (r == EENTERMOUNT) {
121 fs_m_out.m_fs_vfs_lookup.offset = offset;
124 return(r);
128 /*===========================================================================*
129 * parse_path *
130 *===========================================================================*/
131 static int parse_path(
132 ino_t dir_ino,
133 ino_t root_ino,
134 int flags,
135 struct puffs_node **res_inop,
136 size_t *offsetp,
137 int *symlinkp
140 /* Parse the path in user_path, starting at dir_ino. If the path is the empty
141 * string, just return dir_ino. It is upto the caller to treat an empty
142 * path in a special way. Otherwise, if the path consists of just one or
143 * more slash ('/') characters, the path is replaced with ".". Otherwise,
144 * just look up the first (or only) component in path after skipping any
145 * leading slashes.
147 int r, leaving_mount;
148 struct puffs_node *pn, *pn_dir;
149 char *cp, *next_cp; /* component and next component */
150 char component[NAME_MAX+1];
152 /* Start parsing path at the first component in user_path */
153 cp = user_path;
155 /* No symlinks encountered yet */
156 *symlinkp = 0;
158 /* Find starting pnode according to the request message */
159 /* XXX it's deffinitely OK to use nodewalk here */
160 if ((pn = puffs_pn_nodewalk(global_pu, 0, &dir_ino)) == NULL) {
161 lpuffs_debug("nodewalk failed\n");
162 return(ENOENT);
165 /* If dir has been removed return ENOENT. */
166 if (pn->pn_va.va_nlink == NO_LINK) return(ENOENT);
168 /* If the given start pnode is a mountpoint, we must be here because the file
169 * system mounted on top returned an ELEAVEMOUNT error. In this case, we must
170 * only accept ".." as the first path component.
172 leaving_mount = pn->pn_mountpoint; /* True iff pn is a mountpoint */
174 /* Scan the path component by component. */
175 while (TRUE) {
176 if (cp[0] == '\0') {
177 /* We're done; either the path was empty or we've parsed all
178 components of the path */
180 *res_inop = pn;
181 *offsetp += cp - user_path;
183 /* Return EENTERMOUNT if we are at a mount point */
184 if (pn->pn_mountpoint) return(EENTERMOUNT);
186 return(OK);
189 while(cp[0] == '/') cp++;
190 next_cp = get_name(cp, component);
191 if (next_cp == NULL) {
192 return(err_code);
195 /* Special code for '..'. A process is not allowed to leave a chrooted
196 * environment. A lookup of '..' at the root of a mounted filesystem
197 * has to return ELEAVEMOUNT. In both cases, the caller needs search
198 * permission for the current pnode, as it is used as directory.
200 if (strcmp(component, "..") == 0) {
201 /* 'pn' is now accessed as directory */
202 if ((r = forbidden(pn, X_BIT)) != OK) {
203 return(r);
206 if (pn->pn_va.va_fileid == root_ino) {
207 cp = next_cp;
208 continue; /* Ignore the '..' at a process' root
209 and move on to the next component */
212 if (pn->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid
213 && !is_root_fs) {
214 /* Climbing up to parent FS */
215 *offsetp += cp - user_path;
216 return(ELEAVEMOUNT);
220 /* Only check for a mount point if we are not coming from one. */
221 if (!leaving_mount && pn->pn_mountpoint) {
222 /* Going to enter a child FS */
224 *res_inop = pn;
225 *offsetp += cp - user_path;
226 return(EENTERMOUNT);
229 /* There is more path. Keep parsing.
230 * If we're leaving a mountpoint, skip directory permission checks.
232 pn_dir = pn;
233 if ((pn_dir->pn_va.va_mode & I_TYPE) != I_DIRECTORY) {
234 return(ENOTDIR);
236 pn = advance(pn_dir, leaving_mount ? dot2 : component, CHK_PERM);
237 if (err_code == ELEAVEMOUNT || err_code == EENTERMOUNT)
238 err_code = OK;
240 if (err_code != OK) {
241 return(err_code);
244 assert(pn != NULL);
246 leaving_mount = 0;
248 /* The call to advance() succeeded. Fetch next component. */
249 if (S_ISLNK(pn->pn_va.va_mode)) {
250 if (next_cp[0] == '\0' && (flags & PATH_RET_SYMLINK)) {
251 *res_inop = pn;
252 *offsetp += next_cp - user_path;
253 return(OK);
256 /* Extract path name from the symlink file */
257 r = ltraverse(pn, next_cp);
258 next_cp = user_path;
259 *offsetp = 0;
261 /* Symloop limit reached? */
262 if (++(*symlinkp) > _POSIX_SYMLOOP_MAX)
263 r = ELOOP;
265 if (r != OK)
266 return(r);
268 if (next_cp[0] == '/')
269 return(ESYMLINK);
271 pn = pn_dir;
274 cp = next_cp; /* Process subsequent component in next round */
280 /*===========================================================================*
281 * ltraverse *
282 *===========================================================================*/
283 static int ltraverse(
284 struct puffs_node *pn, /* symbolic link */
285 char *suffix /* current remaining path. Has to point in the
286 * user_path buffer
290 /* Traverse a symbolic link. Copy the link text from the pnode and insert
291 * the text into the path. Return error code or report success. Base
292 * directory has to be determined according to the first character of the
293 * new pathname.
295 int r;
296 char sp[PATH_MAX];
297 size_t llen = PATH_MAX; /* length of link */
298 size_t slen; /* length of suffix */
299 PUFFS_MAKECRED(pcr, &global_kcred);
302 if (!S_ISLNK(pn->pn_va.va_mode))
303 r = EACCES;
305 if (global_pu->pu_ops.puffs_node_readlink == NULL)
306 return(EINVAL);
308 if (global_pu->pu_ops.puffs_node_readlink(global_pu, pn, pcr, sp, &llen) != 0)
309 return(EINVAL);
311 slen = strlen(suffix);
313 /* The path we're parsing looks like this:
314 * /already/processed/path/<link> or
315 * /already/processed/path/<link>/not/yet/processed/path
316 * After expanding the <link>, the path will look like
317 * <expandedlink> or
318 * <expandedlink>/not/yet/processed
319 * In both cases user_path must have enough room to hold <expandedlink>.
320 * However, in the latter case we have to move /not/yet/processed to the
321 * right place first, before we expand <link>. When strlen(<expandedlink>) is
322 * smaller than strlen(/already/processes/path), we move the suffix to the
323 * left. Is strlen(<expandedlink>) greater then we move it to the right. Else
324 * we do nothing.
327 if (slen > 0) { /* Do we have path after the link? */
328 /* For simplicity we require that suffix starts with a slash */
329 if (suffix[0] != '/') {
330 panic("ltraverse: suffix does not start with a slash");
333 /* To be able to expand the <link>, we have to move the 'suffix'
334 * to the right place.
336 if (slen + llen + 1 > sizeof(user_path))
337 return(ENAMETOOLONG);/* <expandedlink>+suffix+\0 does not fit*/
338 if ((unsigned)(suffix - user_path) != llen) {
339 /* Move suffix left or right if needed */
340 memmove(&user_path[llen], suffix, slen+1);
342 } else {
343 if (llen + 1 > sizeof(user_path))
344 return(ENAMETOOLONG); /* <expandedlink> + \0 does not fit */
346 /* Set terminating nul */
347 user_path[llen]= '\0';
350 /* Everything is set, now copy the expanded link to user_path */
351 memmove(user_path, sp, llen);
353 return(OK);
357 /*===========================================================================*
358 * advance *
359 *===========================================================================*/
360 struct puffs_node *advance(
361 struct puffs_node *pn_dir, /* pnode for directory to be searched */
362 char string[NAME_MAX + 1], /* component name to look for */
363 int chk_perm /* check permissions when string is
364 * looked up*/
367 /* Given a directory and a component of a path, look up the component in
368 * the directory, find the pnode, open it, and return a pointer to its pnode
369 * slot.
370 * TODO: instead of string, should get pcn.
372 struct puffs_node *pn;
374 struct puffs_newinfo pni;
376 struct puffs_kcn pkcnp;
377 PUFFS_MAKECRED(pcr, &global_kcred);
378 struct puffs_cn pcn = {&pkcnp, (struct puffs_cred *) __UNCONST(pcr), {0,0,0}};
380 enum vtype node_vtype;
381 voff_t size;
382 dev_t rdev;
383 int error;
385 err_code = OK;
387 /* If 'string' is empty, return an error. */
388 if (string[0] == '\0') {
389 err_code = ENOENT;
390 return(NULL);
393 /* Check for NULL. */
394 if (pn_dir == NULL)
395 return(NULL);
397 if (chk_perm) {
398 /* Just search permission is checked */
399 if (forbidden(pn_dir, X_BIT)) {
400 err_code = EACCES;
401 return(NULL);
405 if (strcmp(string, ".") == 0) {
406 /* Otherwise we will fall into trouble: path for pnode to be looked up
407 * will be parent path (same pnode as the one to be looked up) +
408 * requested path. E.g. after several lookups we might get advance
409 * for "." with parent path "/././././././././.".
411 * Another problem is that after lookup pnode will be added
412 * to the pu_pnodelst, which already contains pnode instance for this
413 * pnode. It will cause lot of troubles.
415 return pn_dir;
418 pni.pni_cookie = (void** )&pn;
419 pni.pni_vtype = &node_vtype;
420 pni.pni_size = &size;
421 pni.pni_rdev = &rdev;
423 pcn.pcn_namelen = strlen(string);
424 assert(pcn.pcn_namelen <= MAXPATHLEN);
425 strcpy(pcn.pcn_name, string);
427 if (buildpath) {
428 if (puffs_path_pcnbuild(global_pu, &pcn, pn_dir) != 0) {
429 lpuffs_debug("pathbuild error\n");
430 err_code = ENOENT;
431 return(NULL);
435 /* lookup *must* be present */
436 error = global_pu->pu_ops.puffs_node_lookup(global_pu, pn_dir,
437 &pni, &pcn);
439 if (buildpath) {
440 if (error) {
441 global_pu->pu_pathfree(global_pu, &pcn.pcn_po_full);
442 err_code = ENOENT;
443 return(NULL);
444 } else {
445 struct puffs_node *_pn;
448 * did we get a new node or a
449 * recycled node?
451 _pn = PU_CMAP(global_pu, pn);
452 if (_pn->pn_po.po_path == NULL)
453 _pn->pn_po = pcn.pcn_po_full;
454 else
455 global_pu->pu_pathfree(global_pu, &pcn.pcn_po_full);
459 if (error) {
460 err_code = error < 0 ? error : -error;
461 return(NULL);
462 } else {
463 /* In MFS/ext2 it's set by search_dir, puffs_node_lookup error codes are unclear,
464 * so we use error variable there
466 err_code = OK;
469 assert(pn != NULL);
471 /* The following test is for "mountpoint/.." where mountpoint is a
472 * mountpoint. ".." will refer to the root of the mounted filesystem,
473 * but has to become a reference to the parent of the 'mountpoint'
474 * directory.
476 * This case is recognized by the looked up name pointing to a
477 * root pnode, and the directory in which it is held being a
478 * root pnode, _and_ the name[1] being '.'. (This is a test for '..'
479 * and excludes '.'.)
481 if (pn->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid) {
482 if (pn_dir->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid) {
483 if (string[1] == '.') {
484 if (!is_root_fs) {
485 /* Climbing up mountpoint */
486 err_code = ELEAVEMOUNT;
492 /* See if the pnode is mounted on. If so, switch to root directory of the
493 * mounted file system. The super_block provides the linkage between the
494 * pnode mounted on and the root directory of the mounted file system.
496 if (pn->pn_mountpoint) {
497 /* Mountpoint encountered, report it */
498 err_code = EENTERMOUNT;
501 return(pn);
505 /*===========================================================================*
506 * get_name *
507 *===========================================================================*/
508 static char *get_name(
509 char *path_name, /* path name to parse */
510 char string[NAME_MAX+1] /* component extracted from 'old_name' */
513 /* Given a pointer to a path name in fs space, 'path_name', copy the first
514 * component to 'string' (truncated if necessary, always nul terminated).
515 * A pointer to the string after the first component of the name as yet
516 * unparsed is returned. Roughly speaking,
517 * 'get_name' = 'path_name' - 'string'.
519 * This routine follows the standard convention that /usr/ast, /usr//ast,
520 * //usr///ast and /usr/ast/ are all equivalent.
522 * If len of component is greater, than allowed, then return 0.
524 size_t len;
525 char *cp, *ep;
527 cp = path_name;
529 /* Skip leading slashes */
530 while (cp[0] == '/') cp++;
532 /* Find the end of the first component */
533 ep = cp;
534 while (ep[0] != '\0' && ep[0] != '/')
535 ep++;
537 len = (size_t) (ep - cp);
539 /* XXX we don't check name_max of fileserver (probably we can't) */
540 if (len > NAME_MAX) {
541 err_code = ENAMETOOLONG;
542 return(NULL);
545 /* Special case of the string at cp is empty */
546 if (len == 0)
547 strcpy(string, "."); /* Return "." */
548 else {
549 memcpy(string, cp, len);
550 string[len]= '\0';
553 return(ep);