mfs fix by tveerman from trunk.
[minix.git] / servers / mfs / path.c
blob3235205c4b56892cb61944c192f5aa4e14d86c5c
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
12 #include "fs.h"
13 #include "assert.h"
14 #include <string.h>
15 #include <minix/endpoint.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include "buf.h"
19 #include "inode.h"
20 #include "super.h"
21 #include <minix/vfsif.h>
24 PUBLIC char dot1[2] = "."; /* used for search_dir to bypass the access */
25 PUBLIC char dot2[3] = ".."; /* permissions for . and .. */
27 FORWARD _PROTOTYPE( char *get_name, (char *name, char string[NAME_MAX+1]) );
28 FORWARD _PROTOTYPE( int ltraverse, (struct inode *rip, char *suffix) );
29 FORWARD _PROTOTYPE( int parse_path, (ino_t dir_ino, ino_t root_ino,
30 int flags, struct inode **res_inop,
31 size_t *offsetp, int *symlinkp) );
34 /*===========================================================================*
35 * fs_lookup *
36 *===========================================================================*/
37 PUBLIC int fs_lookup()
39 cp_grant_id_t grant, grant2;
40 int r, r1, flags, symlinks;
41 unsigned int len;
42 size_t offset = 0, path_size, cred_size;
43 ino_t dir_ino, root_ino;
44 struct inode *rip;
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;
53 /* Check length. */
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(FS_PROC_NR, grant, /*offset*/ (vir_bytes) 0,
59 (vir_bytes) user_path, (size_t) len, D);
60 if(r != OK) return(r);
62 /* Verify this is a null-terminated path. */
63 if(user_path[len - 1] != '\0') return(EINVAL);
65 if(flags & PATH_GET_UCRED) { /* Do we have to copy uid/gid credentials? */
66 grant2 = (cp_grant_id_t) fs_m_in.REQ_GRANT2;
67 cred_size = (size_t) fs_m_in.REQ_UCRED_SIZE;
69 if (cred_size > sizeof(credentials)) return(EINVAL); /* Too big. */
70 r = sys_safecopyfrom(FS_PROC_NR, grant2, (vir_bytes) 0,
71 (vir_bytes) &credentials, cred_size, D);
72 if (r != OK) return(r);
74 caller_uid = credentials.vu_uid;
75 caller_gid = credentials.vu_gid;
76 } else {
77 memset(&credentials, 0, sizeof(credentials));
78 caller_uid = (uid_t) fs_m_in.REQ_UID;
79 caller_gid = (gid_t) fs_m_in.REQ_GID;
82 /* Lookup inode */
83 rip = NULL;
84 r = parse_path(dir_ino, root_ino, flags, &rip, &offset, &symlinks);
86 if(symlinks != 0 && (r == ELEAVEMOUNT || r == EENTERMOUNT || r == ESYMLINK)){
87 len = strlen(user_path)+1;
88 if(len > path_size) return(ENAMETOOLONG);
90 r1 = sys_safecopyto(FS_PROC_NR, grant, (vir_bytes) 0,
91 (vir_bytes) user_path, (size_t) len, D);
92 if(r1 != OK) return(r1);
95 if(r == ELEAVEMOUNT || r == ESYMLINK) {
96 /* Report offset and the error */
97 fs_m_out.RES_OFFSET = offset;
98 fs_m_out.RES_SYMLOOP = symlinks;
100 return(r);
103 if (r != OK && r != EENTERMOUNT) return(r);
105 fs_m_out.RES_INODE_NR = rip->i_num;
106 fs_m_out.RES_MODE = rip->i_mode;
107 fs_m_out.RES_FILE_SIZE_LO = rip->i_size;
108 fs_m_out.RES_SYMLOOP = symlinks;
109 fs_m_out.RES_UID = rip->i_uid;
110 fs_m_out.RES_GID = rip->i_gid;
112 /* This is only valid for block and character specials. But it doesn't
113 * cause any harm to set RES_DEV always. */
114 fs_m_out.RES_DEV = (dev_t) rip->i_zone[0];
116 if(r == EENTERMOUNT) {
117 fs_m_out.RES_OFFSET = offset;
118 put_inode(rip); /* Only return a reference to the final object */
121 return(r);
125 /*===========================================================================*
126 * parse_path *
127 *===========================================================================*/
128 PRIVATE int parse_path(dir_ino, root_ino, flags, res_inop, offsetp, symlinkp)
129 ino_t dir_ino;
130 ino_t root_ino;
131 int flags;
132 struct inode **res_inop;
133 size_t *offsetp;
134 int *symlinkp;
136 /* Parse the path in user_path, starting at dir_ino. If the path is the empty
137 * string, just return dir_ino. It is upto the caller to treat an empty
138 * path in a special way. Otherwise, if the path consists of just one or
139 * more slash ('/') characters, the path is replaced with ".". Otherwise,
140 * just look up the first (or only) component in path after skipping any
141 * leading slashes.
143 int r, leaving_mount;
144 struct inode *rip, *dir_ip;
145 char *cp, *next_cp; /* component and next component */
146 char component[NAME_MAX+1];
148 /* Start parsing path at the first component in user_path */
149 cp = user_path;
151 /* No symlinks encountered yet */
152 *symlinkp = 0;
154 /* Find starting inode inode according to the request message */
155 if((rip = find_inode(fs_dev, dir_ino)) == NULL)
156 return(ENOENT);
158 /* If dir has been removed return ENOENT. */
159 if (rip->i_nlinks == NO_LINK) return(ENOENT);
161 dup_inode(rip);
163 /* If the given start inode is a mountpoint, we must be here because the file
164 * system mounted on top returned an ELEAVEMOUNT error. In this case, we must
165 * only accept ".." as the first path component.
167 leaving_mount = rip->i_mountpoint; /* True iff rip is a mountpoint */
169 /* Scan the path component by component. */
170 while (TRUE) {
171 if(cp[0] == '\0') {
172 /* We're done; either the path was empty or we've parsed all
173 components of the path */
175 *res_inop = rip;
176 *offsetp += cp - user_path;
178 /* Return EENTERMOUNT if we are at a mount point */
179 if (rip->i_mountpoint) return(EENTERMOUNT);
181 return(OK);
184 while(cp[0] == '/') cp++;
185 next_cp = get_name(cp, component);
187 /* Special code for '..'. A process is not allowed to leave a chrooted
188 * environment. A lookup of '..' at the root of a mounted filesystem
189 * has to return ELEAVEMOUNT. In both cases, the caller needs search
190 * permission for the current inode, as it is used as directory.
192 if(strcmp(component, "..") == 0) {
193 /* 'rip' is now accessed as directory */
194 if ((r = forbidden(rip, X_BIT)) != OK) {
195 put_inode(rip);
196 return(r);
199 if (rip->i_num == root_ino) {
200 cp = next_cp;
201 continue; /* Ignore the '..' at a process' root
202 and move on to the next component */
205 if (rip->i_num == ROOT_INODE && !rip->i_sp->s_is_root) {
206 /* Climbing up to parent FS */
208 put_inode(rip);
209 *offsetp += cp - user_path;
210 return(ELEAVEMOUNT);
214 /* Only check for a mount point if we are not coming from one. */
215 if (!leaving_mount && rip->i_mountpoint) {
216 /* Going to enter a child FS */
218 *res_inop = rip;
219 *offsetp += cp - user_path;
220 return(EENTERMOUNT);
223 /* There is more path. Keep parsing.
224 * If we're leaving a mountpoint, skip directory permission checks.
226 dir_ip = rip;
227 rip = advance(dir_ip, leaving_mount ? dot2 : component, CHK_PERM);
228 if(err_code == ELEAVEMOUNT || err_code == EENTERMOUNT)
229 err_code = OK;
231 if (err_code != OK) {
232 put_inode(dir_ip);
233 return(err_code);
236 leaving_mount = 0;
238 /* The call to advance() succeeded. Fetch next component. */
239 if (S_ISLNK(rip->i_mode)) {
241 if (next_cp[0] == '\0' && (flags & PATH_RET_SYMLINK)) {
242 put_inode(dir_ip);
243 *res_inop = rip;
244 *offsetp += next_cp - user_path;
245 return(OK);
248 /* Extract path name from the symlink file */
249 r = ltraverse(rip, next_cp);
250 next_cp = user_path;
251 *offsetp = 0;
253 /* Symloop limit reached? */
254 if (++(*symlinkp) > SYMLOOP_MAX)
255 r = ELOOP;
257 if (r != OK) {
258 put_inode(dir_ip);
259 put_inode(rip);
260 return(r);
263 if (next_cp[0] == '/') {
264 put_inode(dir_ip);
265 put_inode(rip);
266 return(ESYMLINK);
269 put_inode(rip);
270 dup_inode(dir_ip);
271 rip = dir_ip;
274 put_inode(dir_ip);
275 cp = next_cp; /* Process subsequent component in next round */
280 /*===========================================================================*
281 * ltraverse *
282 *===========================================================================*/
283 PRIVATE int ltraverse(rip, suffix)
284 register struct inode *rip; /* symbolic link */
285 char *suffix; /* current remaining path. Has to point in the
286 * user_path buffer
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
292 * new pathname.
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 char *sp; /* start of link text */
301 if ((blink = read_map(rip, (off_t) 0)) == NO_BLOCK)
302 return(EIO);
304 bp = get_block(rip->i_dev, blink, NORMAL);
305 llen = (size_t) rip->i_size;
306 sp = bp->b_data;
307 slen = strlen(suffix);
309 /* The path we're parsing looks like this:
310 * /already/processed/path/<link> or
311 * /already/processed/path/<link>/not/yet/processed/path
312 * After expanding the <link>, the path will look like
313 * <expandedlink> or
314 * <expandedlink>/not/yet/processed
315 * In both cases user_path must have enough room to hold <expandedlink>.
316 * However, in the latter case we have to move /not/yet/processed to the
317 * right place first, before we expand <link>. When strlen(<expandedlink>) is
318 * smaller than strlen(/already/processes/path), we move the suffix to the
319 * left. Is strlen(<expandedlink>) greater then we move it to the right. Else
320 * we do nothing.
323 if (slen > 0) { /* Do we have path after the link? */
324 /* For simplicity we require that suffix starts with a slash */
325 if (suffix[0] != '/') {
326 panic("ltraverse: suffix does not start with a slash");
329 /* To be able to expand the <link>, we have to move the 'suffix'
330 * to the right place.
332 if (slen + llen + 1 > sizeof(user_path))
333 return(ENAMETOOLONG);/* <expandedlink>+suffix+\0 does not fit*/
334 if ((unsigned) (suffix-user_path) != llen) {
335 /* Move suffix left or right */
336 memmove(&user_path[llen], suffix, slen+1);
338 } else {
339 if (llen + 1 > sizeof(user_path))
340 return(ENAMETOOLONG); /* <expandedlink> + \0 does not fix */
342 /* Set terminating nul */
343 user_path[llen]= '\0';
346 /* Everything is set, now copy the expanded link to user_path */
347 memmove(user_path, sp, llen);
349 put_block(bp, DIRECTORY_BLOCK);
350 return(OK);
354 /*===========================================================================*
355 * advance *
356 *===========================================================================*/
357 PUBLIC struct inode *advance(dirp, string, chk_perm)
358 struct inode *dirp; /* inode for directory to be searched */
359 char string[NAME_MAX]; /* component name to look for */
360 int chk_perm; /* check permissions when string is looked up*/
362 /* Given a directory and a component of a path, look up the component in
363 * the directory, find the inode, open it, and return a pointer to its inode
364 * slot.
366 ino_t inumb;
367 struct inode *rip;
369 /* If 'string' is empty, return an error. */
370 if (string[0] == '\0') {
371 err_code = ENOENT;
372 return(NULL);
375 /* Check for NULL. */
376 if (dirp == NULL) return(NULL);
378 /* If 'string' is not present in the directory, signal error. */
379 if ( (err_code = search_dir(dirp, string, &inumb, LOOK_UP, chk_perm)) != OK) {
380 return(NULL);
383 /* The component has been found in the directory. Get inode. */
384 if ( (rip = get_inode(dirp->i_dev, inumb)) == NULL) {
385 return(NULL);
388 /* The following test is for "mountpoint/.." where mountpoint is a
389 * mountpoint. ".." will refer to the root of the mounted filesystem,
390 * but has to become a reference to the parent of the 'mountpoint'
391 * directory.
393 * This case is recognized by the looked up name pointing to a
394 * root inode, and the directory in which it is held being a
395 * root inode, _and_ the name[1] being '.'. (This is a test for '..'
396 * and excludes '.'.)
398 if (rip->i_num == ROOT_INODE) {
399 if (dirp->i_num == ROOT_INODE) {
400 if (string[1] == '.') {
401 if (!rip->i_sp->s_is_root) {
402 /* Climbing up mountpoint */
403 err_code = ELEAVEMOUNT;
409 /* See if the inode is mounted on. If so, switch to root directory of the
410 * mounted file system. The super_block provides the linkage between the
411 * inode mounted on and the root directory of the mounted file system.
413 if (rip->i_mountpoint) {
414 /* Mountpoint encountered, report it */
415 err_code = EENTERMOUNT;
418 return(rip);
422 /*===========================================================================*
423 * get_name *
424 *===========================================================================*/
425 PRIVATE char *get_name(path_name, string)
426 char *path_name; /* path name to parse */
427 char string[NAME_MAX+1]; /* component extracted from 'old_name' */
429 /* Given a pointer to a path name in fs space, 'path_name', copy the first
430 * component to 'string' (truncated if necessary, always nul terminated).
431 * A pointer to the string after the first component of the name as yet
432 * unparsed is returned. Roughly speaking,
433 * 'get_name' = 'path_name' - 'string'.
435 * This routine follows the standard convention that /usr/ast, /usr//ast,
436 * //usr///ast and /usr/ast/ are all equivalent.
438 size_t len;
439 char *cp, *ep;
441 cp = path_name;
443 /* Skip leading slashes */
444 while (cp[0] == '/') cp++;
446 /* Find the end of the first component */
447 ep = cp;
448 while(ep[0] != '\0' && ep[0] != '/')
449 ep++;
451 len = (size_t) (ep - cp);
453 /* Truncate the amount to be copied if it exceeds NAME_MAX */
454 if (len > NAME_MAX) len = NAME_MAX;
456 /* Special case of the string at cp is empty */
457 if (len == 0)
458 strcpy(string, "."); /* Return "." */
459 else {
460 memcpy(string, cp, len);
461 string[len]= '\0';
464 return(ep);
468 /*===========================================================================*
469 * search_dir *
470 *===========================================================================*/
471 PUBLIC int search_dir(ldir_ptr, string, inumb, flag, check_permissions)
472 register struct inode *ldir_ptr; /* ptr to inode for dir to search */
473 char string[NAME_MAX]; /* component to search for */
474 ino_t *inumb; /* pointer to inode number */
475 int flag; /* LOOK_UP, ENTER, DELETE or IS_EMPTY */
476 int check_permissions; /* check permissions when flag is !IS_EMPTY */
478 /* This function searches the directory whose inode is pointed to by 'ldip':
479 * if (flag == ENTER) enter 'string' in the directory with inode # '*inumb';
480 * if (flag == DELETE) delete 'string' from the directory;
481 * if (flag == LOOK_UP) search for 'string' and return inode # in 'inumb';
482 * if (flag == IS_EMPTY) return OK if only . and .. in dir else ENOTEMPTY;
484 * if 'string' is dot1 or dot2, no access permissions are checked.
487 register struct direct *dp = NULL;
488 register struct buf *bp = NULL;
489 int i, r, e_hit, t, match;
490 mode_t bits;
491 off_t pos;
492 unsigned new_slots, old_slots;
493 block_t b;
494 struct super_block *sp;
495 int extended = 0;
497 /* If 'ldir_ptr' is not a pointer to a dir inode, error. */
498 if ( (ldir_ptr->i_mode & I_TYPE) != I_DIRECTORY) {
499 return(ENOTDIR);
502 r = OK;
504 if (flag != IS_EMPTY) {
505 bits = (flag == LOOK_UP ? X_BIT : W_BIT | X_BIT);
507 if (string == dot1 || string == dot2) {
508 if (flag != LOOK_UP) r = read_only(ldir_ptr);
509 /* only a writable device is required. */
510 } else if(check_permissions) {
511 r = forbidden(ldir_ptr, bits); /* check access permissions */
514 if (r != OK) return(r);
516 /* Step through the directory one block at a time. */
517 old_slots = (unsigned) (ldir_ptr->i_size/DIR_ENTRY_SIZE);
518 new_slots = 0;
519 e_hit = FALSE;
520 match = 0; /* set when a string match occurs */
522 for (pos = 0; pos < ldir_ptr->i_size; pos += ldir_ptr->i_sp->s_block_size) {
523 b = read_map(ldir_ptr, pos); /* get block number */
525 /* Since directories don't have holes, 'b' cannot be NO_BLOCK. */
526 bp = get_block(ldir_ptr->i_dev, b, NORMAL); /* get a dir block */
528 assert(bp != NULL);
530 /* Search a directory block. */
531 for (dp = &bp->b_dir[0];
532 dp < &bp->b_dir[NR_DIR_ENTRIES(ldir_ptr->i_sp->s_block_size)];
533 dp++) {
534 if (++new_slots > old_slots) { /* not found, but room left */
535 if (flag == ENTER) e_hit = TRUE;
536 break;
539 /* Match occurs if string found. */
540 if (flag != ENTER && dp->d_ino != NO_ENTRY) {
541 if (flag == IS_EMPTY) {
542 /* If this test succeeds, dir is not empty. */
543 if (strcmp(dp->d_name, "." ) != 0 &&
544 strcmp(dp->d_name, "..") != 0) match = 1;
545 } else {
546 if (strncmp(dp->d_name, string, NAME_MAX) == 0){
547 match = 1;
552 if (match) {
553 /* LOOK_UP or DELETE found what it wanted. */
554 r = OK;
555 if (flag == IS_EMPTY) r = ENOTEMPTY;
556 else if (flag == DELETE) {
557 /* Save d_ino for recovery. */
558 t = NAME_MAX - sizeof(ino_t);
559 *((ino_t *) &dp->d_name[t]) = dp->d_ino;
560 dp->d_ino = NO_ENTRY; /* erase entry */
561 bp->b_dirt = DIRTY;
562 ldir_ptr->i_update |= CTIME | MTIME;
563 ldir_ptr->i_dirt = DIRTY;
564 } else {
565 sp = ldir_ptr->i_sp; /* 'flag' is LOOK_UP */
566 *inumb = (ino_t) conv4(sp->s_native,
567 (int) dp->d_ino);
569 put_block(bp, DIRECTORY_BLOCK);
570 return(r);
573 /* Check for free slot for the benefit of ENTER. */
574 if (flag == ENTER && dp->d_ino == 0) {
575 e_hit = TRUE; /* we found a free slot */
576 break;
580 /* The whole block has been searched or ENTER has a free slot. */
581 if (e_hit) break; /* e_hit set if ENTER can be performed now */
582 put_block(bp, DIRECTORY_BLOCK); /* otherwise, continue searching dir */
585 /* The whole directory has now been searched. */
586 if (flag != ENTER) {
587 return(flag == IS_EMPTY ? OK : ENOENT);
590 /* This call is for ENTER. If no free slot has been found so far, try to
591 * extend directory.
593 if (e_hit == FALSE) { /* directory is full and no room left in last block */
594 new_slots++; /* increase directory size by 1 entry */
595 if (new_slots == 0) return(EFBIG); /* dir size limited by slot count */
596 if ( (bp = new_block(ldir_ptr, ldir_ptr->i_size)) == NULL)
597 return(err_code);
598 dp = &bp->b_dir[0];
599 extended = 1;
602 /* 'bp' now points to a directory block with space. 'dp' points to slot. */
603 (void) memset(dp->d_name, 0, (size_t) NAME_MAX); /* clear entry */
604 for (i = 0; i < NAME_MAX && string[i]; i++) dp->d_name[i] = string[i];
605 sp = ldir_ptr->i_sp;
606 dp->d_ino = conv4(sp->s_native, (int) *inumb);
607 bp->b_dirt = DIRTY;
608 put_block(bp, DIRECTORY_BLOCK);
609 ldir_ptr->i_update |= CTIME | MTIME; /* mark mtime for update later */
610 ldir_ptr->i_dirt = DIRTY;
611 if (new_slots > old_slots) {
612 ldir_ptr->i_size = (off_t) new_slots * DIR_ENTRY_SIZE;
613 /* Send the change to disk if the directory is extended. */
614 if (extended) rw_inode(ldir_ptr, WRITING);
616 return(OK);