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
10 * Created (MFS based):
11 * February 2010 (Evgeniy Ivanov)
17 #include <minix/endpoint.h>
19 #include <sys/types.h>
23 #include <minix/vfsif.h>
24 #include <minix/libminixfs.h>
26 char dot1
[2] = "."; /* used for search_dir to bypass the access */
27 char dot2
[3] = ".."; /* permissions for . and .. */
29 static char *get_name(char *name
, char string
[NAME_MAX
+1]);
30 static int ltraverse(struct inode
*rip
, char *suffix
);
31 static int parse_path(ino_t dir_ino
, ino_t root_ino
, int flags
, struct
32 inode
**res_inop
, size_t *offsetp
, int *symlinkp
);
34 /*===========================================================================*
36 *===========================================================================*/
40 int r
, r1
, flags
, symlinks
;
42 size_t offset
= 0, path_size
;
43 ino_t dir_ino
, root_ino
;
46 grant
= (cp_grant_id_t
) fs_m_in
.REQ_GRANT
;
47 path_size
= (size_t) fs_m_in
.REQ_PATH_SIZE
; /* Size of the buffer */
48 len
= (int) fs_m_in
.REQ_PATH_LEN
; /* including terminating nul */
49 dir_ino
= (ino_t
) fs_m_in
.REQ_DIR_INO
;
50 root_ino
= (ino_t
) fs_m_in
.REQ_ROOT_INO
;
51 flags
= (int) fs_m_in
.REQ_FLAGS
;
54 if(len
> sizeof(user_path
)) return(E2BIG
); /* too big for buffer */
55 if(len
== 0) return(EINVAL
); /* too small */
57 /* Copy the pathname and set up caller's user and group id */
58 r
= sys_safecopyfrom(VFS_PROC_NR
, grant
, /*offset*/ 0,
59 (vir_bytes
) user_path
, (size_t) len
);
60 if(r
!= OK
) return(r
);
62 /* Verify this is a null-terminated path. */
63 if(user_path
[len
- 1] != '\0') return(EINVAL
);
65 memset(&credentials
, 0, sizeof(credentials
));
66 if(!(flags
& PATH_GET_UCRED
)) { /* Do we have to copy uid/gid credentials? */
67 caller_uid
= (uid_t
) fs_m_in
.REQ_UID
;
68 caller_gid
= (gid_t
) fs_m_in
.REQ_GID
;
70 if((r
=fs_lookup_credentials(&credentials
,
71 &caller_uid
, &caller_gid
,
72 (cp_grant_id_t
) fs_m_in
.REQ_GRANT2
,
73 (size_t) fs_m_in
.REQ_UCRED_SIZE
)) != OK
)
79 r
= parse_path(dir_ino
, root_ino
, flags
, &rip
, &offset
, &symlinks
);
81 if(symlinks
!= 0 && (r
== ELEAVEMOUNT
|| r
== EENTERMOUNT
|| r
== ESYMLINK
)){
82 len
= strlen(user_path
)+1;
83 if(len
> path_size
) return(ENAMETOOLONG
);
85 r1
= sys_safecopyto(VFS_PROC_NR
, grant
, (vir_bytes
) 0,
86 (vir_bytes
) user_path
, (size_t) len
);
87 if (r1
!= OK
) return(r1
);
90 if(r
== ELEAVEMOUNT
|| r
== ESYMLINK
) {
91 /* Report offset and the error */
92 fs_m_out
.RES_OFFSET
= offset
;
93 fs_m_out
.RES_SYMLOOP
= symlinks
;
98 if (r
!= OK
&& r
!= EENTERMOUNT
) return(r
);
100 fs_m_out
.RES_INODE_NR
= rip
->i_num
;
101 fs_m_out
.RES_MODE
= rip
->i_mode
;
102 fs_m_out
.RES_FILE_SIZE_LO
= rip
->i_size
;
103 fs_m_out
.RES_SYMLOOP
= symlinks
;
104 fs_m_out
.RES_UID
= rip
->i_uid
;
105 fs_m_out
.RES_GID
= rip
->i_gid
;
107 /* This is only valid for block and character specials. But it doesn't
108 * cause any harm to set RES_DEV always. */
109 fs_m_out
.RES_DEV
= (dev_t
) rip
->i_block
[0];
111 if(r
== EENTERMOUNT
) {
112 fs_m_out
.RES_OFFSET
= offset
;
113 put_inode(rip
); /* Only return a reference to the final object */
120 /*===========================================================================*
122 *===========================================================================*/
123 static int parse_path(dir_ino
, root_ino
, flags
, res_inop
, offsetp
, symlinkp
)
127 struct inode
**res_inop
;
131 /* Parse the path in user_path, starting at dir_ino. If the path is the empty
132 * string, just return dir_ino. It is upto the caller to treat an empty
133 * path in a special way. Otherwise, if the path consists of just one or
134 * more slash ('/') characters, the path is replaced with ".". Otherwise,
135 * just look up the first (or only) component in path after skipping any
138 int r
, leaving_mount
;
139 struct inode
*rip
, *dir_ip
;
140 char *cp
, *next_cp
; /* component and next component */
141 char component
[NAME_MAX
+1];
143 /* Start parsing path at the first component in user_path */
146 /* No symlinks encountered yet */
149 /* Find starting inode inode according to the request message */
150 if((rip
= find_inode(fs_dev
, dir_ino
)) == NULL
)
153 /* If dir has been removed return ENOENT. */
154 if (rip
->i_links_count
== NO_LINK
) return(ENOENT
);
158 /* If the given start inode is a mountpoint, we must be here because the file
159 * system mounted on top returned an ELEAVEMOUNT error. In this case, we must
160 * only accept ".." as the first path component.
162 leaving_mount
= rip
->i_mountpoint
; /* True iff rip is a mountpoint */
164 /* Scan the path component by component. */
167 /* We're done; either the path was empty or we've parsed all
168 components of the path */
171 *offsetp
+= cp
- user_path
;
173 /* Return EENTERMOUNT if we are at a mount point */
174 if (rip
->i_mountpoint
) return(EENTERMOUNT
);
179 while(cp
[0] == '/') cp
++;
180 next_cp
= get_name(cp
, component
);
181 if (next_cp
== NULL
) {
186 /* Special code for '..'. A process is not allowed to leave a chrooted
187 * environment. A lookup of '..' at the root of a mounted filesystem
188 * has to return ELEAVEMOUNT. In both cases, the caller needs search
189 * permission for the current inode, as it is used as directory.
191 if(strcmp(component
, "..") == 0) {
192 /* 'rip' is now accessed as directory */
193 if ((r
= forbidden(rip
, X_BIT
)) != OK
) {
198 if (rip
->i_num
== root_ino
) {
200 continue; /* Ignore the '..' at a process' root
201 and move on to the next component */
204 if (rip
->i_num
== ROOT_INODE
&& !rip
->i_sp
->s_is_root
) {
205 /* Climbing up to parent FS */
208 *offsetp
+= cp
- user_path
;
213 /* Only check for a mount point if we are not coming from one. */
214 if (!leaving_mount
&& rip
->i_mountpoint
) {
215 /* Going to enter a child FS */
218 *offsetp
+= cp
- user_path
;
222 /* There is more path. Keep parsing.
223 * If we're leaving a mountpoint, skip directory permission checks.
226 rip
= advance(dir_ip
, leaving_mount
? dot2
: component
, CHK_PERM
);
227 if(err_code
== ELEAVEMOUNT
|| err_code
== EENTERMOUNT
)
230 if (err_code
!= OK
) {
237 /* The call to advance() succeeded. Fetch next component. */
238 if (S_ISLNK(rip
->i_mode
)) {
240 if (next_cp
[0] == '\0' && (flags
& PATH_RET_SYMLINK
)) {
243 *offsetp
+= next_cp
- user_path
;
247 /* Extract path name from the symlink file */
248 r
= ltraverse(rip
, next_cp
);
252 /* Symloop limit reached? */
253 if (++(*symlinkp
) > SYMLOOP_MAX
)
262 if (next_cp
[0] == '/') {
274 cp
= next_cp
; /* Process subsequent component in next round */
280 /*===========================================================================*
282 *===========================================================================*/
283 static int ltraverse(rip
, suffix
)
284 register struct inode
*rip
; /* symbolic link */
285 char *suffix
; /* current remaining path. Has to point in the
289 /* Traverse a symbolic link. Copy the link text from the inode and insert
290 * the text into the path. Return error code or report success. Base
291 * directory has to be determined according to the first character of the
295 block_t blink
; /* block containing link text */
296 size_t llen
; /* length of link */
297 size_t slen
; /* length of suffix */
298 struct buf
*bp
; /* buffer containing link text */
299 const char *sp
; /* start of link text */
301 llen
= (size_t) rip
->i_size
;
303 if (llen
>= MAX_FAST_SYMLINK_LENGTH
) {
305 if ((blink
= read_map(rip
, (off_t
) 0)) == NO_BLOCK
)
307 bp
= get_block(rip
->i_dev
, blink
, NORMAL
);
310 /* fast symlink, stored in inode */
311 sp
= (const char*) rip
->i_block
;
314 slen
= strlen(suffix
);
316 /* The path we're parsing looks like this:
317 * /already/processed/path/<link> or
318 * /already/processed/path/<link>/not/yet/processed/path
319 * After expanding the <link>, the path will look like
321 * <expandedlink>/not/yet/processed
322 * In both cases user_path must have enough room to hold <expandedlink>.
323 * However, in the latter case we have to move /not/yet/processed to the
324 * right place first, before we expand <link>. When strlen(<expandedlink>) is
325 * smaller than strlen(/already/processes/path), we move the suffix to the
326 * left. Is strlen(<expandedlink>) greater then we move it to the right. Else
330 if (slen
> 0) { /* Do we have path after the link? */
331 /* For simplicity we require that suffix starts with a slash */
332 if (suffix
[0] != '/') {
333 panic("ltraverse: suffix does not start with a slash");
336 /* To be able to expand the <link>, we have to move the 'suffix'
337 * to the right place.
339 if (slen
+ llen
+ 1 > sizeof(user_path
))
340 return(ENAMETOOLONG
);/* <expandedlink>+suffix+\0 does not fit*/
341 if ((unsigned)(suffix
- user_path
) != llen
) {
342 /* Move suffix left or right if needed */
343 memmove(&user_path
[llen
], suffix
, slen
+1);
346 if (llen
+ 1 > sizeof(user_path
))
347 return(ENAMETOOLONG
); /* <expandedlink> + \0 does not fit */
349 /* Set terminating nul */
350 user_path
[llen
]= '\0';
353 /* Everything is set, now copy the expanded link to user_path */
354 memmove(user_path
, sp
, llen
);
356 if (llen
>= MAX_FAST_SYMLINK_LENGTH
)
357 put_block(bp
, DIRECTORY_BLOCK
);
363 /*===========================================================================*
365 *===========================================================================*/
366 struct inode
*advance(dirp
, string
, chk_perm
)
367 struct inode
*dirp
; /* inode for directory to be searched */
368 char string
[NAME_MAX
+ 1]; /* component name to look for */
369 int chk_perm
; /* check permissions when string is looked up*/
371 /* Given a directory and a component of a path, look up the component in
372 * the directory, find the inode, open it, and return a pointer to its inode
378 /* If 'string' is empty, return an error. */
379 if (string
[0] == '\0') {
384 /* Check for NULL. */
385 if (dirp
== NULL
) return(NULL
);
387 /* If 'string' is not present in the directory, signal error. */
388 if ( (err_code
= search_dir(dirp
, string
, &numb
, LOOK_UP
,
389 chk_perm
, 0)) != OK
) {
393 /* The component has been found in the directory. Get inode. */
394 if ( (rip
= get_inode(dirp
->i_dev
, (int) numb
)) == NULL
) {
398 /* The following test is for "mountpoint/.." where mountpoint is a
399 * mountpoint. ".." will refer to the root of the mounted filesystem,
400 * but has to become a reference to the parent of the 'mountpoint'
403 * This case is recognized by the looked up name pointing to a
404 * root inode, and the directory in which it is held being a
405 * root inode, _and_ the name[1] being '.'. (This is a test for '..'
408 if (rip
->i_num
== ROOT_INODE
) {
409 if (dirp
->i_num
== ROOT_INODE
) {
410 if (string
[1] == '.') {
411 if (!rip
->i_sp
->s_is_root
) {
412 /* Climbing up mountpoint */
413 err_code
= ELEAVEMOUNT
;
419 /* See if the inode is mounted on. If so, switch to root directory of the
420 * mounted file system. The super_block provides the linkage between the
421 * inode mounted on and the root directory of the mounted file system.
423 if (rip
->i_mountpoint
) {
424 /* Mountpoint encountered, report it */
425 err_code
= EENTERMOUNT
;
432 /*===========================================================================*
434 *===========================================================================*/
435 static char *get_name(path_name
, string
)
436 char *path_name
; /* path name to parse */
437 char string
[NAME_MAX
+1]; /* component extracted from 'old_name' */
439 /* Given a pointer to a path name in fs space, 'path_name', copy the first
440 * component to 'string' (truncated if necessary, always nul terminated).
441 * A pointer to the string after the first component of the name as yet
442 * unparsed is returned. Roughly speaking,
443 * 'get_name' = 'path_name' - 'string'.
445 * This routine follows the standard convention that /usr/ast, /usr//ast,
446 * //usr///ast and /usr/ast/ are all equivalent.
448 * If len of component is greater, than allowed, then return 0.
455 /* Skip leading slashes */
456 while (cp
[0] == '/') cp
++;
458 /* Find the end of the first component */
460 while(ep
[0] != '\0' && ep
[0] != '/')
463 len
= (size_t) (ep
- cp
);
465 if (len
> NAME_MAX
|| len
> EXT2_NAME_MAX
) {
466 err_code
= ENAMETOOLONG
;
470 /* Special case of the string at cp is empty */
472 strlcpy(string
, ".", NAME_MAX
+ 1); /* Return "." */
474 memcpy(string
, cp
, len
);
482 /*===========================================================================*
484 *===========================================================================*/
485 int search_dir(ldir_ptr
, string
, numb
, flag
, check_permissions
, ftype
)
486 register struct inode
*ldir_ptr
; /* ptr to inode for dir to search */
487 char string
[NAME_MAX
+ 1]; /* component to search for */
488 ino_t
*numb
; /* pointer to inode number */
489 int flag
; /* LOOK_UP, ENTER, DELETE or IS_EMPTY */
490 int check_permissions
; /* check permissions when flag is !IS_EMPTY */
491 int ftype
; /* used when ENTER and
492 * INCOMPAT_FILETYPE */
494 /* This function searches the directory whose inode is pointed to by 'ldip':
495 * if (flag == ENTER) enter 'string' in the directory with inode # '*numb';
496 * if (flag == DELETE) delete 'string' from the directory;
497 * if (flag == LOOK_UP) search for 'string' and return inode # in 'numb';
498 * if (flag == IS_EMPTY) return OK if only . and .. in dir else ENOTEMPTY;
500 * if 'string' is dot1 or dot2, no access permissions are checked.
503 register struct ext2_disk_dir_desc
*dp
= NULL
;
504 register struct ext2_disk_dir_desc
*prev_dp
= NULL
;
505 register struct buf
*bp
= NULL
;
506 int i
, r
, e_hit
, t
, match
;
512 int required_space
= 0;
515 /* If 'ldir_ptr' is not a pointer to a dir inode, error. */
516 if ( (ldir_ptr
->i_mode
& I_TYPE
) != I_DIRECTORY
) {
522 if (flag
!= IS_EMPTY
) {
523 bits
= (flag
== LOOK_UP
? X_BIT
: W_BIT
| X_BIT
);
525 if (string
== dot1
|| string
== dot2
) {
526 if (flag
!= LOOK_UP
) r
= read_only(ldir_ptr
);
527 /* only a writable device is required. */
528 } else if(check_permissions
) {
529 r
= forbidden(ldir_ptr
, bits
); /* check access permissions */
532 if (r
!= OK
) return(r
);
536 match
= 0; /* set when a string match occurs */
540 string_len
= strlen(string
);
541 required_space
= MIN_DIR_ENTRY_SIZE
+ string_len
;
542 required_space
+= (required_space
& 0x03) == 0 ? 0 :
543 (DIR_ENTRY_ALIGN
- (required_space
& 0x03) );
545 if (ldir_ptr
->i_last_dpos
< ldir_ptr
->i_size
&&
546 ldir_ptr
->i_last_dentry_size
<= required_space
)
547 pos
= ldir_ptr
->i_last_dpos
;
550 for (; pos
< ldir_ptr
->i_size
; pos
+= ldir_ptr
->i_sp
->s_block_size
) {
551 b
= read_map(ldir_ptr
, pos
); /* get block number */
553 /* Since directories don't have holes, 'b' cannot be NO_BLOCK. */
554 bp
= get_block(ldir_ptr
->i_dev
, b
, NORMAL
); /* get a dir block */
555 prev_dp
= NULL
; /* New block - new first dentry, so no prev. */
558 panic("get_block returned NO_BLOCK");
561 /* Search a directory block.
562 * Note, we set prev_dp at the end of the loop.
564 for (dp
= (struct ext2_disk_dir_desc
*) &b_data(bp
);
565 CUR_DISC_DIR_POS(dp
, &b_data(bp
)) < ldir_ptr
->i_sp
->s_block_size
;
566 dp
= NEXT_DISC_DIR_DESC(dp
) ) {
567 /* Match occurs if string found. */
568 if (flag
!= ENTER
&& dp
->d_ino
!= NO_ENTRY
) {
569 if (flag
== IS_EMPTY
) {
570 /* If this test succeeds, dir is not empty. */
571 if (ansi_strcmp(dp
->d_name
, ".", dp
->d_name_len
) != 0 &&
572 ansi_strcmp(dp
->d_name
, "..", dp
->d_name_len
) != 0) match
= 1;
574 if (ansi_strcmp(dp
->d_name
, string
, dp
->d_name_len
) == 0){
581 /* LOOK_UP or DELETE found what it wanted. */
583 if (flag
== IS_EMPTY
) r
= ENOTEMPTY
;
584 else if (flag
== DELETE
) {
585 if (dp
->d_name_len
>= sizeof(ino_t
)) {
586 /* Save d_ino for recovery. */
587 t
= dp
->d_name_len
- sizeof(ino_t
);
588 *((ino_t
*) &dp
->d_name
[t
]) = dp
->d_ino
;
590 dp
->d_ino
= NO_ENTRY
; /* erase entry */
593 /* If we don't support HTree (directory index),
594 * which is fully compatible ext2 feature,
595 * we should reset EXT2_INDEX_FL, when modify
596 * linked directory structure.
598 * @TODO: actually we could just reset it for
599 * each directory, but I added if() to not
600 * forget about it later, when add HTree
603 if (!HAS_COMPAT_FEATURE(ldir_ptr
->i_sp
,
605 ldir_ptr
->i_flags
&= ~EXT2_INDEX_FL
;
606 if (pos
< ldir_ptr
->i_last_dpos
) {
607 ldir_ptr
->i_last_dpos
= pos
;
608 ldir_ptr
->i_last_dentry_size
=
609 conv2(le_CPU
, dp
->d_rec_len
);
611 ldir_ptr
->i_update
|= CTIME
| MTIME
;
612 ldir_ptr
->i_dirt
= IN_DIRTY
;
613 /* Now we have cleared dentry, if it's not
614 * the first one, merge it with previous one.
615 * Since we assume, that existing dentry must be
616 * correct, there is no way to spann a data block.
619 u16_t temp
= conv2(le_CPU
,
621 temp
+= conv2(le_CPU
,
623 prev_dp
->d_rec_len
= conv2(le_CPU
,
627 /* 'flag' is LOOK_UP */
628 *numb
= (ino_t
) conv4(le_CPU
, dp
->d_ino
);
630 put_block(bp
, DIRECTORY_BLOCK
);
634 /* Check for free slot for the benefit of ENTER. */
635 if (flag
== ENTER
&& dp
->d_ino
== NO_ENTRY
) {
636 /* we found a free slot, check if it has enough space */
637 if (required_space
<= conv2(le_CPU
, dp
->d_rec_len
)) {
638 e_hit
= TRUE
; /* we found a free slot */
642 /* Can we shrink dentry? */
643 if (flag
== ENTER
&& required_space
<= DIR_ENTRY_SHRINK(dp
)) {
644 /* Shrink directory and create empty slot, now
645 * dp->d_rec_len = DIR_ENTRY_ACTUAL_SIZE + DIR_ENTRY_SHRINK.
647 int new_slot_size
= conv2(le_CPU
, dp
->d_rec_len
);
648 int actual_size
= DIR_ENTRY_ACTUAL_SIZE(dp
);
649 new_slot_size
-= actual_size
;
650 dp
->d_rec_len
= conv2(le_CPU
, actual_size
);
651 dp
= NEXT_DISC_DIR_DESC(dp
);
652 dp
->d_rec_len
= conv2(le_CPU
, new_slot_size
);
653 /* if we fail before writing real ino */
654 dp
->d_ino
= NO_ENTRY
;
656 e_hit
= TRUE
; /* we found a free slot */
663 /* The whole block has been searched or ENTER has a free slot. */
664 if (e_hit
) break; /* e_hit set if ENTER can be performed now */
665 put_block(bp
, DIRECTORY_BLOCK
); /* otherwise, continue searching dir */
668 /* The whole directory has now been searched. */
670 return(flag
== IS_EMPTY
? OK
: ENOENT
);
673 /* When ENTER next time, start searching for free slot from
674 * i_last_dpos. It gives solid performance improvement.
676 ldir_ptr
->i_last_dpos
= pos
;
677 ldir_ptr
->i_last_dentry_size
= required_space
;
679 /* This call is for ENTER. If no free slot has been found so far, try to
682 if (e_hit
== FALSE
) { /* directory is full and no room left in last block */
683 new_slots
++; /* increase directory size by 1 entry */
684 if ( (bp
= new_block(ldir_ptr
, ldir_ptr
->i_size
)) == NULL
)
686 dp
= (struct ext2_disk_dir_desc
*) &b_data(bp
);
687 dp
->d_rec_len
= conv2(le_CPU
, ldir_ptr
->i_sp
->s_block_size
);
688 dp
->d_name_len
= DIR_ENTRY_MAX_NAME_LEN(dp
); /* for failure */
692 /* 'bp' now points to a directory block with space. 'dp' points to slot. */
693 dp
->d_name_len
= string_len
;
694 for (i
= 0; i
< NAME_MAX
&& i
< dp
->d_name_len
&& string
[i
]; i
++)
695 dp
->d_name
[i
] = string
[i
];
696 dp
->d_ino
= (int) conv4(le_CPU
, *numb
);
697 if (HAS_INCOMPAT_FEATURE(ldir_ptr
->i_sp
, INCOMPAT_FILETYPE
)) {
698 /* Convert ftype (from inode.i_mode) to dp->d_file_type */
699 if (ftype
== I_REGULAR
)
700 dp
->d_file_type
= EXT2_FT_REG_FILE
;
701 else if (ftype
== I_DIRECTORY
)
702 dp
->d_file_type
= EXT2_FT_DIR
;
703 else if (ftype
== I_SYMBOLIC_LINK
)
704 dp
->d_file_type
= EXT2_FT_SYMLINK
;
705 else if (ftype
== I_BLOCK_SPECIAL
)
706 dp
->d_file_type
= EXT2_FT_BLKDEV
;
707 else if (ftype
== I_CHAR_SPECIAL
)
708 dp
->d_file_type
= EXT2_FT_CHRDEV
;
709 else if (ftype
== I_NAMED_PIPE
)
710 dp
->d_file_type
= EXT2_FT_FIFO
;
712 dp
->d_file_type
= EXT2_FT_UNKNOWN
;
715 put_block(bp
, DIRECTORY_BLOCK
);
716 ldir_ptr
->i_update
|= CTIME
| MTIME
; /* mark mtime for update later */
717 ldir_ptr
->i_dirt
= IN_DIRTY
;
719 if (new_slots
== 1) {
720 ldir_ptr
->i_size
+= (off_t
) conv2(le_CPU
, dp
->d_rec_len
);
721 /* Send the change to disk if the directory is extended. */
722 if (extended
) rw_inode(ldir_ptr
, WRITING
);