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. 3 * 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 9 * 10 */ 11 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> 22 #include <minix/libminixfs.h> 23 24 25 char dot1[2] = "."; /* used for search_dir to bypass the access */ 26 char dot2[3] = ".."; /* permissions for . and .. */ 27 28 static char *get_name(char *name, char string[MFS_NAME_MAX+1]); 29 static int ltraverse(struct inode *rip, char *suffix); 30 static int parse_path(ino_t dir_ino, ino_t root_ino, int flags, struct 31 inode **res_inop, size_t *offsetp, int *symlinkp); 32 33 34 /*===========================================================================* 35 * fs_lookup * 36 *===========================================================================*/ 37 int fs_lookup() 38 { 39 cp_grant_id_t grant; 40 int r, r1, flags, symlinks; 41 unsigned int len; 42 size_t offset = 0, path_size; 43 ino_t dir_ino, root_ino; 44 struct inode *rip; 45 46 grant = fs_m_in.m_vfs_fs_lookup.grant_path; 47 path_size = fs_m_in.m_vfs_fs_lookup.path_size; /* Size of the buffer */ 48 len = fs_m_in.m_vfs_fs_lookup.path_len; /* including terminating nul */ 49 dir_ino = fs_m_in.m_vfs_fs_lookup.dir_ino; 50 root_ino = fs_m_in.m_vfs_fs_lookup.root_ino; 51 flags = fs_m_in.m_vfs_fs_lookup.flags; 52 53 /* Check length. */ 54 if(len > sizeof(user_path)) return(E2BIG); /* too big for buffer */ 55 if(len == 0) return(EINVAL); /* too small */ 56 57 /* Copy the pathname and set up caller's user and group id */ 58 r = sys_safecopyfrom(VFS_PROC_NR, grant, /*offset*/ (vir_bytes) 0, 59 (vir_bytes) user_path, (size_t) len); 60 if(r != OK) return(r); 61 62 /* Verify this is a null-terminated path. */ 63 if(user_path[len - 1] != '\0') return(EINVAL); 64 65 memset(&credentials, 0, sizeof(credentials)); 66 if(!(flags & PATH_GET_UCRED)) { /* Do we have to copy uid/gid credentials? */ 67 caller_uid = fs_m_in.m_vfs_fs_lookup.uid; 68 caller_gid = fs_m_in.m_vfs_fs_lookup.gid; 69 } else { 70 if((r=fs_lookup_credentials(&credentials, 71 &caller_uid, &caller_gid, 72 fs_m_in.m_vfs_fs_lookup.grant_ucred, 73 fs_m_in.m_vfs_fs_lookup.ucred_size)) != OK) 74 return r; 75 } 76 77 /* Lookup inode */ 78 rip = NULL; 79 r = parse_path(dir_ino, root_ino, flags, &rip, &offset, &symlinks); 80 81 if(symlinks != 0 && (r == ELEAVEMOUNT || r == EENTERMOUNT || r == ESYMLINK)){ 82 len = strlen(user_path)+1; 83 if(len > path_size) return(ENAMETOOLONG); 84 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); 88 } 89 90 if(r == ELEAVEMOUNT || r == ESYMLINK) { 91 /* Report offset and the error */ 92 fs_m_out.m_fs_vfs_lookup.offset = offset; 93 fs_m_out.m_fs_vfs_lookup.symloop = symlinks; 94 95 return(r); 96 } 97 98 if (r != OK && r != EENTERMOUNT) return(r); 99 100 fs_m_out.m_fs_vfs_lookup.inode = rip->i_num; 101 fs_m_out.m_fs_vfs_lookup.mode = rip->i_mode; 102 fs_m_out.m_fs_vfs_lookup.file_size = rip->i_size; 103 fs_m_out.m_fs_vfs_lookup.symloop = symlinks; 104 fs_m_out.m_fs_vfs_lookup.uid = rip->i_uid; 105 fs_m_out.m_fs_vfs_lookup.gid = rip->i_gid; 106 107 /* This is only valid for block and character specials. But it doesn't 108 * cause any harm to always set the device field. */ 109 fs_m_out.m_fs_vfs_lookup.device = (dev_t) rip->i_zone[0]; 110 111 if(r == EENTERMOUNT) { 112 fs_m_out.m_fs_vfs_lookup.offset = offset; 113 put_inode(rip); /* Only return a reference to the final object */ 114 } 115 116 return(r); 117 } 118 119 120 /*===========================================================================* 121 * parse_path * 122 *===========================================================================*/ 123 static int parse_path( 124 ino_t dir_ino, 125 ino_t root_ino, 126 int flags, 127 struct inode **res_inop, 128 size_t *offsetp, 129 int *symlinkp 130 ) { 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 136 * leading slashes. 137 */ 138 int r, leaving_mount; 139 struct inode *rip, *dir_ip; 140 char *cp, *next_cp; /* component and next component */ 141 char component[MFS_NAME_MAX+1]; 142 143 /* Start parsing path at the first component in user_path */ 144 cp = user_path; 145 146 /* No symlinks encountered yet */ 147 *symlinkp = 0; 148 149 /* Find starting inode inode according to the request message */ 150 if((rip = find_inode(fs_dev, dir_ino)) == NULL) 151 return(ENOENT); 152 153 /* If dir has been removed return ENOENT. */ 154 if (rip->i_nlinks == NO_LINK) return(ENOENT); 155 156 dup_inode(rip); 157 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. 161 */ 162 leaving_mount = rip->i_mountpoint; /* True iff rip is a mountpoint */ 163 164 /* Scan the path component by component. */ 165 while (TRUE) { 166 if(cp[0] == '\0') { 167 /* We're done; either the path was empty or we've parsed all 168 components of the path */ 169 170 *res_inop = rip; 171 *offsetp += cp - user_path; 172 173 /* Return EENTERMOUNT if we are at a mount point */ 174 if (rip->i_mountpoint) return(EENTERMOUNT); 175 176 return(OK); 177 } 178 179 while(cp[0] == '/') cp++; 180 next_cp = get_name(cp, component); 181 182 /* Special code for '..'. A process is not allowed to leave a chrooted 183 * environment. A lookup of '..' at the root of a mounted filesystem 184 * has to return ELEAVEMOUNT. In both cases, the caller needs search 185 * permission for the current inode, as it is used as directory. 186 */ 187 if(strcmp(component, "..") == 0) { 188 /* 'rip' is now accessed as directory */ 189 if ((r = forbidden(rip, X_BIT)) != OK) { 190 put_inode(rip); 191 return(r); 192 } 193 194 if (rip->i_num == root_ino) { 195 cp = next_cp; 196 continue; /* Ignore the '..' at a process' root 197 and move on to the next component */ 198 } 199 200 if (rip->i_num == ROOT_INODE && !rip->i_sp->s_is_root) { 201 /* Climbing up to parent FS */ 202 203 put_inode(rip); 204 *offsetp += cp - user_path; 205 return(ELEAVEMOUNT); 206 } 207 } 208 209 /* Only check for a mount point if we are not coming from one. */ 210 if (!leaving_mount && rip->i_mountpoint) { 211 /* Going to enter a child FS */ 212 213 *res_inop = rip; 214 *offsetp += cp - user_path; 215 return(EENTERMOUNT); 216 } 217 218 /* There is more path. Keep parsing. 219 * If we're leaving a mountpoint, skip directory permission checks. 220 */ 221 dir_ip = rip; 222 rip = advance(dir_ip, leaving_mount ? dot2 : component, CHK_PERM); 223 if(err_code == ELEAVEMOUNT || err_code == EENTERMOUNT) 224 err_code = OK; 225 226 if (err_code != OK) { 227 put_inode(dir_ip); 228 return(err_code); 229 } 230 231 leaving_mount = 0; 232 233 /* The call to advance() succeeded. Fetch next component. */ 234 if (S_ISLNK(rip->i_mode)) { 235 236 if (next_cp[0] == '\0' && (flags & PATH_RET_SYMLINK)) { 237 put_inode(dir_ip); 238 *res_inop = rip; 239 *offsetp += next_cp - user_path; 240 return(OK); 241 } 242 243 /* Extract path name from the symlink file */ 244 r = ltraverse(rip, next_cp); 245 next_cp = user_path; 246 *offsetp = 0; 247 248 /* Symloop limit reached? */ 249 if (++(*symlinkp) > _POSIX_SYMLOOP_MAX) 250 r = ELOOP; 251 252 if (r != OK) { 253 put_inode(dir_ip); 254 put_inode(rip); 255 return(r); 256 } 257 258 if (next_cp[0] == '/') { 259 put_inode(dir_ip); 260 put_inode(rip); 261 return(ESYMLINK); 262 } 263 264 put_inode(rip); 265 dup_inode(dir_ip); 266 rip = dir_ip; 267 } 268 269 put_inode(dir_ip); 270 cp = next_cp; /* Process subsequent component in next round */ 271 } 272 } 273 274 275 /*===========================================================================* 276 * ltraverse * 277 *===========================================================================*/ 278 static int ltraverse(rip, suffix) 279 register struct inode *rip; /* symbolic link */ 280 char *suffix; /* current remaining path. Has to point in the 281 * user_path buffer 282 */ 283 { 284 /* Traverse a symbolic link. Copy the link text from the inode and insert 285 * the text into the path. Return error code or report success. Base 286 * directory has to be determined according to the first character of the 287 * new pathname. 288 */ 289 290 size_t llen; /* length of link */ 291 size_t slen; /* length of suffix */ 292 struct buf *bp; /* buffer containing link text */ 293 char *sp; /* start of link text */ 294 295 if(!(bp = get_block_map(rip, 0))) 296 return(EIO); 297 llen = (size_t) rip->i_size; 298 sp = b_data(bp); 299 slen = strlen(suffix); 300 301 /* The path we're parsing looks like this: 302 * /already/processed/path/<link> or 303 * /already/processed/path/<link>/not/yet/processed/path 304 * After expanding the <link>, the path will look like 305 * <expandedlink> or 306 * <expandedlink>/not/yet/processed 307 * In both cases user_path must have enough room to hold <expandedlink>. 308 * However, in the latter case we have to move /not/yet/processed to the 309 * right place first, before we expand <link>. When strlen(<expandedlink>) is 310 * smaller than strlen(/already/processes/path), we move the suffix to the 311 * left. Is strlen(<expandedlink>) greater then we move it to the right. Else 312 * we do nothing. 313 */ 314 315 if (slen > 0) { /* Do we have path after the link? */ 316 /* For simplicity we require that suffix starts with a slash */ 317 if (suffix[0] != '/') { 318 panic("ltraverse: suffix does not start with a slash"); 319 } 320 321 /* To be able to expand the <link>, we have to move the 'suffix' 322 * to the right place. 323 */ 324 if (slen + llen + 1 > sizeof(user_path)) 325 return(ENAMETOOLONG);/* <expandedlink>+suffix+\0 does not fit*/ 326 if ((unsigned) (suffix-user_path) != llen) { 327 /* Move suffix left or right */ 328 memmove(&user_path[llen], suffix, slen+1); 329 } 330 } else { 331 if (llen + 1 > sizeof(user_path)) 332 return(ENAMETOOLONG); /* <expandedlink> + \0 does not fix */ 333 334 /* Set terminating nul */ 335 user_path[llen]= '\0'; 336 } 337 338 /* Everything is set, now copy the expanded link to user_path */ 339 memmove(user_path, sp, llen); 340 341 put_block(bp, DIRECTORY_BLOCK); 342 return(OK); 343 } 344 345 346 /*===========================================================================* 347 * advance * 348 *===========================================================================*/ 349 struct inode *advance(dirp, string, chk_perm) 350 struct inode *dirp; /* inode for directory to be searched */ 351 char string[MFS_NAME_MAX]; /* component name to look for */ 352 int chk_perm; /* check permissions when string is looked up*/ 353 { 354 /* Given a directory and a component of a path, look up the component in 355 * the directory, find the inode, open it, and return a pointer to its inode 356 * slot. 357 */ 358 ino_t numb; 359 struct inode *rip; 360 361 /* If 'string' is empty, return an error. */ 362 if (string[0] == '\0') { 363 err_code = ENOENT; 364 return(NULL); 365 } 366 367 /* Check for NULL. */ 368 if (dirp == NULL) return(NULL); 369 370 /* If 'string' is not present in the directory, signal error. */ 371 if ( (err_code = search_dir(dirp, string, &numb, LOOK_UP, chk_perm)) != OK) { 372 return(NULL); 373 } 374 375 /* The component has been found in the directory. Get inode. */ 376 if ( (rip = get_inode(dirp->i_dev, (int) numb)) == NULL) { 377 return(NULL); 378 } 379 380 /* The following test is for "mountpoint/.." where mountpoint is a 381 * mountpoint. ".." will refer to the root of the mounted filesystem, 382 * but has to become a reference to the parent of the 'mountpoint' 383 * directory. 384 * 385 * This case is recognized by the looked up name pointing to a 386 * root inode, and the directory in which it is held being a 387 * root inode, _and_ the name[1] being '.'. (This is a test for '..' 388 * and excludes '.'.) 389 */ 390 if (rip->i_num == ROOT_INODE) { 391 if (dirp->i_num == ROOT_INODE) { 392 if (string[1] == '.') { 393 if (!rip->i_sp->s_is_root) { 394 /* Climbing up mountpoint */ 395 err_code = ELEAVEMOUNT; 396 } 397 } 398 } 399 } 400 401 /* See if the inode is mounted on. If so, switch to root directory of the 402 * mounted file system. The super_block provides the linkage between the 403 * inode mounted on and the root directory of the mounted file system. 404 */ 405 if (rip->i_mountpoint) { 406 /* Mountpoint encountered, report it */ 407 err_code = EENTERMOUNT; 408 } 409 410 return(rip); 411 } 412 413 414 /*===========================================================================* 415 * get_name * 416 *===========================================================================*/ 417 static char *get_name(path_name, string) 418 char *path_name; /* path name to parse */ 419 char string[MFS_NAME_MAX+1]; /* component extracted from 'old_name' */ 420 { 421 /* Given a pointer to a path name in fs space, 'path_name', copy the first 422 * component to 'string' (truncated if necessary, always nul terminated). 423 * A pointer to the string after the first component of the name as yet 424 * unparsed is returned. Roughly speaking, 425 * 'get_name' = 'path_name' - 'string'. 426 * 427 * This routine follows the standard convention that /usr/ast, /usr//ast, 428 * //usr///ast and /usr/ast/ are all equivalent. 429 */ 430 size_t len; 431 char *cp, *ep; 432 433 cp = path_name; 434 435 /* Skip leading slashes */ 436 while (cp[0] == '/') cp++; 437 438 /* Find the end of the first component */ 439 ep = cp; 440 while(ep[0] != '\0' && ep[0] != '/') 441 ep++; 442 443 len = (size_t) (ep - cp); 444 445 /* Truncate the amount to be copied if it exceeds MFS_NAME_MAX */ 446 if (len > MFS_NAME_MAX) len = MFS_NAME_MAX; 447 448 /* Special case of the string at cp is empty */ 449 if (len == 0) 450 strlcpy(string, ".", MFS_NAME_MAX + 1); /* Return "." */ 451 else { 452 memcpy(string, cp, len); 453 string[len]= '\0'; 454 } 455 456 return(ep); 457 } 458 459 460 /*===========================================================================* 461 * search_dir * 462 *===========================================================================*/ 463 int search_dir(ldir_ptr, string, numb, flag, check_permissions) 464 register struct inode *ldir_ptr; /* ptr to inode for dir to search */ 465 char string[MFS_NAME_MAX]; /* component to search for */ 466 ino_t *numb; /* pointer to inode number */ 467 int flag; /* LOOK_UP, ENTER, DELETE or IS_EMPTY */ 468 int check_permissions; /* check permissions when flag is !IS_EMPTY */ 469 { 470 /* This function searches the directory whose inode is pointed to by 'ldip': 471 * if (flag == ENTER) enter 'string' in the directory with inode # '*numb'; 472 * if (flag == DELETE) delete 'string' from the directory; 473 * if (flag == LOOK_UP) search for 'string' and return inode # in 'numb'; 474 * if (flag == IS_EMPTY) return OK if only . and .. in dir else ENOTEMPTY; 475 * 476 * if 'string' is dot1 or dot2, no access permissions are checked. 477 */ 478 479 register struct direct *dp = NULL; 480 register struct buf *bp = NULL; 481 int i, r, e_hit, t, match; 482 mode_t bits; 483 off_t pos; 484 unsigned new_slots, old_slots; 485 struct super_block *sp; 486 int extended = 0; 487 488 /* If 'ldir_ptr' is not a pointer to a dir inode, error. */ 489 if ( (ldir_ptr->i_mode & I_TYPE) != I_DIRECTORY) { 490 return(ENOTDIR); 491 } 492 493 if((flag == DELETE || flag == ENTER) && ldir_ptr->i_sp->s_rd_only) 494 return EROFS; 495 496 r = OK; 497 498 if (flag != IS_EMPTY) { 499 bits = (flag == LOOK_UP ? X_BIT : W_BIT | X_BIT); 500 501 if (string == dot1 || string == dot2) { 502 if (flag != LOOK_UP) r = read_only(ldir_ptr); 503 /* only a writable device is required. */ 504 } else if(check_permissions) { 505 r = forbidden(ldir_ptr, bits); /* check access permissions */ 506 } 507 } 508 if (r != OK) return(r); 509 510 /* Step through the directory one block at a time. */ 511 old_slots = (unsigned) (ldir_ptr->i_size/DIR_ENTRY_SIZE); 512 new_slots = 0; 513 e_hit = FALSE; 514 match = 0; /* set when a string match occurs */ 515 516 pos = 0; 517 if (flag == ENTER && ldir_ptr->i_last_dpos < ldir_ptr->i_size) { 518 pos = ldir_ptr->i_last_dpos; 519 new_slots = (unsigned) (pos/DIR_ENTRY_SIZE); 520 } 521 522 for (; pos < ldir_ptr->i_size; pos += ldir_ptr->i_sp->s_block_size) { 523 assert(ldir_ptr->i_dev != NO_DEV); 524 525 /* Since directories don't have holes, 'b' cannot be NO_BLOCK. */ 526 bp = get_block_map(ldir_ptr, pos); 527 528 assert(ldir_ptr->i_dev != NO_DEV); 529 assert(bp != NULL); 530 assert(lmfs_dev(bp) != NO_DEV); 531 532 /* Search a directory block. */ 533 for (dp = &b_dir(bp)[0]; 534 dp < &b_dir(bp)[NR_DIR_ENTRIES(ldir_ptr->i_sp->s_block_size)]; 535 dp++) { 536 if (++new_slots > old_slots) { /* not found, but room left */ 537 if (flag == ENTER) e_hit = TRUE; 538 break; 539 } 540 541 /* Match occurs if string found. */ 542 if (flag != ENTER && dp->mfs_d_ino != NO_ENTRY) { 543 if (flag == IS_EMPTY) { 544 /* If this test succeeds, dir is not empty. */ 545 if (strcmp(dp->mfs_d_name, "." ) != 0 && 546 strcmp(dp->mfs_d_name, "..") != 0) match = 1; 547 } else { 548 if (strncmp(dp->mfs_d_name, string, 549 sizeof(dp->mfs_d_name)) == 0){ 550 match = 1; 551 } 552 } 553 } 554 555 if (match) { 556 /* LOOK_UP or DELETE found what it wanted. */ 557 r = OK; 558 if (flag == IS_EMPTY) r = ENOTEMPTY; 559 else if (flag == DELETE) { 560 /* Save d_ino for recovery. */ 561 t = MFS_NAME_MAX - sizeof(ino_t); 562 *((ino_t *) &dp->mfs_d_name[t]) = dp->mfs_d_ino; 563 dp->mfs_d_ino = NO_ENTRY; /* erase entry */ 564 MARKDIRTY(bp); 565 ldir_ptr->i_update |= CTIME | MTIME; 566 IN_MARKDIRTY(ldir_ptr); 567 if (pos < ldir_ptr->i_last_dpos) 568 ldir_ptr->i_last_dpos = pos; 569 } else { 570 sp = ldir_ptr->i_sp; /* 'flag' is LOOK_UP */ 571 *numb = (ino_t) conv4(sp->s_native, 572 (int) dp->mfs_d_ino); 573 } 574 assert(lmfs_dev(bp) != NO_DEV); 575 put_block(bp, DIRECTORY_BLOCK); 576 return(r); 577 } 578 579 /* Check for free slot for the benefit of ENTER. */ 580 if (flag == ENTER && dp->mfs_d_ino == 0) { 581 e_hit = TRUE; /* we found a free slot */ 582 break; 583 } 584 } 585 586 /* The whole block has been searched or ENTER has a free slot. */ 587 if (e_hit) break; /* e_hit set if ENTER can be performed now */ 588 assert(lmfs_dev(bp) != NO_DEV); 589 put_block(bp, DIRECTORY_BLOCK); /* otherwise, continue searching dir */ 590 } 591 592 /* The whole directory has now been searched. */ 593 if (flag != ENTER) { 594 return(flag == IS_EMPTY ? OK : ENOENT); 595 } 596 597 /* When ENTER next time, start searching for free slot from 598 * i_last_dpos. It gives some performance improvement (3-5%). 599 */ 600 ldir_ptr->i_last_dpos = pos; 601 602 /* This call is for ENTER. If no free slot has been found so far, try to 603 * extend directory. 604 */ 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)) == NULL) 609 return(err_code); 610 dp = &b_dir(bp)[0]; 611 extended = 1; 612 } 613 614 /* 'bp' now points to a directory block with space. 'dp' points to slot. */ 615 (void) memset(dp->mfs_d_name, 0, (size_t) MFS_NAME_MAX); /* clear entry */ 616 for (i = 0; i < MFS_NAME_MAX && string[i]; i++) dp->mfs_d_name[i] = string[i]; 617 sp = ldir_ptr->i_sp; 618 dp->mfs_d_ino = conv4(sp->s_native, (int) *numb); 619 MARKDIRTY(bp); 620 put_block(bp, DIRECTORY_BLOCK); 621 ldir_ptr->i_update |= CTIME | MTIME; /* mark mtime for update later */ 622 IN_MARKDIRTY(ldir_ptr); 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); 627 } 628 return(OK); 629 } 630 631