1 /* This file contains the procedures that look up path names in the directory
2 * system and determine the inode number that goes with a given path name.
4 * The entry points into this file are
5 * eat_path: the 'main' routine of the path-to-inode conversion mechanism
6 * last_dir: find the final directory on a given path
7 * advance: parse one component of a path name
8 * search_dir: search a directory for a string and return its inode number
14 #include <minix/callnr.h>
15 #include <minix/endpoint.h>
17 #include <sys/types.h>
22 #include <minix/vfsif.h>
25 PUBLIC
char dot1
[2] = "."; /* used for search_dir to bypass the access */
26 PUBLIC
char dot2
[3] = ".."; /* permissions for . and .. */
28 FORWARD
_PROTOTYPE( char *get_name
, (char *name
, char string
[NAME_MAX
+1]) );
29 FORWARD
_PROTOTYPE( int ltraverse
, (struct inode
*rip
, char *suffix
) );
30 FORWARD
_PROTOTYPE( int parse_path
, (ino_t dir_ino
, ino_t root_ino
,
31 int flags
, struct inode
**res_inop
,
32 size_t *offsetp
, int *symlinkp
) );
35 /*===========================================================================*
37 *===========================================================================*/
38 PUBLIC
int fs_lookup()
40 cp_grant_id_t grant
, grant2
;
41 int r
, r1
, len
, flags
, symlinks
;
42 size_t offset
= 0, path_size
, cred_size
;
43 ino_t dir_ino
, root_ino
;
46 grant
= fs_m_in
.REQ_GRANT
;
47 path_size
= fs_m_in
.REQ_PATH_SIZE
; /* Size of the buffer */
48 len
= fs_m_in
.REQ_PATH_LEN
; /* including terminating nul */
49 dir_ino
= fs_m_in
.REQ_DIR_INO
;
50 root_ino
= fs_m_in
.REQ_ROOT_INO
;
51 flags
= fs_m_in
.REQ_FLAGS
;
54 if(len
> sizeof(user_path
)) return(E2BIG
); /* too big for buffer */
55 if(len
< 1) return(EINVAL
); /* too small */
57 /* Copy the pathname and set up caller's user and group id */
58 r
= sys_safecopyfrom(FS_PROC_NR
, grant
, /*offset*/ 0,
59 (vir_bytes
) user_path
, (phys_bytes
) len
, D
);
61 printf("MFS %s:%d sys_safecopyfrom failed: %d\n",
62 __FILE__
, __LINE__
, r
);
66 /* Verify this is a null-terminated path. */
67 if(user_path
[len
- 1] != '\0') return(EINVAL
);
69 if(flags
& PATH_GET_UCRED
) { /* Do we have to copy uid/gid credentials? */
71 grant2
= fs_m_in
.REQ_GRANT2
;
72 cred_size
= fs_m_in
.REQ_UCRED_SIZE
;
74 if (cred_size
> sizeof(credentials
)) return(EINVAL
); /* Too big. */
75 r
= sys_safecopyfrom(FS_PROC_NR
, grant2
, 0, (vir_bytes
) &credentials
,
76 (phys_bytes
) cred_size
, D
);
78 printf("MFS %s:%d sys_safecopyfrom failed: %d\n",
79 __FILE__
, __LINE__
, r
);
82 caller_uid
= credentials
.vu_uid
;
83 caller_gid
= credentials
.vu_gid
;
85 memset(&credentials
, 0, sizeof(credentials
));
86 caller_uid
= fs_m_in
.REQ_UID
;
87 caller_gid
= fs_m_in
.REQ_GID
;
92 r
= parse_path(dir_ino
, root_ino
, flags
, &rip
, &offset
, &symlinks
);
94 if(symlinks
!= 0 && (r
== ELEAVEMOUNT
|| r
== EENTERMOUNT
|| r
== ESYMLINK
)){
95 len
= strlen(user_path
)+1;
96 if(len
> path_size
) return(ENAMETOOLONG
);
98 r1
= sys_safecopyto(FS_PROC_NR
, grant
, 0, (vir_bytes
) user_path
,
101 printf("%s:%d fs_lookup: sys_safecopyto failed: %d\n",
102 __FILE__
, __LINE__
, r1
);
107 if(r
== ELEAVEMOUNT
|| r
== ESYMLINK
) {
108 /* Report offset and the error */
109 fs_m_out
.RES_OFFSET
= offset
;
110 fs_m_out
.RES_SYMLOOP
= symlinks
;
115 if (r
!= OK
&& r
!= EENTERMOUNT
) return(r
);
117 fs_m_out
.RES_INODE_NR
= rip
->i_num
;
118 fs_m_out
.RES_MODE
= rip
->i_mode
;
119 fs_m_out
.RES_FILE_SIZE_LO
= rip
->i_size
;
120 fs_m_out
.RES_SYMLOOP
= symlinks
;
121 fs_m_out
.RES_UID
= rip
->i_uid
;
122 fs_m_out
.RES_GID
= rip
->i_gid
;
124 /* This is only valid for block and character specials. But it doesn't
125 * cause any harm to set RES_DEV always. */
126 fs_m_out
.RES_DEV
= (dev_t
) rip
->i_zone
[0];
128 if(r
== EENTERMOUNT
) {
129 fs_m_out
.RES_OFFSET
= offset
;
130 put_inode(rip
); /* Only return a reference to the final object */
137 /*===========================================================================*
139 *===========================================================================*/
140 PRIVATE
int parse_path(dir_ino
, root_ino
, flags
, res_inop
, offsetp
, symlinkp
)
144 struct inode
**res_inop
;
148 /* Parse the path in user_path, starting at dir_ino. If the path is the empty
149 * string, just return dir_ino. It is upto the caller to treat an empty
150 * path in a special way. Otherwise, if the path consists of just one or
151 * more slash ('/') characters, the path is replaced with ".". Otherwise,
152 * just look up the first (or only) component in path after skipping any
155 int r
, leaving_mount
;
156 struct inode
*rip
, *dir_ip
;
157 char *cp
, *next_cp
; /* component and next component */
158 char component
[NAME_MAX
+1];
160 /* Start parsing path at the first component in user_path */
163 /* No symlinks encountered yet */
166 /* Find starting inode inode according to the request message */
167 if((rip
= find_inode(fs_dev
, dir_ino
)) == NIL_INODE
)
170 /* If dir has been removed return ENOENT. */
171 if (rip
->i_nlinks
== 0)
176 /* If the given start inode is a mountpoint, we must be here because the file
177 * system mounted on top returned an ELEAVEMOUNT error. In this case, we must
178 * only accept ".." as the first path component.
180 leaving_mount
= rip
->i_mountpoint
; /* True iff rip is a mountpoint */
182 /* Scan the path component by component. */
185 /* We're done; either the path was empty or we've parsed all
186 components of the path */
189 *offsetp
+= cp
- user_path
;
191 /* Return EENTERMOUNT if we are at a mount point */
192 if (rip
->i_mountpoint
) return(EENTERMOUNT
);
197 while(cp
[0] == '/') cp
++;
198 next_cp
= get_name(cp
, component
);
200 /* Special code for '..'. A process is not allowed to leave a chrooted
201 * environment. A lookup of '..' at the root of a mounted filesystem
202 * has to return ELEAVEMOUNT. In both cases, the caller needs search
203 * permission for the current inode, as it is used as directory.
205 if(strcmp(component
, "..") == 0) {
206 /* 'rip' is now accessed as directory */
207 if ((r
= forbidden(rip
, X_BIT
)) != OK
) {
212 if (rip
->i_num
== root_ino
) {
214 continue; /* Ignore the '..' at a process' root
215 and move on to the next component */
218 if (rip
->i_num
== ROOT_INODE
&& !rip
->i_sp
->s_is_root
) {
219 /* Climbing up to parent FS */
222 *offsetp
+= cp
- user_path
;
227 /* Only check for a mount point if we are not coming from one. */
228 if (!leaving_mount
&& rip
->i_mountpoint
) {
229 /* Going to enter a child FS */
232 *offsetp
+= cp
- user_path
;
236 /* There is more path. Keep parsing.
237 * If we're leaving a mountpoint, skip directory permission checks.
240 rip
= advance(dir_ip
, leaving_mount
? dot2
: component
, CHK_PERM
);
241 if(err_code
== ELEAVEMOUNT
|| err_code
== EENTERMOUNT
)
244 if (err_code
!= OK
) {
251 /* The call to advance() succeeded. Fetch next component. */
252 if (S_ISLNK(rip
->i_mode
)) {
254 if (next_cp
[0] == '\0' && (flags
& PATH_RET_SYMLINK
)) {
257 *offsetp
+= next_cp
- user_path
;
261 /* Extract path name from the symlink file */
262 r
= ltraverse(rip
, next_cp
);
266 /* Symloop limit reached? */
267 if (++(*symlinkp
) > SYMLOOP_MAX
)
276 if (next_cp
[0] == '/') {
288 cp
= next_cp
; /* Process subsequent component in next round */
293 /*===========================================================================*
295 *===========================================================================*/
296 PRIVATE
int ltraverse(rip
, suffix
)
297 register struct inode
*rip
; /* symbolic link */
298 char *suffix
; /* current remaining path. Has to point in the
302 /* Traverse a symbolic link. Copy the link text from the inode and insert
303 * the text into the path. Return error code or report success. Base
304 * directory has to be determined according to the first character of the
308 block_t blink
; /* block containing link text */
309 size_t llen
; /* length of link */
310 size_t slen
; /* length of suffix */
311 struct buf
*bp
; /* buffer containing link text */
312 char *sp
; /* start of link text */
316 if ((blink
= read_map(rip
, (off_t
) 0)) == NO_BLOCK
)
319 bp
= get_block(rip
->i_dev
, blink
, NORMAL
);
322 slen
= strlen(suffix
);
324 /* The path we're parsing looks like this:
325 * /already/processed/path/<link> or
326 * /already/processed/path/<link>/not/yet/processed/path
327 * After expanding the <link>, the path will look like
329 * <expandedlink>/not/yet/processed
330 * In both cases user_path must have enough room to hold <expandedlink>.
331 * However, in the latter case we have to move /not/yet/processed to the
332 * right place first, before we expand <link>. When strlen(<expandedlink>) is
333 * smaller than strlen(/already/processes/path), we move the suffix to the
334 * left. Is strlen(<expandedlink>) greater then we move it to the right. Else
337 if (slen
> 0) { /* Do we have path after the link? */
338 /* For simplicity we require that suffix starts with a slash */
339 if (suffix
[0] != '/') {
340 panic("ltraverse: suffix does not start with a slash");
343 /* To be able to expand the <link>, we have to move the 'suffix'
344 * to the right place. */
345 if (slen
+ llen
+ 1 > sizeof(user_path
))
346 return(ENAMETOOLONG
);/* <expandedlink>+suffix+\0 does not fit*/
347 if (suffix
-user_path
!= llen
) /* Move suffix left or right if needed */
348 memmove(&user_path
[llen
], suffix
, slen
+1);
350 if (llen
+ 1 > sizeof(user_path
))
351 return(ENAMETOOLONG
); /* <expandedlink> + \0 does not fix */
353 /* Set terminating nul */
354 user_path
[llen
]= '\0';
357 /* Everything is set, now copy the expanded link to user_path */
358 memmove(user_path
, sp
, llen
);
360 put_block(bp
, DIRECTORY_BLOCK
);
365 /*===========================================================================*
367 *===========================================================================*/
368 PUBLIC
struct inode
*advance(dirp
, string
, chk_perm
)
369 struct inode
*dirp
; /* inode for directory to be searched */
370 char string
[NAME_MAX
]; /* component name to look for */
371 int chk_perm
; /* check permissions when string is looked up*/
373 /* Given a directory and a component of a path, look up the component in
374 * the directory, find the inode, open it, and return a pointer to its inode
380 /* If 'string' is empty, return an error. */
381 if (string
[0] == '\0') {
386 /* Check for NIL_INODE. */
387 if (dirp
== NIL_INODE
) return(NIL_INODE
);
389 /* If 'string' is not present in the directory, signal error. */
390 if ( (err_code
= search_dir(dirp
, string
, &numb
, LOOK_UP
, chk_perm
)) != OK
) {
394 /* The component has been found in the directory. Get inode. */
395 if ( (rip
= get_inode(dirp
->i_dev
, (int) numb
)) == NIL_INODE
) {
399 /* The following test is for "mountpoint/.." where mountpoint is a
400 * mountpoint. ".." will refer to the root of the mounted filesystem,
401 * but has to become a reference to the parent of the 'mountpoint'
404 * This case is recognized by the looked up name pointing to a
405 * root inode, and the directory in which it is held being a
406 * root inode, _and_ the name[1] being '.'. (This is a test for '..'
409 if (rip
->i_num
== ROOT_INODE
) {
410 if (dirp
->i_num
== ROOT_INODE
) {
411 if (string
[1] == '.') {
412 if (!rip
->i_sp
->s_is_root
) {
413 /* Climbing up mountpoint */
414 err_code
= ELEAVEMOUNT
;
420 /* See if the inode is mounted on. If so, switch to root directory of the
421 * mounted file system. The super_block provides the linkage between the
422 * inode mounted on and the root directory of the mounted file system.
424 if (rip
!= NIL_INODE
&& rip
->i_mountpoint
) {
425 /* Mountpoint encountered, report it */
426 err_code
= EENTERMOUNT
;
433 /*===========================================================================*
435 *===========================================================================*/
436 PRIVATE
char *get_name(path_name
, string
)
437 char *path_name
; /* path name to parse */
438 char string
[NAME_MAX
+1]; /* component extracted from 'old_name' */
440 /* Given a pointer to a path name in fs space, 'path_name', copy the first
441 * component to 'string' (truncated if necessary, always nul terminated).
442 * A pointer to the string after the first component of the name as yet
443 * unparsed is returned. Roughly speaking,
444 * 'get_name' = 'path_name' - 'string'.
446 * This routine follows the standard convention that /usr/ast, /usr//ast,
447 * //usr///ast and /usr/ast/ are all equivalent.
454 /* Skip leading slashes */
455 while (cp
[0] == '/') cp
++;
457 /* Find the end of the first component */
459 while(ep
[0] != '\0' && ep
[0] != '/')
464 /* Truncate the amount to be copied if it exceeds NAME_MAX */
468 /* Special case of the string at cp is empty */
470 strcpy(string
, "."); /* Return "." */
472 memcpy(string
, cp
, len
);
480 /*===========================================================================*
482 *===========================================================================*/
483 PUBLIC
int search_dir(ldir_ptr
, string
, numb
, flag
, check_permissions
)
484 register struct inode
*ldir_ptr
; /* ptr to inode for dir to search */
485 char string
[NAME_MAX
]; /* component to search for */
486 ino_t
*numb
; /* pointer to inode number */
487 int flag
; /* LOOK_UP, ENTER, DELETE or IS_EMPTY */
488 int check_permissions
; /* check permissions when flag is !IS_EMPTY */
490 /* This function searches the directory whose inode is pointed to by 'ldip':
491 * if (flag == ENTER) enter 'string' in the directory with inode # '*numb';
492 * if (flag == DELETE) delete 'string' from the directory;
493 * if (flag == LOOK_UP) search for 'string' and return inode # in 'numb';
494 * if (flag == IS_EMPTY) return OK if only . and .. in dir else ENOTEMPTY;
496 * if 'string' is dot1 or dot2, no access permissions are checked.
499 register struct direct
*dp
= NULL
;
500 register struct buf
*bp
= NULL
;
501 int i
, r
, e_hit
, t
, match
;
504 unsigned new_slots
, old_slots
;
506 struct super_block
*sp
;
509 /* If 'ldir_ptr' is not a pointer to a dir inode, error. */
510 if ( (ldir_ptr
->i_mode
& I_TYPE
) != I_DIRECTORY
) {
516 if (flag
!= IS_EMPTY
) {
517 bits
= (flag
== LOOK_UP
? X_BIT
: W_BIT
| X_BIT
);
519 if (string
== dot1
|| string
== dot2
) {
520 if (flag
!= LOOK_UP
) r
= read_only(ldir_ptr
);
521 /* only a writable device is required. */
522 } else if(check_permissions
) {
523 r
= forbidden(ldir_ptr
, bits
); /* check access permissions */
526 if (r
!= OK
) return(r
);
528 /* Step through the directory one block at a time. */
529 old_slots
= (unsigned) (ldir_ptr
->i_size
/DIR_ENTRY_SIZE
);
532 match
= 0; /* set when a string match occurs */
534 for (pos
= 0; pos
< ldir_ptr
->i_size
; pos
+= ldir_ptr
->i_sp
->s_block_size
) {
535 b
= read_map(ldir_ptr
, pos
); /* get block number */
537 /* Since directories don't have holes, 'b' cannot be NO_BLOCK. */
538 bp
= get_block(ldir_ptr
->i_dev
, b
, NORMAL
); /* get a dir block */
541 panic("get_block returned NO_BLOCK");
543 /* Search a directory block. */
544 for (dp
= &bp
->b_dir
[0];
545 dp
< &bp
->b_dir
[NR_DIR_ENTRIES(ldir_ptr
->i_sp
->s_block_size
)];
547 if (++new_slots
> old_slots
) { /* not found, but room left */
548 if (flag
== ENTER
) e_hit
= TRUE
;
552 /* Match occurs if string found. */
553 if (flag
!= ENTER
&& dp
->d_ino
!= 0) {
554 if (flag
== IS_EMPTY
) {
555 /* If this test succeeds, dir is not empty. */
556 if (strcmp(dp
->d_name
, "." ) != 0 &&
557 strcmp(dp
->d_name
, "..") != 0) match
= 1;
559 if (strncmp(dp
->d_name
, string
, NAME_MAX
) == 0){
566 /* LOOK_UP or DELETE found what it wanted. */
568 if (flag
== IS_EMPTY
) r
= ENOTEMPTY
;
569 else if (flag
== DELETE
) {
570 /* Save d_ino for recovery. */
571 t
= NAME_MAX
- sizeof(ino_t
);
572 *((ino_t
*) &dp
->d_name
[t
]) = dp
->d_ino
;
573 dp
->d_ino
= 0; /* erase entry */
575 ldir_ptr
->i_update
|= CTIME
| MTIME
;
576 ldir_ptr
->i_dirt
= DIRTY
;
578 sp
= ldir_ptr
->i_sp
; /* 'flag' is LOOK_UP */
579 *numb
= conv4(sp
->s_native
, (int) dp
->d_ino
);
581 put_block(bp
, DIRECTORY_BLOCK
);
585 /* Check for free slot for the benefit of ENTER. */
586 if (flag
== ENTER
&& dp
->d_ino
== 0) {
587 e_hit
= TRUE
; /* we found a free slot */
592 /* The whole block has been searched or ENTER has a free slot. */
593 if (e_hit
) break; /* e_hit set if ENTER can be performed now */
594 put_block(bp
, DIRECTORY_BLOCK
); /* otherwise, continue searching dir */
597 /* The whole directory has now been searched. */
599 return(flag
== IS_EMPTY
? OK
: ENOENT
);
602 /* This call is for ENTER. If no free slot has been found so far, try to
605 if (e_hit
== FALSE
) { /* directory is full and no room left in last block */
606 new_slots
++; /* increase directory size by 1 entry */
607 if (new_slots
== 0) return(EFBIG
); /* dir size limited by slot count */
608 if ( (bp
= new_block(ldir_ptr
, ldir_ptr
->i_size
)) == NIL_BUF
)
614 /* 'bp' now points to a directory block with space. 'dp' points to slot. */
615 (void) memset(dp
->d_name
, 0, (size_t) NAME_MAX
); /* clear entry */
616 for (i
= 0; i
< NAME_MAX
&& string
[i
]; i
++) dp
->d_name
[i
] = string
[i
];
618 dp
->d_ino
= conv4(sp
->s_native
, (int) *numb
);
620 put_block(bp
, DIRECTORY_BLOCK
);
621 ldir_ptr
->i_update
|= CTIME
| MTIME
; /* mark mtime for update later */
622 ldir_ptr
->i_dirt
= DIRTY
;
623 if (new_slots
> old_slots
) {
624 ldir_ptr
->i_size
= (off_t
) new_slots
* DIR_ENTRY_SIZE
;
625 /* Send the change to disk if the directory is extended. */
626 if (extended
) rw_inode(ldir_ptr
, WRITING
);