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)
11 #include <sys/cdefs.h>
13 #include <sys/types.h>
21 #include <minix/endpoint.h>
22 #include <minix/vfsif.h>
23 #include <minix/libminixfs.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 /*===========================================================================*
37 *===========================================================================*/
41 int r
, r1
, flags
, symlinks
;
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
;
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
;
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
)
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
;
100 if (r
!= OK
&& r
!= EENTERMOUNT
) {
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
;
128 /*===========================================================================*
130 *===========================================================================*/
131 static int parse_path(dir_ino
, root_ino
, flags
, res_inop
, offsetp
, symlinkp
)
135 struct puffs_node
**res_inop
;
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
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 */
154 /* No symlinks encountered yet */
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");
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. */
176 /* We're done; either the path was empty or we've parsed all
177 components of the path */
180 *offsetp
+= cp
- user_path
;
182 /* Return EENTERMOUNT if we are at a mount point */
183 if (pn
->pn_mountpoint
) return(EENTERMOUNT
);
188 while(cp
[0] == '/') cp
++;
189 next_cp
= get_name(cp
, component
);
190 if (next_cp
== NULL
) {
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
) {
205 if (pn
->pn_va
.va_fileid
== root_ino
) {
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
213 /* Climbing up to parent FS */
214 *offsetp
+= cp
- user_path
;
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 */
224 *offsetp
+= cp
- user_path
;
228 /* There is more path. Keep parsing.
229 * If we're leaving a mountpoint, skip directory permission checks.
232 if ((pn_dir
->pn_va
.va_mode
& I_TYPE
) != I_DIRECTORY
) {
235 pn
= advance(pn_dir
, leaving_mount
? dot2
: component
, CHK_PERM
);
236 if (err_code
== ELEAVEMOUNT
|| err_code
== EENTERMOUNT
)
239 if (err_code
!= OK
) {
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
)) {
251 *offsetp
+= next_cp
- user_path
;
255 /* Extract path name from the symlink file */
256 r
= ltraverse(pn
, next_cp
);
260 /* Symloop limit reached? */
261 if (++(*symlinkp
) > SYMLOOP_MAX
)
267 if (next_cp
[0] == '/')
273 cp
= next_cp
; /* Process subsequent component in next round */
279 /*===========================================================================*
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
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
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
))
303 if (global_pu
->pu_ops
.puffs_node_readlink
== NULL
)
306 if (global_pu
->pu_ops
.puffs_node_readlink(global_pu
, pn
, pcr
, sp
, &llen
) != 0)
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
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
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);
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
);
355 /*===========================================================================*
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
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
;
383 /* If 'string' is empty, return an error. */
384 if (string
[0] == '\0') {
389 /* Check for NULL. */
394 /* Just search permission is checked */
395 if (forbidden(pn_dir
, X_BIT
)) {
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.
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
);
424 if (puffs_path_pcnbuild(global_pu
, &pcn
, pn_dir
) != 0) {
425 lpuffs_debug("pathbuild error\n");
431 /* lookup *must* be present */
432 error
= global_pu
->pu_ops
.puffs_node_lookup(global_pu
, pn_dir
,
437 global_pu
->pu_pathfree(global_pu
, &pcn
.pcn_po_full
);
441 struct puffs_node
*_pn
;
444 * did we get a new node or a
447 _pn
= PU_CMAP(global_pu
, pn
);
448 if (_pn
->pn_po
.po_path
== NULL
)
449 _pn
->pn_po
= pcn
.pcn_po_full
;
451 global_pu
->pu_pathfree(global_pu
, &pcn
.pcn_po_full
);
456 err_code
= error
< 0 ? error
: -error
;
459 /* In MFS/ext2 it's set by search_dir, puffs_node_lookup error codes are unclear,
460 * so we use error variable there
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'
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 '..'
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] == '.') {
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
;
501 /*===========================================================================*
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.
524 /* Skip leading slashes */
525 while (cp
[0] == '/') cp
++;
527 /* Find the end of the first component */
529 while (ep
[0] != '\0' && ep
[0] != '/')
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
;
540 /* Special case of the string at cp is empty */
542 strcpy(string
, "."); /* Return "." */
544 memcpy(string
, cp
, len
);