pkgin_all: script to auto-install all packages
[minix.git] / lib / libpuffs / path.c
blobbac1a1d040b95f6e89276c9390242e24ea5c0124
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()
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 = (cp_grant_id_t) fs_m_in.REQ_GRANT;
48 path_size = (size_t) fs_m_in.REQ_PATH_SIZE; /* Size of the buffer */
49 len = (int) fs_m_in.REQ_PATH_LEN; /* including terminating nul */
50 dir_ino = (ino_t) fs_m_in.REQ_DIR_INO;
51 root_ino = (ino_t) fs_m_in.REQ_ROOT_INO;
52 flags = (int) fs_m_in.REQ_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 = (uid_t) fs_m_in.REQ_UID;
69 caller_gid = (gid_t) fs_m_in.REQ_GID;
70 } else {
71 if((r=fs_lookup_credentials(&credentials,
72 &caller_uid, &caller_gid,
73 (cp_grant_id_t) fs_m_in.REQ_GRANT2,
74 (size_t) fs_m_in.REQ_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.RES_OFFSET = offset;
95 fs_m_out.RES_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.RES_INODE_NR = pn->pn_va.va_fileid;
110 fs_m_out.RES_MODE = pn->pn_va.va_mode;
111 fs_m_out.RES_FILE_SIZE_LO = pn->pn_va.va_size;
112 fs_m_out.RES_SYMLOOP = symlinks;
113 fs_m_out.RES_UID = pn->pn_va.va_uid;
114 fs_m_out.RES_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 set RES_DEV always. */
118 fs_m_out.RES_DEV = pn->pn_va.va_rdev;
120 if (r == EENTERMOUNT) {
121 fs_m_out.RES_OFFSET = offset;
124 return(r);
128 /*===========================================================================*
129 * parse_path *
130 *===========================================================================*/
131 static int parse_path(dir_ino, root_ino, flags, res_inop, offsetp, symlinkp)
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;
139 /* Parse the path in user_path, starting at dir_ino. If the path is the empty
140 * string, just return dir_ino. It is upto the caller to treat an empty
141 * path in a special way. Otherwise, if the path consists of just one or
142 * more slash ('/') characters, the path is replaced with ".". Otherwise,
143 * just look up the first (or only) component in path after skipping any
144 * leading slashes.
146 int r, leaving_mount;
147 struct puffs_node *pn, *pn_dir;
148 char *cp, *next_cp; /* component and next component */
149 char component[NAME_MAX+1];
151 /* Start parsing path at the first component in user_path */
152 cp = user_path;
154 /* No symlinks encountered yet */
155 *symlinkp = 0;
157 /* Find starting pnode according to the request message */
158 /* XXX it's deffinitely OK to use nodewalk here */
159 if ((pn = puffs_pn_nodewalk(global_pu, 0, &dir_ino)) == NULL) {
160 lpuffs_debug("nodewalk failed\n");
161 return(ENOENT);
164 /* If dir has been removed return ENOENT. */
165 if (pn->pn_va.va_nlink == NO_LINK) return(ENOENT);
167 /* If the given start pnode is a mountpoint, we must be here because the file
168 * system mounted on top returned an ELEAVEMOUNT error. In this case, we must
169 * only accept ".." as the first path component.
171 leaving_mount = pn->pn_mountpoint; /* True iff pn is a mountpoint */
173 /* Scan the path component by component. */
174 while (TRUE) {
175 if (cp[0] == '\0') {
176 /* We're done; either the path was empty or we've parsed all
177 components of the path */
179 *res_inop = pn;
180 *offsetp += cp - user_path;
182 /* Return EENTERMOUNT if we are at a mount point */
183 if (pn->pn_mountpoint) return(EENTERMOUNT);
185 return(OK);
188 while(cp[0] == '/') cp++;
189 next_cp = get_name(cp, component);
190 if (next_cp == NULL) {
191 return(err_code);
194 /* Special code for '..'. A process is not allowed to leave a chrooted
195 * environment. A lookup of '..' at the root of a mounted filesystem
196 * has to return ELEAVEMOUNT. In both cases, the caller needs search
197 * permission for the current pnode, as it is used as directory.
199 if (strcmp(component, "..") == 0) {
200 /* 'pn' is now accessed as directory */
201 if ((r = forbidden(pn, X_BIT)) != OK) {
202 return(r);
205 if (pn->pn_va.va_fileid == root_ino) {
206 cp = next_cp;
207 continue; /* Ignore the '..' at a process' root
208 and move on to the next component */
211 if (pn->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid
212 && !is_root_fs) {
213 /* Climbing up to parent FS */
214 *offsetp += cp - user_path;
215 return(ELEAVEMOUNT);
219 /* Only check for a mount point if we are not coming from one. */
220 if (!leaving_mount && pn->pn_mountpoint) {
221 /* Going to enter a child FS */
223 *res_inop = pn;
224 *offsetp += cp - user_path;
225 return(EENTERMOUNT);
228 /* There is more path. Keep parsing.
229 * If we're leaving a mountpoint, skip directory permission checks.
231 pn_dir = pn;
232 if ((pn_dir->pn_va.va_mode & I_TYPE) != I_DIRECTORY) {
233 return(ENOTDIR);
235 pn = advance(pn_dir, leaving_mount ? dot2 : component, CHK_PERM);
236 if (err_code == ELEAVEMOUNT || err_code == EENTERMOUNT)
237 err_code = OK;
239 if (err_code != OK) {
240 return(err_code);
243 assert(pn != NULL);
245 leaving_mount = 0;
247 /* The call to advance() succeeded. Fetch next component. */
248 if (S_ISLNK(pn->pn_va.va_mode)) {
249 if (next_cp[0] == '\0' && (flags & PATH_RET_SYMLINK)) {
250 *res_inop = pn;
251 *offsetp += next_cp - user_path;
252 return(OK);
255 /* Extract path name from the symlink file */
256 r = ltraverse(pn, next_cp);
257 next_cp = user_path;
258 *offsetp = 0;
260 /* Symloop limit reached? */
261 if (++(*symlinkp) > SYMLOOP_MAX)
262 r = ELOOP;
264 if (r != OK)
265 return(r);
267 if (next_cp[0] == '/')
268 return(ESYMLINK);
270 pn = pn_dir;
273 cp = next_cp; /* Process subsequent component in next round */
279 /*===========================================================================*
280 * ltraverse *
281 *===========================================================================*/
282 static int ltraverse(pn, suffix)
283 register struct puffs_node *pn;/* symbolic link */
284 char *suffix; /* current remaining path. Has to point in the
285 * user_path buffer
288 /* Traverse a symbolic link. Copy the link text from the pnode and insert
289 * the text into the path. Return error code or report success. Base
290 * directory has to be determined according to the first character of the
291 * new pathname.
293 int r;
294 char sp[PATH_MAX];
295 size_t llen = PATH_MAX;/* length of link */
296 size_t slen; /* length of suffix */
297 PUFFS_MAKECRED(pcr, &global_kcred);
300 if (!S_ISLNK(pn->pn_va.va_mode))
301 r = EACCES;
303 if (global_pu->pu_ops.puffs_node_readlink == NULL)
304 return(EINVAL);
306 if (global_pu->pu_ops.puffs_node_readlink(global_pu, pn, pcr, sp, &llen) != 0)
307 return(EINVAL);
309 slen = strlen(suffix);
311 /* The path we're parsing looks like this:
312 * /already/processed/path/<link> or
313 * /already/processed/path/<link>/not/yet/processed/path
314 * After expanding the <link>, the path will look like
315 * <expandedlink> or
316 * <expandedlink>/not/yet/processed
317 * In both cases user_path must have enough room to hold <expandedlink>.
318 * However, in the latter case we have to move /not/yet/processed to the
319 * right place first, before we expand <link>. When strlen(<expandedlink>) is
320 * smaller than strlen(/already/processes/path), we move the suffix to the
321 * left. Is strlen(<expandedlink>) greater then we move it to the right. Else
322 * we do nothing.
325 if (slen > 0) { /* Do we have path after the link? */
326 /* For simplicity we require that suffix starts with a slash */
327 if (suffix[0] != '/') {
328 panic("ltraverse: suffix does not start with a slash");
331 /* To be able to expand the <link>, we have to move the 'suffix'
332 * to the right place.
334 if (slen + llen + 1 > sizeof(user_path))
335 return(ENAMETOOLONG);/* <expandedlink>+suffix+\0 does not fit*/
336 if ((unsigned)(suffix - user_path) != llen) {
337 /* Move suffix left or right if needed */
338 memmove(&user_path[llen], suffix, slen+1);
340 } else {
341 if (llen + 1 > sizeof(user_path))
342 return(ENAMETOOLONG); /* <expandedlink> + \0 does not fit */
344 /* Set terminating nul */
345 user_path[llen]= '\0';
348 /* Everything is set, now copy the expanded link to user_path */
349 memmove(user_path, sp, llen);
351 return(OK);
355 /*===========================================================================*
356 * advance *
357 *===========================================================================*/
358 struct puffs_node *advance(pn_dir, string, chk_perm)
359 struct puffs_node *pn_dir; /* pnode for directory to be searched */
360 char string[NAME_MAX + 1]; /* component name to look for */
361 int chk_perm; /* check permissions when string is looked up*/
363 /* Given a directory and a component of a path, look up the component in
364 * the directory, find the pnode, open it, and return a pointer to its pnode
365 * slot.
366 * TODO: instead of string, should get pcn.
368 struct puffs_node *pn;
370 struct puffs_newinfo pni;
372 struct puffs_kcn pkcnp;
373 PUFFS_MAKECRED(pcr, &global_kcred);
374 struct puffs_cn pcn = {&pkcnp, (struct puffs_cred *) __UNCONST(pcr), {0,0,0}};
376 enum vtype node_vtype;
377 voff_t size;
378 dev_t rdev;
379 int error;
381 err_code = OK;
383 /* If 'string' is empty, return an error. */
384 if (string[0] == '\0') {
385 err_code = ENOENT;
386 return(NULL);
389 /* Check for NULL. */
390 if (pn_dir == NULL)
391 return(NULL);
393 if (chk_perm) {
394 /* Just search permission is checked */
395 if (forbidden(pn_dir, X_BIT)) {
396 err_code = EACCES;
397 return(NULL);
401 if (strcmp(string, ".") == 0) {
402 /* Otherwise we will fall into trouble: path for pnode to be looked up
403 * will be parent path (same pnode as the one to be looked up) +
404 * requested path. E.g. after several lookups we might get advance
405 * for "." with parent path "/././././././././.".
407 * Another problem is that after lookup pnode will be added
408 * to the pu_pnodelst, which already contains pnode instance for this
409 * pnode. It will cause lot of troubles.
411 return pn_dir;
414 pni.pni_cookie = (void** )&pn;
415 pni.pni_vtype = &node_vtype;
416 pni.pni_size = &size;
417 pni.pni_rdev = &rdev;
419 pcn.pcn_namelen = strlen(string);
420 assert(pcn.pcn_namelen <= MAXPATHLEN);
421 strcpy(pcn.pcn_name, string);
423 if (buildpath) {
424 if (puffs_path_pcnbuild(global_pu, &pcn, pn_dir) != 0) {
425 lpuffs_debug("pathbuild error\n");
426 err_code = ENOENT;
427 return(NULL);
431 /* lookup *must* be present */
432 error = global_pu->pu_ops.puffs_node_lookup(global_pu, pn_dir,
433 &pni, &pcn);
435 if (buildpath) {
436 if (error) {
437 global_pu->pu_pathfree(global_pu, &pcn.pcn_po_full);
438 err_code = ENOENT;
439 return(NULL);
440 } else {
441 struct puffs_node *_pn;
444 * did we get a new node or a
445 * recycled node?
447 _pn = PU_CMAP(global_pu, pn);
448 if (_pn->pn_po.po_path == NULL)
449 _pn->pn_po = pcn.pcn_po_full;
450 else
451 global_pu->pu_pathfree(global_pu, &pcn.pcn_po_full);
455 if (error) {
456 err_code = error < 0 ? error : -error;
457 return(NULL);
458 } else {
459 /* In MFS/ext2 it's set by search_dir, puffs_node_lookup error codes are unclear,
460 * so we use error variable there
462 err_code = OK;
465 assert(pn != NULL);
467 /* The following test is for "mountpoint/.." where mountpoint is a
468 * mountpoint. ".." will refer to the root of the mounted filesystem,
469 * but has to become a reference to the parent of the 'mountpoint'
470 * directory.
472 * This case is recognized by the looked up name pointing to a
473 * root pnode, and the directory in which it is held being a
474 * root pnode, _and_ the name[1] being '.'. (This is a test for '..'
475 * and excludes '.'.)
477 if (pn->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid) {
478 if (pn_dir->pn_va.va_fileid == global_pu->pu_pn_root->pn_va.va_fileid) {
479 if (string[1] == '.') {
480 if (!is_root_fs) {
481 /* Climbing up mountpoint */
482 err_code = ELEAVEMOUNT;
488 /* See if the pnode is mounted on. If so, switch to root directory of the
489 * mounted file system. The super_block provides the linkage between the
490 * pnode mounted on and the root directory of the mounted file system.
492 if (pn->pn_mountpoint) {
493 /* Mountpoint encountered, report it */
494 err_code = EENTERMOUNT;
497 return(pn);
501 /*===========================================================================*
502 * get_name *
503 *===========================================================================*/
504 static char *get_name(path_name, string)
505 char *path_name; /* path name to parse */
506 char string[NAME_MAX+1]; /* component extracted from 'old_name' */
508 /* Given a pointer to a path name in fs space, 'path_name', copy the first
509 * component to 'string' (truncated if necessary, always nul terminated).
510 * A pointer to the string after the first component of the name as yet
511 * unparsed is returned. Roughly speaking,
512 * 'get_name' = 'path_name' - 'string'.
514 * This routine follows the standard convention that /usr/ast, /usr//ast,
515 * //usr///ast and /usr/ast/ are all equivalent.
517 * If len of component is greater, than allowed, then return 0.
519 size_t len;
520 char *cp, *ep;
522 cp = path_name;
524 /* Skip leading slashes */
525 while (cp[0] == '/') cp++;
527 /* Find the end of the first component */
528 ep = cp;
529 while (ep[0] != '\0' && ep[0] != '/')
530 ep++;
532 len = (size_t) (ep - cp);
534 /* XXX we don't check name_max of fileserver (probably we can't) */
535 if (len > NAME_MAX) {
536 err_code = ENAMETOOLONG;
537 return(NULL);
540 /* Special case of the string at cp is empty */
541 if (len == 0)
542 strcpy(string, "."); /* Return "." */
543 else {
544 memcpy(string, cp, len);
545 string[len]= '\0';
548 return(ep);