Fixed extern declaration from pointer to array
[minix.git] / servers / mfs / path.c
blobc9bb8514eddf50d3aea5a00fc0b8a7e6ddcfca7b
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(__FILE__,
341 "ltraverse: suffix does not start with a slash",
342 NO_NUM);
345 /* To be able to expand the <link>, we have to move the 'suffix'
346 * to the right place. */
347 if (slen + llen + 1 > sizeof(user_path))
348 return(ENAMETOOLONG);/* <expandedlink>+suffix+\0 does not fit*/
349 if (suffix-user_path != llen) /* Move suffix left or right if needed */
350 memmove(&user_path[llen], suffix, slen+1);
351 } else {
352 if (llen + 1 > sizeof(user_path))
353 return(ENAMETOOLONG); /* <expandedlink> + \0 does not fix */
355 /* Set terminating nul */
356 user_path[llen]= '\0';
359 /* Everything is set, now copy the expanded link to user_path */
360 memmove(user_path, sp, llen);
362 put_block(bp, DIRECTORY_BLOCK);
363 return(OK);
367 /*===========================================================================*
368 * advance *
369 *===========================================================================*/
370 PUBLIC struct inode *advance(dirp, string, chk_perm)
371 struct inode *dirp; /* inode for directory to be searched */
372 char string[NAME_MAX]; /* component name to look for */
373 int chk_perm; /* check permissions when string is looked up*/
375 /* Given a directory and a component of a path, look up the component in
376 * the directory, find the inode, open it, and return a pointer to its inode
377 * slot.
379 ino_t numb;
380 struct inode *rip;
382 /* If 'string' is empty, return an error. */
383 if (string[0] == '\0') {
384 err_code = ENOENT;
385 return(NIL_INODE);
388 /* Check for NIL_INODE. */
389 if (dirp == NIL_INODE) return(NIL_INODE);
391 /* If 'string' is not present in the directory, signal error. */
392 if ( (err_code = search_dir(dirp, string, &numb, LOOK_UP, chk_perm)) != OK) {
393 return(NIL_INODE);
396 /* The component has been found in the directory. Get inode. */
397 if ( (rip = get_inode(dirp->i_dev, (int) numb)) == NIL_INODE) {
398 return(NIL_INODE);
401 /* The following test is for "mountpoint/.." where mountpoint is a
402 * mountpoint. ".." will refer to the root of the mounted filesystem,
403 * but has to become a reference to the parent of the 'mountpoint'
404 * directory.
406 * This case is recognized by the looked up name pointing to a
407 * root inode, and the directory in which it is held being a
408 * root inode, _and_ the name[1] being '.'. (This is a test for '..'
409 * and excludes '.'.)
411 if (rip->i_num == ROOT_INODE) {
412 if (dirp->i_num == ROOT_INODE) {
413 if (string[1] == '.') {
414 if (!rip->i_sp->s_is_root) {
415 /* Climbing up mountpoint */
416 err_code = ELEAVEMOUNT;
422 /* See if the inode is mounted on. If so, switch to root directory of the
423 * mounted file system. The super_block provides the linkage between the
424 * inode mounted on and the root directory of the mounted file system.
426 if (rip != NIL_INODE && rip->i_mountpoint) {
427 /* Mountpoint encountered, report it */
428 err_code = EENTERMOUNT;
431 return(rip);
435 /*===========================================================================*
436 * get_name *
437 *===========================================================================*/
438 PRIVATE char *get_name(path_name, string)
439 char *path_name; /* path name to parse */
440 char string[NAME_MAX+1]; /* component extracted from 'old_name' */
442 /* Given a pointer to a path name in fs space, 'path_name', copy the first
443 * component to 'string' (truncated if necessary, always nul terminated).
444 * A pointer to the string after the first component of the name as yet
445 * unparsed is returned. Roughly speaking,
446 * 'get_name' = 'path_name' - 'string'.
448 * This routine follows the standard convention that /usr/ast, /usr//ast,
449 * //usr///ast and /usr/ast/ are all equivalent.
451 size_t len;
452 char *cp, *ep;
454 cp = path_name;
456 /* Skip leading slashes */
457 while (cp[0] == '/') cp++;
459 /* Find the end of the first component */
460 ep = cp;
461 while(ep[0] != '\0' && ep[0] != '/')
462 ep++;
464 len = ep - cp;
466 /* Truncate the amount to be copied if it exceeds NAME_MAX */
467 if (len > NAME_MAX)
468 len = NAME_MAX;
470 /* Special case of the string at cp is empty */
471 if (len == 0)
472 strcpy(string, "."); /* Return "." */
473 else {
474 memcpy(string, cp, len);
475 string[len]= '\0';
478 return(ep);
482 /*===========================================================================*
483 * search_dir *
484 *===========================================================================*/
485 PUBLIC int search_dir(ldir_ptr, string, numb, flag, check_permissions)
486 register struct inode *ldir_ptr; /* ptr to inode for dir to search */
487 char string[NAME_MAX]; /* 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 */
492 /* This function searches the directory whose inode is pointed to by 'ldip':
493 * if (flag == ENTER) enter 'string' in the directory with inode # '*numb';
494 * if (flag == DELETE) delete 'string' from the directory;
495 * if (flag == LOOK_UP) search for 'string' and return inode # in 'numb';
496 * if (flag == IS_EMPTY) return OK if only . and .. in dir else ENOTEMPTY;
498 * if 'string' is dot1 or dot2, no access permissions are checked.
501 register struct direct *dp = NULL;
502 register struct buf *bp = NULL;
503 int i, r, e_hit, t, match;
504 mode_t bits;
505 off_t pos;
506 unsigned new_slots, old_slots;
507 block_t b;
508 struct super_block *sp;
509 int extended = 0;
511 /* If 'ldir_ptr' is not a pointer to a dir inode, error. */
512 if ( (ldir_ptr->i_mode & I_TYPE) != I_DIRECTORY) {
513 return(ENOTDIR);
516 r = OK;
518 if (flag != IS_EMPTY) {
519 bits = (flag == LOOK_UP ? X_BIT : W_BIT | X_BIT);
521 if (string == dot1 || string == dot2) {
522 if (flag != LOOK_UP) r = read_only(ldir_ptr);
523 /* only a writable device is required. */
524 } else if(check_permissions) {
525 r = forbidden(ldir_ptr, bits); /* check access permissions */
528 if (r != OK) return(r);
530 /* Step through the directory one block at a time. */
531 old_slots = (unsigned) (ldir_ptr->i_size/DIR_ENTRY_SIZE);
532 new_slots = 0;
533 e_hit = FALSE;
534 match = 0; /* set when a string match occurs */
536 for (pos = 0; pos < ldir_ptr->i_size; pos += ldir_ptr->i_sp->s_block_size) {
537 b = read_map(ldir_ptr, pos); /* get block number */
539 /* Since directories don't have holes, 'b' cannot be NO_BLOCK. */
540 bp = get_block(ldir_ptr->i_dev, b, NORMAL); /* get a dir block */
542 if (bp == NO_BLOCK)
543 panic(__FILE__,"get_block returned NO_BLOCK", NO_NUM);
545 /* Search a directory block. */
546 for (dp = &bp->b_dir[0];
547 dp < &bp->b_dir[NR_DIR_ENTRIES(ldir_ptr->i_sp->s_block_size)];
548 dp++) {
549 if (++new_slots > old_slots) { /* not found, but room left */
550 if (flag == ENTER) e_hit = TRUE;
551 break;
554 /* Match occurs if string found. */
555 if (flag != ENTER && dp->d_ino != 0) {
556 if (flag == IS_EMPTY) {
557 /* If this test succeeds, dir is not empty. */
558 if (strcmp(dp->d_name, "." ) != 0 &&
559 strcmp(dp->d_name, "..") != 0) match = 1;
560 } else {
561 if (strncmp(dp->d_name, string, NAME_MAX) == 0){
562 match = 1;
567 if (match) {
568 /* LOOK_UP or DELETE found what it wanted. */
569 r = OK;
570 if (flag == IS_EMPTY) r = ENOTEMPTY;
571 else if (flag == DELETE) {
572 /* Save d_ino for recovery. */
573 t = NAME_MAX - sizeof(ino_t);
574 *((ino_t *) &dp->d_name[t]) = dp->d_ino;
575 dp->d_ino = 0; /* erase entry */
576 bp->b_dirt = DIRTY;
577 ldir_ptr->i_update |= CTIME | MTIME;
578 ldir_ptr->i_dirt = DIRTY;
579 } else {
580 sp = ldir_ptr->i_sp; /* 'flag' is LOOK_UP */
581 *numb = conv4(sp->s_native, (int) dp->d_ino);
583 put_block(bp, DIRECTORY_BLOCK);
584 return(r);
587 /* Check for free slot for the benefit of ENTER. */
588 if (flag == ENTER && dp->d_ino == 0) {
589 e_hit = TRUE; /* we found a free slot */
590 break;
594 /* The whole block has been searched or ENTER has a free slot. */
595 if (e_hit) break; /* e_hit set if ENTER can be performed now */
596 put_block(bp, DIRECTORY_BLOCK); /* otherwise, continue searching dir */
599 /* The whole directory has now been searched. */
600 if (flag != ENTER) {
601 return(flag == IS_EMPTY ? OK : ENOENT);
604 /* This call is for ENTER. If no free slot has been found so far, try to
605 * extend directory.
607 if (e_hit == FALSE) { /* directory is full and no room left in last block */
608 new_slots++; /* increase directory size by 1 entry */
609 if (new_slots == 0) return(EFBIG); /* dir size limited by slot count */
610 if ( (bp = new_block(ldir_ptr, ldir_ptr->i_size)) == NIL_BUF)
611 return(err_code);
612 dp = &bp->b_dir[0];
613 extended = 1;
616 /* 'bp' now points to a directory block with space. 'dp' points to slot. */
617 (void) memset(dp->d_name, 0, (size_t) NAME_MAX); /* clear entry */
618 for (i = 0; i < NAME_MAX && string[i]; i++) dp->d_name[i] = string[i];
619 sp = ldir_ptr->i_sp;
620 dp->d_ino = conv4(sp->s_native, (int) *numb);
621 bp->b_dirt = DIRTY;
622 put_block(bp, DIRECTORY_BLOCK);
623 ldir_ptr->i_update |= CTIME | MTIME; /* mark mtime for update later */
624 ldir_ptr->i_dirt = DIRTY;
625 if (new_slots > old_slots) {
626 ldir_ptr->i_size = (off_t) new_slots * DIR_ENTRY_SIZE;
627 /* Send the change to disk if the directory is extended. */
628 if (extended) rw_inode(ldir_ptr, WRITING);
630 return(OK);