panic() cleanup.
[minix.git] / servers / mfs / path.c
blobd5268b281a3a95d05381966b5181def7d5e0a7cc
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 <string.h>
14 #include <minix/callnr.h>
15 #include <minix/endpoint.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <unistd.h>
19 #include "buf.h"
20 #include "inode.h"
21 #include "super.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 /*===========================================================================*
36 * fs_lookup *
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;
44 struct inode *rip;
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;
53 /* Check length. */
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);
60 if(r != OK) {
61 printf("MFS %s:%d sys_safecopyfrom failed: %d\n",
62 __FILE__, __LINE__, r);
63 return(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? */
70 int i;
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);
77 if (r != OK) {
78 printf("MFS %s:%d sys_safecopyfrom failed: %d\n",
79 __FILE__, __LINE__, r);
80 return(r);
82 caller_uid = credentials.vu_uid;
83 caller_gid = credentials.vu_gid;
84 } else {
85 memset(&credentials, 0, sizeof(credentials));
86 caller_uid = fs_m_in.REQ_UID;
87 caller_gid = fs_m_in.REQ_GID;
90 /* Lookup inode */
91 rip = NULL;
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,
99 (phys_bytes) len, D);
100 if(r1 != OK) {
101 printf("%s:%d fs_lookup: sys_safecopyto failed: %d\n",
102 __FILE__, __LINE__, r1);
103 return(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;
112 return(r);
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 */
133 return(r);
137 /*===========================================================================*
138 * parse_path *
139 *===========================================================================*/
140 PRIVATE int parse_path(dir_ino, root_ino, flags, res_inop, offsetp, symlinkp)
141 ino_t dir_ino;
142 ino_t root_ino;
143 int flags;
144 struct inode **res_inop;
145 size_t *offsetp;
146 int *symlinkp;
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
153 * leading slashes.
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 */
161 cp = user_path;
163 /* No symlinks encountered yet */
164 *symlinkp = 0;
166 /* Find starting inode inode according to the request message */
167 if((rip = find_inode(fs_dev, dir_ino)) == NIL_INODE)
168 return(ENOENT);
170 /* If dir has been removed return ENOENT. */
171 if (rip->i_nlinks == 0)
172 return(ENOENT);
174 dup_inode(rip);
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. */
183 while (TRUE) {
184 if(cp[0] == '\0') {
185 /* We're done; either the path was empty or we've parsed all
186 components of the path */
188 *res_inop = rip;
189 *offsetp += cp - user_path;
191 /* Return EENTERMOUNT if we are at a mount point */
192 if (rip->i_mountpoint) return(EENTERMOUNT);
194 return(OK);
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) {
208 put_inode(rip);
209 return(r);
212 if (rip->i_num == root_ino) {
213 cp = next_cp;
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 */
221 put_inode(rip);
222 *offsetp += cp - user_path;
223 return(ELEAVEMOUNT);
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 */
231 *res_inop = rip;
232 *offsetp += cp - user_path;
233 return(EENTERMOUNT);
236 /* There is more path. Keep parsing.
237 * If we're leaving a mountpoint, skip directory permission checks.
239 dir_ip = rip;
240 rip = advance(dir_ip, leaving_mount ? dot2 : component, CHK_PERM);
241 if(err_code == ELEAVEMOUNT || err_code == EENTERMOUNT)
242 err_code = OK;
244 if (err_code != OK) {
245 put_inode(dir_ip);
246 return(err_code);
249 leaving_mount = 0;
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)) {
255 put_inode(dir_ip);
256 *res_inop = rip;
257 *offsetp += next_cp - user_path;
258 return(OK);
261 /* Extract path name from the symlink file */
262 r = ltraverse(rip, next_cp);
263 next_cp = user_path;
264 *offsetp = 0;
266 /* Symloop limit reached? */
267 if (++(*symlinkp) > SYMLOOP_MAX)
268 r = ELOOP;
270 if (r != OK) {
271 put_inode(dir_ip);
272 put_inode(rip);
273 return(r);
276 if (next_cp[0] == '/') {
277 put_inode(dir_ip);
278 put_inode(rip);
279 return(ESYMLINK);
282 put_inode(rip);
283 dup_inode(dir_ip);
284 rip = dir_ip;
287 put_inode(dir_ip);
288 cp = next_cp; /* Process subsequent component in next round */
293 /*===========================================================================*
294 * ltraverse *
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
299 * user_path buffer
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
305 * new pathname.
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 */
314 bp = NIL_BUF;
316 if ((blink = read_map(rip, (off_t) 0)) == NO_BLOCK)
317 return(EIO);
319 bp = get_block(rip->i_dev, blink, NORMAL);
320 llen = rip->i_size;
321 sp = bp->b_data;
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
328 * <expandedlink> or
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
335 * we do nothing. */
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);
349 } else {
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);
361 return(OK);
365 /*===========================================================================*
366 * advance *
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
375 * slot.
377 ino_t numb;
378 struct inode *rip;
380 /* If 'string' is empty, return an error. */
381 if (string[0] == '\0') {
382 err_code = ENOENT;
383 return(NIL_INODE);
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) {
391 return(NIL_INODE);
394 /* The component has been found in the directory. Get inode. */
395 if ( (rip = get_inode(dirp->i_dev, (int) numb)) == NIL_INODE) {
396 return(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'
402 * directory.
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 '..'
407 * and excludes '.'.)
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;
429 return(rip);
433 /*===========================================================================*
434 * get_name *
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.
449 size_t len;
450 char *cp, *ep;
452 cp = path_name;
454 /* Skip leading slashes */
455 while (cp[0] == '/') cp++;
457 /* Find the end of the first component */
458 ep = cp;
459 while(ep[0] != '\0' && ep[0] != '/')
460 ep++;
462 len = ep - cp;
464 /* Truncate the amount to be copied if it exceeds NAME_MAX */
465 if (len > NAME_MAX)
466 len = NAME_MAX;
468 /* Special case of the string at cp is empty */
469 if (len == 0)
470 strcpy(string, "."); /* Return "." */
471 else {
472 memcpy(string, cp, len);
473 string[len]= '\0';
476 return(ep);
480 /*===========================================================================*
481 * search_dir *
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;
502 mode_t bits;
503 off_t pos;
504 unsigned new_slots, old_slots;
505 block_t b;
506 struct super_block *sp;
507 int extended = 0;
509 /* If 'ldir_ptr' is not a pointer to a dir inode, error. */
510 if ( (ldir_ptr->i_mode & I_TYPE) != I_DIRECTORY) {
511 return(ENOTDIR);
514 r = OK;
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);
530 new_slots = 0;
531 e_hit = FALSE;
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 */
540 if (bp == NO_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)];
546 dp++) {
547 if (++new_slots > old_slots) { /* not found, but room left */
548 if (flag == ENTER) e_hit = TRUE;
549 break;
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;
558 } else {
559 if (strncmp(dp->d_name, string, NAME_MAX) == 0){
560 match = 1;
565 if (match) {
566 /* LOOK_UP or DELETE found what it wanted. */
567 r = OK;
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 */
574 bp->b_dirt = DIRTY;
575 ldir_ptr->i_update |= CTIME | MTIME;
576 ldir_ptr->i_dirt = DIRTY;
577 } else {
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);
582 return(r);
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 */
588 break;
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. */
598 if (flag != ENTER) {
599 return(flag == IS_EMPTY ? OK : ENOENT);
602 /* This call is for ENTER. If no free slot has been found so far, try to
603 * extend directory.
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)
609 return(err_code);
610 dp = &bp->b_dir[0];
611 extended = 1;
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];
617 sp = ldir_ptr->i_sp;
618 dp->d_ino = conv4(sp->s_native, (int) *numb);
619 bp->b_dirt = DIRTY;
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);
628 return(OK);