1 /* 2 * Copyright (c) 2000-2005 Silicon Graphics, Inc. 3 * Copyright (c) 2013 Red Hat, Inc. 4 * All Rights Reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it would be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 #include "xfs.h" 20 #include "xfs_fs.h" 21 #include "xfs_format.h" 22 #include "xfs_log_format.h" 23 #include "xfs_trans_resv.h" 24 #include "xfs_bit.h" 25 #include "xfs_mount.h" 26 #include "xfs_da_format.h" 27 #include "xfs_da_btree.h" 28 #include "xfs_inode.h" 29 #include "xfs_dir2.h" 30 #include "xfs_dir2_priv.h" 31 #include "xfs_error.h" 32 #include "xfs_trace.h" 33 #include "xfs_bmap.h" 34 #include "xfs_trans.h" 35 36 /* 37 * Directory file type support functions 38 */ 39 static unsigned char xfs_dir3_filetype_table[] = { 40 DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, 41 DT_FIFO, DT_SOCK, DT_LNK, DT_WHT, 42 }; 43 44 static unsigned char 45 xfs_dir3_get_dtype( 46 struct xfs_mount *mp, 47 __uint8_t filetype) 48 { 49 if (!xfs_sb_version_hasftype(&mp->m_sb)) 50 return DT_UNKNOWN; 51 52 if (filetype >= XFS_DIR3_FT_MAX) 53 return DT_UNKNOWN; 54 55 return xfs_dir3_filetype_table[filetype]; 56 } 57 58 STATIC int 59 xfs_dir2_sf_getdents( 60 struct xfs_da_args *args, 61 struct dir_context *ctx) 62 { 63 int i; /* shortform entry number */ 64 struct xfs_inode *dp = args->dp; /* incore directory inode */ 65 xfs_dir2_dataptr_t off; /* current entry's offset */ 66 xfs_dir2_sf_entry_t *sfep; /* shortform directory entry */ 67 xfs_dir2_sf_hdr_t *sfp; /* shortform structure */ 68 xfs_dir2_dataptr_t dot_offset; 69 xfs_dir2_dataptr_t dotdot_offset; 70 xfs_ino_t ino; 71 struct xfs_da_geometry *geo = args->geo; 72 73 ASSERT(dp->i_df.if_flags & XFS_IFINLINE); 74 /* 75 * Give up if the directory is way too short. 76 */ 77 if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) { 78 ASSERT(XFS_FORCED_SHUTDOWN(dp->i_mount)); 79 return -EIO; 80 } 81 82 ASSERT(dp->i_df.if_bytes == dp->i_d.di_size); 83 ASSERT(dp->i_df.if_u1.if_data != NULL); 84 85 sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data; 86 87 ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count)); 88 89 /* 90 * If the block number in the offset is out of range, we're done. 91 */ 92 if (xfs_dir2_dataptr_to_db(geo, ctx->pos) > geo->datablk) 93 return 0; 94 95 /* 96 * Precalculate offsets for . and .. as we will always need them. 97 * 98 * XXX(hch): the second argument is sometimes 0 and sometimes 99 * geo->datablk 100 */ 101 dot_offset = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, 102 dp->d_ops->data_dot_offset); 103 dotdot_offset = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, 104 dp->d_ops->data_dotdot_offset); 105 106 /* 107 * Put . entry unless we're starting past it. 108 */ 109 if (ctx->pos <= dot_offset) { 110 ctx->pos = dot_offset & 0x7fffffff; 111 if (!dir_emit(ctx, ".", 1, dp->i_ino, DT_DIR)) 112 return 0; 113 } 114 115 /* 116 * Put .. entry unless we're starting past it. 117 */ 118 if (ctx->pos <= dotdot_offset) { 119 ino = dp->d_ops->sf_get_parent_ino(sfp); 120 ctx->pos = dotdot_offset & 0x7fffffff; 121 if (!dir_emit(ctx, "..", 2, ino, DT_DIR)) 122 return 0; 123 } 124 125 /* 126 * Loop while there are more entries and put'ing works. 127 */ 128 sfep = xfs_dir2_sf_firstentry(sfp); 129 for (i = 0; i < sfp->count; i++) { 130 __uint8_t filetype; 131 132 off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, 133 xfs_dir2_sf_get_offset(sfep)); 134 135 if (ctx->pos > off) { 136 sfep = dp->d_ops->sf_nextentry(sfp, sfep); 137 continue; 138 } 139 140 ino = dp->d_ops->sf_get_ino(sfp, sfep); 141 filetype = dp->d_ops->sf_get_ftype(sfep); 142 ctx->pos = off & 0x7fffffff; 143 if (!dir_emit(ctx, (char *)sfep->name, sfep->namelen, ino, 144 xfs_dir3_get_dtype(dp->i_mount, filetype))) 145 return 0; 146 sfep = dp->d_ops->sf_nextentry(sfp, sfep); 147 } 148 149 ctx->pos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk + 1, 0) & 150 0x7fffffff; 151 return 0; 152 } 153 154 /* 155 * Readdir for block directories. 156 */ 157 STATIC int 158 xfs_dir2_block_getdents( 159 struct xfs_da_args *args, 160 struct dir_context *ctx) 161 { 162 struct xfs_inode *dp = args->dp; /* incore directory inode */ 163 xfs_dir2_data_hdr_t *hdr; /* block header */ 164 struct xfs_buf *bp; /* buffer for block */ 165 xfs_dir2_block_tail_t *btp; /* block tail */ 166 xfs_dir2_data_entry_t *dep; /* block data entry */ 167 xfs_dir2_data_unused_t *dup; /* block unused entry */ 168 char *endptr; /* end of the data entries */ 169 int error; /* error return value */ 170 char *ptr; /* current data entry */ 171 int wantoff; /* starting block offset */ 172 xfs_off_t cook; 173 struct xfs_da_geometry *geo = args->geo; 174 int lock_mode; 175 176 /* 177 * If the block number in the offset is out of range, we're done. 178 */ 179 if (xfs_dir2_dataptr_to_db(geo, ctx->pos) > geo->datablk) 180 return 0; 181 182 lock_mode = xfs_ilock_data_map_shared(dp); 183 error = xfs_dir3_block_read(NULL, dp, &bp); 184 xfs_iunlock(dp, lock_mode); 185 if (error) 186 return error; 187 188 /* 189 * Extract the byte offset we start at from the seek pointer. 190 * We'll skip entries before this. 191 */ 192 wantoff = xfs_dir2_dataptr_to_off(geo, ctx->pos); 193 hdr = bp->b_addr; 194 xfs_dir3_data_check(dp, bp); 195 /* 196 * Set up values for the loop. 197 */ 198 btp = xfs_dir2_block_tail_p(geo, hdr); 199 ptr = (char *)dp->d_ops->data_entry_p(hdr); 200 endptr = (char *)xfs_dir2_block_leaf_p(btp); 201 202 /* 203 * Loop over the data portion of the block. 204 * Each object is a real entry (dep) or an unused one (dup). 205 */ 206 while (ptr < endptr) { 207 __uint8_t filetype; 208 209 dup = (xfs_dir2_data_unused_t *)ptr; 210 /* 211 * Unused, skip it. 212 */ 213 if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { 214 ptr += be16_to_cpu(dup->length); 215 continue; 216 } 217 218 dep = (xfs_dir2_data_entry_t *)ptr; 219 220 /* 221 * Bump pointer for the next iteration. 222 */ 223 ptr += dp->d_ops->data_entsize(dep->namelen); 224 /* 225 * The entry is before the desired starting point, skip it. 226 */ 227 if ((char *)dep - (char *)hdr < wantoff) 228 continue; 229 230 cook = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, 231 (char *)dep - (char *)hdr); 232 233 ctx->pos = cook & 0x7fffffff; 234 filetype = dp->d_ops->data_get_ftype(dep); 235 /* 236 * If it didn't fit, set the final offset to here & return. 237 */ 238 if (!dir_emit(ctx, (char *)dep->name, dep->namelen, 239 be64_to_cpu(dep->inumber), 240 xfs_dir3_get_dtype(dp->i_mount, filetype))) { 241 xfs_trans_brelse(NULL, bp); 242 return 0; 243 } 244 } 245 246 /* 247 * Reached the end of the block. 248 * Set the offset to a non-existent block 1 and return. 249 */ 250 ctx->pos = xfs_dir2_db_off_to_dataptr(geo, geo->datablk + 1, 0) & 251 0x7fffffff; 252 xfs_trans_brelse(NULL, bp); 253 return 0; 254 } 255 256 struct xfs_dir2_leaf_map_info { 257 xfs_extlen_t map_blocks; /* number of fsbs in map */ 258 xfs_dablk_t map_off; /* last mapped file offset */ 259 int map_size; /* total entries in *map */ 260 int map_valid; /* valid entries in *map */ 261 int nmap; /* mappings to ask xfs_bmapi */ 262 xfs_dir2_db_t curdb; /* db for current block */ 263 int ra_current; /* number of read-ahead blks */ 264 int ra_index; /* *map index for read-ahead */ 265 int ra_offset; /* map entry offset for ra */ 266 int ra_want; /* readahead count wanted */ 267 struct xfs_bmbt_irec map[]; /* map vector for blocks */ 268 }; 269 270 STATIC int 271 xfs_dir2_leaf_readbuf( 272 struct xfs_da_args *args, 273 size_t bufsize, 274 struct xfs_dir2_leaf_map_info *mip, 275 xfs_dir2_off_t *curoff, 276 struct xfs_buf **bpp, 277 bool trim_map) 278 { 279 struct xfs_inode *dp = args->dp; 280 struct xfs_buf *bp = NULL; 281 struct xfs_bmbt_irec *map = mip->map; 282 struct blk_plug plug; 283 int error = 0; 284 int length; 285 int i; 286 int j; 287 struct xfs_da_geometry *geo = args->geo; 288 289 /* 290 * If the caller just finished processing a buffer, it will tell us 291 * we need to trim that block out of the mapping now it is done. 292 */ 293 if (trim_map) { 294 mip->map_blocks -= geo->fsbcount; 295 /* 296 * Loop to get rid of the extents for the 297 * directory block. 298 */ 299 for (i = geo->fsbcount; i > 0; ) { 300 j = min_t(int, map->br_blockcount, i); 301 map->br_blockcount -= j; 302 map->br_startblock += j; 303 map->br_startoff += j; 304 /* 305 * If mapping is done, pitch it from 306 * the table. 307 */ 308 if (!map->br_blockcount && --mip->map_valid) 309 memmove(&map[0], &map[1], 310 sizeof(map[0]) * mip->map_valid); 311 i -= j; 312 } 313 } 314 315 /* 316 * Recalculate the readahead blocks wanted. 317 */ 318 mip->ra_want = howmany(bufsize + geo->blksize, (1 << geo->fsblog)) - 1; 319 ASSERT(mip->ra_want >= 0); 320 321 /* 322 * If we don't have as many as we want, and we haven't 323 * run out of data blocks, get some more mappings. 324 */ 325 if (1 + mip->ra_want > mip->map_blocks && 326 mip->map_off < xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET)) { 327 /* 328 * Get more bmaps, fill in after the ones 329 * we already have in the table. 330 */ 331 mip->nmap = mip->map_size - mip->map_valid; 332 error = xfs_bmapi_read(dp, mip->map_off, 333 xfs_dir2_byte_to_da(geo, XFS_DIR2_LEAF_OFFSET) - 334 mip->map_off, 335 &map[mip->map_valid], &mip->nmap, 0); 336 337 /* 338 * Don't know if we should ignore this or try to return an 339 * error. The trouble with returning errors is that readdir 340 * will just stop without actually passing the error through. 341 */ 342 if (error) 343 goto out; /* XXX */ 344 345 /* 346 * If we got all the mappings we asked for, set the final map 347 * offset based on the last bmap value received. Otherwise, 348 * we've reached the end. 349 */ 350 if (mip->nmap == mip->map_size - mip->map_valid) { 351 i = mip->map_valid + mip->nmap - 1; 352 mip->map_off = map[i].br_startoff + map[i].br_blockcount; 353 } else 354 mip->map_off = xfs_dir2_byte_to_da(geo, 355 XFS_DIR2_LEAF_OFFSET); 356 357 /* 358 * Look for holes in the mapping, and eliminate them. Count up 359 * the valid blocks. 360 */ 361 for (i = mip->map_valid; i < mip->map_valid + mip->nmap; ) { 362 if (map[i].br_startblock == HOLESTARTBLOCK) { 363 mip->nmap--; 364 length = mip->map_valid + mip->nmap - i; 365 if (length) 366 memmove(&map[i], &map[i + 1], 367 sizeof(map[i]) * length); 368 } else { 369 mip->map_blocks += map[i].br_blockcount; 370 i++; 371 } 372 } 373 mip->map_valid += mip->nmap; 374 } 375 376 /* 377 * No valid mappings, so no more data blocks. 378 */ 379 if (!mip->map_valid) { 380 *curoff = xfs_dir2_da_to_byte(geo, mip->map_off); 381 goto out; 382 } 383 384 /* 385 * Read the directory block starting at the first mapping. 386 */ 387 mip->curdb = xfs_dir2_da_to_db(geo, map->br_startoff); 388 error = xfs_dir3_data_read(NULL, dp, map->br_startoff, 389 map->br_blockcount >= geo->fsbcount ? 390 XFS_FSB_TO_DADDR(dp->i_mount, map->br_startblock) : 391 -1, &bp); 392 /* 393 * Should just skip over the data block instead of giving up. 394 */ 395 if (error) 396 goto out; /* XXX */ 397 398 /* 399 * Adjust the current amount of read-ahead: we just read a block that 400 * was previously ra. 401 */ 402 if (mip->ra_current) 403 mip->ra_current -= geo->fsbcount; 404 405 /* 406 * Do we need more readahead? 407 */ 408 blk_start_plug(&plug); 409 for (mip->ra_index = mip->ra_offset = i = 0; 410 mip->ra_want > mip->ra_current && i < mip->map_blocks; 411 i += geo->fsbcount) { 412 ASSERT(mip->ra_index < mip->map_valid); 413 /* 414 * Read-ahead a contiguous directory block. 415 */ 416 if (i > mip->ra_current && 417 map[mip->ra_index].br_blockcount >= geo->fsbcount) { 418 xfs_dir3_data_readahead(dp, 419 map[mip->ra_index].br_startoff + mip->ra_offset, 420 XFS_FSB_TO_DADDR(dp->i_mount, 421 map[mip->ra_index].br_startblock + 422 mip->ra_offset)); 423 mip->ra_current = i; 424 } 425 426 /* 427 * Read-ahead a non-contiguous directory block. This doesn't 428 * use our mapping, but this is a very rare case. 429 */ 430 else if (i > mip->ra_current) { 431 xfs_dir3_data_readahead(dp, 432 map[mip->ra_index].br_startoff + 433 mip->ra_offset, -1); 434 mip->ra_current = i; 435 } 436 437 /* 438 * Advance offset through the mapping table. 439 */ 440 for (j = 0; j < geo->fsbcount; j += length ) { 441 /* 442 * The rest of this extent but not more than a dir 443 * block. 444 */ 445 length = min_t(int, geo->fsbcount, 446 map[mip->ra_index].br_blockcount - 447 mip->ra_offset); 448 mip->ra_offset += length; 449 450 /* 451 * Advance to the next mapping if this one is used up. 452 */ 453 if (mip->ra_offset == map[mip->ra_index].br_blockcount) { 454 mip->ra_offset = 0; 455 mip->ra_index++; 456 } 457 } 458 } 459 blk_finish_plug(&plug); 460 461 out: 462 *bpp = bp; 463 return error; 464 } 465 466 /* 467 * Getdents (readdir) for leaf and node directories. 468 * This reads the data blocks only, so is the same for both forms. 469 */ 470 STATIC int 471 xfs_dir2_leaf_getdents( 472 struct xfs_da_args *args, 473 struct dir_context *ctx, 474 size_t bufsize) 475 { 476 struct xfs_inode *dp = args->dp; 477 struct xfs_buf *bp = NULL; /* data block buffer */ 478 xfs_dir2_data_hdr_t *hdr; /* data block header */ 479 xfs_dir2_data_entry_t *dep; /* data entry */ 480 xfs_dir2_data_unused_t *dup; /* unused entry */ 481 int error = 0; /* error return value */ 482 int length; /* temporary length value */ 483 int byteoff; /* offset in current block */ 484 xfs_dir2_off_t curoff; /* current overall offset */ 485 xfs_dir2_off_t newoff; /* new curoff after new blk */ 486 char *ptr = NULL; /* pointer to current data */ 487 struct xfs_dir2_leaf_map_info *map_info; 488 struct xfs_da_geometry *geo = args->geo; 489 490 /* 491 * If the offset is at or past the largest allowed value, 492 * give up right away. 493 */ 494 if (ctx->pos >= XFS_DIR2_MAX_DATAPTR) 495 return 0; 496 497 /* 498 * Set up to bmap a number of blocks based on the caller's 499 * buffer size, the directory block size, and the filesystem 500 * block size. 501 */ 502 length = howmany(bufsize + geo->blksize, (1 << geo->fsblog)); 503 map_info = kmem_zalloc(offsetof(struct xfs_dir2_leaf_map_info, map) + 504 (length * sizeof(struct xfs_bmbt_irec)), 505 KM_SLEEP | KM_NOFS); 506 map_info->map_size = length; 507 508 /* 509 * Inside the loop we keep the main offset value as a byte offset 510 * in the directory file. 511 */ 512 curoff = xfs_dir2_dataptr_to_byte(ctx->pos); 513 514 /* 515 * Force this conversion through db so we truncate the offset 516 * down to get the start of the data block. 517 */ 518 map_info->map_off = xfs_dir2_db_to_da(geo, 519 xfs_dir2_byte_to_db(geo, curoff)); 520 521 /* 522 * Loop over directory entries until we reach the end offset. 523 * Get more blocks and readahead as necessary. 524 */ 525 while (curoff < XFS_DIR2_LEAF_OFFSET) { 526 __uint8_t filetype; 527 528 /* 529 * If we have no buffer, or we're off the end of the 530 * current buffer, need to get another one. 531 */ 532 if (!bp || ptr >= (char *)bp->b_addr + geo->blksize) { 533 int lock_mode; 534 bool trim_map = false; 535 536 if (bp) { 537 xfs_trans_brelse(NULL, bp); 538 bp = NULL; 539 trim_map = true; 540 } 541 542 lock_mode = xfs_ilock_data_map_shared(dp); 543 error = xfs_dir2_leaf_readbuf(args, bufsize, map_info, 544 &curoff, &bp, trim_map); 545 xfs_iunlock(dp, lock_mode); 546 if (error || !map_info->map_valid) 547 break; 548 549 /* 550 * Having done a read, we need to set a new offset. 551 */ 552 newoff = xfs_dir2_db_off_to_byte(geo, 553 map_info->curdb, 0); 554 /* 555 * Start of the current block. 556 */ 557 if (curoff < newoff) 558 curoff = newoff; 559 /* 560 * Make sure we're in the right block. 561 */ 562 else if (curoff > newoff) 563 ASSERT(xfs_dir2_byte_to_db(geo, curoff) == 564 map_info->curdb); 565 hdr = bp->b_addr; 566 xfs_dir3_data_check(dp, bp); 567 /* 568 * Find our position in the block. 569 */ 570 ptr = (char *)dp->d_ops->data_entry_p(hdr); 571 byteoff = xfs_dir2_byte_to_off(geo, curoff); 572 /* 573 * Skip past the header. 574 */ 575 if (byteoff == 0) 576 curoff += dp->d_ops->data_entry_offset; 577 /* 578 * Skip past entries until we reach our offset. 579 */ 580 else { 581 while ((char *)ptr - (char *)hdr < byteoff) { 582 dup = (xfs_dir2_data_unused_t *)ptr; 583 584 if (be16_to_cpu(dup->freetag) 585 == XFS_DIR2_DATA_FREE_TAG) { 586 587 length = be16_to_cpu(dup->length); 588 ptr += length; 589 continue; 590 } 591 dep = (xfs_dir2_data_entry_t *)ptr; 592 length = 593 dp->d_ops->data_entsize(dep->namelen); 594 ptr += length; 595 } 596 /* 597 * Now set our real offset. 598 */ 599 curoff = 600 xfs_dir2_db_off_to_byte(geo, 601 xfs_dir2_byte_to_db(geo, curoff), 602 (char *)ptr - (char *)hdr); 603 if (ptr >= (char *)hdr + geo->blksize) { 604 continue; 605 } 606 } 607 } 608 /* 609 * We have a pointer to an entry. 610 * Is it a live one? 611 */ 612 dup = (xfs_dir2_data_unused_t *)ptr; 613 /* 614 * No, it's unused, skip over it. 615 */ 616 if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { 617 length = be16_to_cpu(dup->length); 618 ptr += length; 619 curoff += length; 620 continue; 621 } 622 623 dep = (xfs_dir2_data_entry_t *)ptr; 624 length = dp->d_ops->data_entsize(dep->namelen); 625 filetype = dp->d_ops->data_get_ftype(dep); 626 627 ctx->pos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff; 628 if (!dir_emit(ctx, (char *)dep->name, dep->namelen, 629 be64_to_cpu(dep->inumber), 630 xfs_dir3_get_dtype(dp->i_mount, filetype))) 631 break; 632 633 /* 634 * Advance to next entry in the block. 635 */ 636 ptr += length; 637 curoff += length; 638 /* bufsize may have just been a guess; don't go negative */ 639 bufsize = bufsize > length ? bufsize - length : 0; 640 } 641 642 /* 643 * All done. Set output offset value to current offset. 644 */ 645 if (curoff > xfs_dir2_dataptr_to_byte(XFS_DIR2_MAX_DATAPTR)) 646 ctx->pos = XFS_DIR2_MAX_DATAPTR & 0x7fffffff; 647 else 648 ctx->pos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff; 649 kmem_free(map_info); 650 if (bp) 651 xfs_trans_brelse(NULL, bp); 652 return error; 653 } 654 655 /* 656 * Read a directory. 657 */ 658 int 659 xfs_readdir( 660 struct xfs_inode *dp, 661 struct dir_context *ctx, 662 size_t bufsize) 663 { 664 struct xfs_da_args args = { NULL }; 665 int rval; 666 int v; 667 668 trace_xfs_readdir(dp); 669 670 if (XFS_FORCED_SHUTDOWN(dp->i_mount)) 671 return -EIO; 672 673 ASSERT(S_ISDIR(VFS_I(dp)->i_mode)); 674 XFS_STATS_INC(dp->i_mount, xs_dir_getdents); 675 676 args.dp = dp; 677 args.geo = dp->i_mount->m_dir_geo; 678 679 xfs_ilock(dp, XFS_IOLOCK_SHARED); 680 if (dp->i_d.di_format == XFS_DINODE_FMT_LOCAL) 681 rval = xfs_dir2_sf_getdents(&args, ctx); 682 else if ((rval = xfs_dir2_isblock(&args, &v))) 683 ; 684 else if (v) 685 rval = xfs_dir2_block_getdents(&args, ctx); 686 else 687 rval = xfs_dir2_leaf_getdents(&args, ctx, bufsize); 688 xfs_iunlock(dp, XFS_IOLOCK_SHARED); 689 690 return rval; 691 } 692