001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 020 package org.apache.hadoop.fs; 021 022 import com.google.common.annotations.VisibleForTesting; 023 024 import java.io.BufferedOutputStream; 025 import java.io.DataOutput; 026 import java.io.File; 027 import java.io.FileInputStream; 028 import java.io.FileNotFoundException; 029 import java.io.FileOutputStream; 030 import java.io.IOException; 031 import java.io.OutputStream; 032 import java.io.FileDescriptor; 033 import java.net.URI; 034 import java.nio.ByteBuffer; 035 import java.util.Arrays; 036 import java.util.EnumSet; 037 import java.util.StringTokenizer; 038 039 import org.apache.hadoop.classification.InterfaceAudience; 040 import org.apache.hadoop.classification.InterfaceStability; 041 import org.apache.hadoop.conf.Configuration; 042 import org.apache.hadoop.fs.permission.FsPermission; 043 import org.apache.hadoop.io.nativeio.NativeIO; 044 import org.apache.hadoop.util.Progressable; 045 import org.apache.hadoop.util.Shell; 046 import org.apache.hadoop.util.StringUtils; 047 048 /**************************************************************** 049 * Implement the FileSystem API for the raw local filesystem. 050 * 051 *****************************************************************/ 052 @InterfaceAudience.Public 053 @InterfaceStability.Stable 054 public class RawLocalFileSystem extends FileSystem { 055 static final URI NAME = URI.create("file:///"); 056 private Path workingDir; 057 // Temporary workaround for HADOOP-9652. 058 private static boolean useDeprecatedFileStatus = true; 059 060 @VisibleForTesting 061 public static void useStatIfAvailable() { 062 useDeprecatedFileStatus = !Stat.isAvailable(); 063 } 064 065 public RawLocalFileSystem() { 066 workingDir = getInitialWorkingDirectory(); 067 } 068 069 private Path makeAbsolute(Path f) { 070 if (f.isAbsolute()) { 071 return f; 072 } else { 073 return new Path(workingDir, f); 074 } 075 } 076 077 /** Convert a path to a File. */ 078 public File pathToFile(Path path) { 079 checkPath(path); 080 if (!path.isAbsolute()) { 081 path = new Path(getWorkingDirectory(), path); 082 } 083 return new File(path.toUri().getPath()); 084 } 085 086 @Override 087 public URI getUri() { return NAME; } 088 089 @Override 090 public void initialize(URI uri, Configuration conf) throws IOException { 091 super.initialize(uri, conf); 092 setConf(conf); 093 } 094 095 /******************************************************* 096 * For open()'s FSInputStream. 097 *******************************************************/ 098 class LocalFSFileInputStream extends FSInputStream implements HasFileDescriptor { 099 private FileInputStream fis; 100 private long position; 101 102 public LocalFSFileInputStream(Path f) throws IOException { 103 fis = new FileInputStream(pathToFile(f)); 104 } 105 106 @Override 107 public void seek(long pos) throws IOException { 108 fis.getChannel().position(pos); 109 this.position = pos; 110 } 111 112 @Override 113 public long getPos() throws IOException { 114 return this.position; 115 } 116 117 @Override 118 public boolean seekToNewSource(long targetPos) throws IOException { 119 return false; 120 } 121 122 /* 123 * Just forward to the fis 124 */ 125 @Override 126 public int available() throws IOException { return fis.available(); } 127 @Override 128 public void close() throws IOException { fis.close(); } 129 @Override 130 public boolean markSupported() { return false; } 131 132 @Override 133 public int read() throws IOException { 134 try { 135 int value = fis.read(); 136 if (value >= 0) { 137 this.position++; 138 statistics.incrementBytesRead(1); 139 } 140 return value; 141 } catch (IOException e) { // unexpected exception 142 throw new FSError(e); // assume native fs error 143 } 144 } 145 146 @Override 147 public int read(byte[] b, int off, int len) throws IOException { 148 try { 149 int value = fis.read(b, off, len); 150 if (value > 0) { 151 this.position += value; 152 statistics.incrementBytesRead(value); 153 } 154 return value; 155 } catch (IOException e) { // unexpected exception 156 throw new FSError(e); // assume native fs error 157 } 158 } 159 160 @Override 161 public int read(long position, byte[] b, int off, int len) 162 throws IOException { 163 ByteBuffer bb = ByteBuffer.wrap(b, off, len); 164 try { 165 int value = fis.getChannel().read(bb, position); 166 if (value > 0) { 167 statistics.incrementBytesRead(value); 168 } 169 return value; 170 } catch (IOException e) { 171 throw new FSError(e); 172 } 173 } 174 175 @Override 176 public long skip(long n) throws IOException { 177 long value = fis.skip(n); 178 if (value > 0) { 179 this.position += value; 180 } 181 return value; 182 } 183 184 @Override 185 public FileDescriptor getFileDescriptor() throws IOException { 186 return fis.getFD(); 187 } 188 } 189 190 @Override 191 public FSDataInputStream open(Path f, int bufferSize) throws IOException { 192 if (!exists(f)) { 193 throw new FileNotFoundException(f.toString()); 194 } 195 return new FSDataInputStream(new BufferedFSInputStream( 196 new LocalFSFileInputStream(f), bufferSize)); 197 } 198 199 /********************************************************* 200 * For create()'s FSOutputStream. 201 *********************************************************/ 202 class LocalFSFileOutputStream extends OutputStream { 203 private FileOutputStream fos; 204 205 private LocalFSFileOutputStream(Path f, boolean append) throws IOException { 206 this.fos = new FileOutputStream(pathToFile(f), append); 207 } 208 209 /* 210 * Just forward to the fos 211 */ 212 @Override 213 public void close() throws IOException { fos.close(); } 214 @Override 215 public void flush() throws IOException { fos.flush(); } 216 @Override 217 public void write(byte[] b, int off, int len) throws IOException { 218 try { 219 fos.write(b, off, len); 220 } catch (IOException e) { // unexpected exception 221 throw new FSError(e); // assume native fs error 222 } 223 } 224 225 @Override 226 public void write(int b) throws IOException { 227 try { 228 fos.write(b); 229 } catch (IOException e) { // unexpected exception 230 throw new FSError(e); // assume native fs error 231 } 232 } 233 } 234 235 @Override 236 public FSDataOutputStream append(Path f, int bufferSize, 237 Progressable progress) throws IOException { 238 if (!exists(f)) { 239 throw new FileNotFoundException("File " + f + " not found"); 240 } 241 if (getFileStatus(f).isDirectory()) { 242 throw new IOException("Cannot append to a diretory (=" + f + " )"); 243 } 244 return new FSDataOutputStream(new BufferedOutputStream( 245 new LocalFSFileOutputStream(f, true), bufferSize), statistics); 246 } 247 248 @Override 249 public FSDataOutputStream create(Path f, boolean overwrite, int bufferSize, 250 short replication, long blockSize, Progressable progress) 251 throws IOException { 252 return create(f, overwrite, true, bufferSize, replication, blockSize, progress); 253 } 254 255 private FSDataOutputStream create(Path f, boolean overwrite, 256 boolean createParent, int bufferSize, short replication, long blockSize, 257 Progressable progress) throws IOException { 258 if (exists(f) && !overwrite) { 259 throw new IOException("File already exists: "+f); 260 } 261 Path parent = f.getParent(); 262 if (parent != null && !mkdirs(parent)) { 263 throw new IOException("Mkdirs failed to create " + parent.toString()); 264 } 265 return new FSDataOutputStream(new BufferedOutputStream( 266 new LocalFSFileOutputStream(f, false), bufferSize), statistics); 267 } 268 269 @Override 270 @Deprecated 271 public FSDataOutputStream createNonRecursive(Path f, FsPermission permission, 272 EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize, 273 Progressable progress) throws IOException { 274 if (exists(f) && !flags.contains(CreateFlag.OVERWRITE)) { 275 throw new IOException("File already exists: "+f); 276 } 277 return new FSDataOutputStream(new BufferedOutputStream( 278 new LocalFSFileOutputStream(f, false), bufferSize), statistics); 279 } 280 281 @Override 282 public FSDataOutputStream create(Path f, FsPermission permission, 283 boolean overwrite, int bufferSize, short replication, long blockSize, 284 Progressable progress) throws IOException { 285 286 FSDataOutputStream out = create(f, 287 overwrite, bufferSize, replication, blockSize, progress); 288 setPermission(f, permission); 289 return out; 290 } 291 292 @Override 293 public FSDataOutputStream createNonRecursive(Path f, FsPermission permission, 294 boolean overwrite, 295 int bufferSize, short replication, long blockSize, 296 Progressable progress) throws IOException { 297 FSDataOutputStream out = create(f, 298 overwrite, false, bufferSize, replication, blockSize, progress); 299 setPermission(f, permission); 300 return out; 301 } 302 303 @Override 304 public boolean rename(Path src, Path dst) throws IOException { 305 // Attempt rename using Java API. 306 File srcFile = pathToFile(src); 307 File dstFile = pathToFile(dst); 308 if (srcFile.renameTo(dstFile)) { 309 return true; 310 } 311 312 // Enforce POSIX rename behavior that a source directory replaces an existing 313 // destination if the destination is an empty directory. On most platforms, 314 // this is already handled by the Java API call above. Some platforms 315 // (notably Windows) do not provide this behavior, so the Java API call above 316 // fails. Delete destination and attempt rename again. 317 if (this.exists(dst)) { 318 FileStatus sdst = this.getFileStatus(dst); 319 if (sdst.isDirectory() && dstFile.list().length == 0) { 320 if (LOG.isDebugEnabled()) { 321 LOG.debug("Deleting empty destination and renaming " + src + " to " + 322 dst); 323 } 324 if (this.delete(dst, false) && srcFile.renameTo(dstFile)) { 325 return true; 326 } 327 } 328 } 329 330 // The fallback behavior accomplishes the rename by a full copy. 331 if (LOG.isDebugEnabled()) { 332 LOG.debug("Falling through to a copy of " + src + " to " + dst); 333 } 334 return FileUtil.copy(this, src, this, dst, true, getConf()); 335 } 336 337 /** 338 * Delete the given path to a file or directory. 339 * @param p the path to delete 340 * @param recursive to delete sub-directories 341 * @return true if the file or directory and all its contents were deleted 342 * @throws IOException if p is non-empty and recursive is false 343 */ 344 @Override 345 public boolean delete(Path p, boolean recursive) throws IOException { 346 File f = pathToFile(p); 347 if (f.isFile()) { 348 return f.delete(); 349 } else if (!recursive && f.isDirectory() && 350 (FileUtil.listFiles(f).length != 0)) { 351 throw new IOException("Directory " + f.toString() + " is not empty"); 352 } 353 return FileUtil.fullyDelete(f); 354 } 355 356 @Override 357 public FileStatus[] listStatus(Path f) throws IOException { 358 File localf = pathToFile(f); 359 FileStatus[] results; 360 361 if (!localf.exists()) { 362 throw new FileNotFoundException("File " + f + " does not exist"); 363 } 364 if (localf.isFile()) { 365 if (!useDeprecatedFileStatus) { 366 return new FileStatus[] { getFileStatus(f) }; 367 } 368 return new FileStatus[] { 369 new DeprecatedRawLocalFileStatus(localf, getDefaultBlockSize(f), this)}; 370 } 371 372 String[] names = localf.list(); 373 if (names == null) { 374 return null; 375 } 376 results = new FileStatus[names.length]; 377 int j = 0; 378 for (int i = 0; i < names.length; i++) { 379 try { 380 // Assemble the path using the Path 3 arg constructor to make sure 381 // paths with colon are properly resolved on Linux 382 results[j] = getFileStatus(new Path(f, new Path(null, null, names[i]))); 383 j++; 384 } catch (FileNotFoundException e) { 385 // ignore the files not found since the dir list may have have changed 386 // since the names[] list was generated. 387 } 388 } 389 if (j == names.length) { 390 return results; 391 } 392 return Arrays.copyOf(results, j); 393 } 394 395 /** 396 * Creates the specified directory hierarchy. Does not 397 * treat existence as an error. 398 */ 399 @Override 400 public boolean mkdirs(Path f) throws IOException { 401 if(f == null) { 402 throw new IllegalArgumentException("mkdirs path arg is null"); 403 } 404 Path parent = f.getParent(); 405 File p2f = pathToFile(f); 406 if(parent != null) { 407 File parent2f = pathToFile(parent); 408 if(parent2f != null && parent2f.exists() && !parent2f.isDirectory()) { 409 throw new FileAlreadyExistsException("Parent path is not a directory: " 410 + parent); 411 } 412 } 413 return (parent == null || mkdirs(parent)) && 414 (p2f.mkdir() || p2f.isDirectory()); 415 } 416 417 @Override 418 public boolean mkdirs(Path f, FsPermission permission) throws IOException { 419 boolean b = mkdirs(f); 420 if(b) { 421 setPermission(f, permission); 422 } 423 return b; 424 } 425 426 427 @Override 428 protected boolean primitiveMkdir(Path f, FsPermission absolutePermission) 429 throws IOException { 430 boolean b = mkdirs(f); 431 setPermission(f, absolutePermission); 432 return b; 433 } 434 435 436 @Override 437 public Path getHomeDirectory() { 438 return this.makeQualified(new Path(System.getProperty("user.home"))); 439 } 440 441 /** 442 * Set the working directory to the given directory. 443 */ 444 @Override 445 public void setWorkingDirectory(Path newDir) { 446 workingDir = makeAbsolute(newDir); 447 checkPath(workingDir); 448 } 449 450 @Override 451 public Path getWorkingDirectory() { 452 return workingDir; 453 } 454 455 @Override 456 protected Path getInitialWorkingDirectory() { 457 return this.makeQualified(new Path(System.getProperty("user.dir"))); 458 } 459 460 @Override 461 public FsStatus getStatus(Path p) throws IOException { 462 File partition = pathToFile(p == null ? new Path("/") : p); 463 //File provides getUsableSpace() and getFreeSpace() 464 //File provides no API to obtain used space, assume used = total - free 465 return new FsStatus(partition.getTotalSpace(), 466 partition.getTotalSpace() - partition.getFreeSpace(), 467 partition.getFreeSpace()); 468 } 469 470 // In the case of the local filesystem, we can just rename the file. 471 @Override 472 public void moveFromLocalFile(Path src, Path dst) throws IOException { 473 rename(src, dst); 474 } 475 476 // We can write output directly to the final location 477 @Override 478 public Path startLocalOutput(Path fsOutputFile, Path tmpLocalFile) 479 throws IOException { 480 return fsOutputFile; 481 } 482 483 // It's in the right place - nothing to do. 484 @Override 485 public void completeLocalOutput(Path fsWorkingFile, Path tmpLocalFile) 486 throws IOException { 487 } 488 489 @Override 490 public void close() throws IOException { 491 super.close(); 492 } 493 494 @Override 495 public String toString() { 496 return "LocalFS"; 497 } 498 499 @Override 500 public FileStatus getFileStatus(Path f) throws IOException { 501 return getFileLinkStatusInternal(f, true); 502 } 503 504 @Deprecated 505 private FileStatus deprecatedGetFileStatus(Path f) throws IOException { 506 File path = pathToFile(f); 507 if (path.exists()) { 508 return new DeprecatedRawLocalFileStatus(pathToFile(f), 509 getDefaultBlockSize(f), this); 510 } else { 511 throw new FileNotFoundException("File " + f + " does not exist"); 512 } 513 } 514 515 @Deprecated 516 static class DeprecatedRawLocalFileStatus extends FileStatus { 517 /* We can add extra fields here. It breaks at least CopyFiles.FilePair(). 518 * We recognize if the information is already loaded by check if 519 * onwer.equals(""). 520 */ 521 private boolean isPermissionLoaded() { 522 return !super.getOwner().isEmpty(); 523 } 524 525 DeprecatedRawLocalFileStatus(File f, long defaultBlockSize, FileSystem fs) { 526 super(f.length(), f.isDirectory(), 1, defaultBlockSize, 527 f.lastModified(), new Path(f.getPath()).makeQualified(fs.getUri(), 528 fs.getWorkingDirectory())); 529 } 530 531 @Override 532 public FsPermission getPermission() { 533 if (!isPermissionLoaded()) { 534 loadPermissionInfo(); 535 } 536 return super.getPermission(); 537 } 538 539 @Override 540 public String getOwner() { 541 if (!isPermissionLoaded()) { 542 loadPermissionInfo(); 543 } 544 return super.getOwner(); 545 } 546 547 @Override 548 public String getGroup() { 549 if (!isPermissionLoaded()) { 550 loadPermissionInfo(); 551 } 552 return super.getGroup(); 553 } 554 555 /// loads permissions, owner, and group from `ls -ld` 556 private void loadPermissionInfo() { 557 IOException e = null; 558 try { 559 String output = FileUtil.execCommand(new File(getPath().toUri()), 560 Shell.getGetPermissionCommand()); 561 StringTokenizer t = 562 new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX); 563 //expected format 564 //-rw------- 1 username groupname ... 565 String permission = t.nextToken(); 566 if (permission.length() > 10) { //files with ACLs might have a '+' 567 permission = permission.substring(0, 10); 568 } 569 setPermission(FsPermission.valueOf(permission)); 570 t.nextToken(); 571 572 String owner = t.nextToken(); 573 // If on windows domain, token format is DOMAIN\\user and we want to 574 // extract only the user name 575 if (Shell.WINDOWS) { 576 int i = owner.indexOf('\\'); 577 if (i != -1) 578 owner = owner.substring(i + 1); 579 } 580 setOwner(owner); 581 582 setGroup(t.nextToken()); 583 } catch (Shell.ExitCodeException ioe) { 584 if (ioe.getExitCode() != 1) { 585 e = ioe; 586 } else { 587 setPermission(null); 588 setOwner(null); 589 setGroup(null); 590 } 591 } catch (IOException ioe) { 592 e = ioe; 593 } finally { 594 if (e != null) { 595 throw new RuntimeException("Error while running command to get " + 596 "file permissions : " + 597 StringUtils.stringifyException(e)); 598 } 599 } 600 } 601 602 @Override 603 public void write(DataOutput out) throws IOException { 604 if (!isPermissionLoaded()) { 605 loadPermissionInfo(); 606 } 607 super.write(out); 608 } 609 } 610 611 /** 612 * Use the command chown to set owner. 613 */ 614 @Override 615 public void setOwner(Path p, String username, String groupname) 616 throws IOException { 617 FileUtil.setOwner(pathToFile(p), username, groupname); 618 } 619 620 /** 621 * Use the command chmod to set permission. 622 */ 623 @Override 624 public void setPermission(Path p, FsPermission permission) 625 throws IOException { 626 if (NativeIO.isAvailable()) { 627 NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(), 628 permission.toShort()); 629 } else { 630 String perm = String.format("%04o", permission.toShort()); 631 Shell.execCommand(Shell.getSetPermissionCommand(perm, false, 632 FileUtil.makeShellPath(pathToFile(p), true))); 633 } 634 } 635 636 /** 637 * Sets the {@link Path}'s last modified time <em>only</em> to the given 638 * valid time. 639 * 640 * @param mtime the modification time to set (only if greater than zero). 641 * @param atime currently ignored. 642 * @throws IOException if setting the last modified time fails. 643 */ 644 @Override 645 public void setTimes(Path p, long mtime, long atime) throws IOException { 646 File f = pathToFile(p); 647 if(mtime >= 0) { 648 if(!f.setLastModified(mtime)) { 649 throw new IOException( 650 "couldn't set last-modified time to " + 651 mtime + 652 " for " + 653 f.getAbsolutePath()); 654 } 655 } 656 } 657 658 @Override 659 public boolean supportsSymlinks() { 660 return true; 661 } 662 663 @SuppressWarnings("deprecation") 664 @Override 665 public void createSymlink(Path target, Path link, boolean createParent) 666 throws IOException { 667 if (!FileSystem.areSymlinksEnabled()) { 668 throw new UnsupportedOperationException("Symlinks not supported"); 669 } 670 final String targetScheme = target.toUri().getScheme(); 671 if (targetScheme != null && !"file".equals(targetScheme)) { 672 throw new IOException("Unable to create symlink to non-local file "+ 673 "system: "+target.toString()); 674 } 675 if (createParent) { 676 mkdirs(link.getParent()); 677 } 678 679 // NB: Use createSymbolicLink in java.nio.file.Path once available 680 int result = FileUtil.symLink(target.toString(), 681 makeAbsolute(link).toString()); 682 if (result != 0) { 683 throw new IOException("Error " + result + " creating symlink " + 684 link + " to " + target); 685 } 686 } 687 688 /** 689 * Return a FileStatus representing the given path. If the path refers 690 * to a symlink return a FileStatus representing the link rather than 691 * the object the link refers to. 692 */ 693 @Override 694 public FileStatus getFileLinkStatus(final Path f) throws IOException { 695 FileStatus fi = getFileLinkStatusInternal(f, false); 696 // getFileLinkStatus is supposed to return a symlink with a 697 // qualified path 698 if (fi.isSymlink()) { 699 Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(), 700 fi.getPath(), fi.getSymlink()); 701 fi.setSymlink(targetQual); 702 } 703 return fi; 704 } 705 706 /** 707 * Public {@link FileStatus} methods delegate to this function, which in turn 708 * either call the new {@link Stat} based implementation or the deprecated 709 * methods based on platform support. 710 * 711 * @param f Path to stat 712 * @param dereference whether to dereference the final path component if a 713 * symlink 714 * @return FileStatus of f 715 * @throws IOException 716 */ 717 private FileStatus getFileLinkStatusInternal(final Path f, 718 boolean dereference) throws IOException { 719 if (!useDeprecatedFileStatus) { 720 return getNativeFileLinkStatus(f, dereference); 721 } else if (dereference) { 722 return deprecatedGetFileStatus(f); 723 } else { 724 return deprecatedGetFileLinkStatusInternal(f); 725 } 726 } 727 728 /** 729 * Deprecated. Remains for legacy support. Should be removed when {@link Stat} 730 * gains support for Windows and other operating systems. 731 */ 732 @Deprecated 733 private FileStatus deprecatedGetFileLinkStatusInternal(final Path f) 734 throws IOException { 735 String target = FileUtil.readLink(new File(f.toString())); 736 737 try { 738 FileStatus fs = getFileStatus(f); 739 // If f refers to a regular file or directory 740 if (target.isEmpty()) { 741 return fs; 742 } 743 // Otherwise f refers to a symlink 744 return new FileStatus(fs.getLen(), 745 false, 746 fs.getReplication(), 747 fs.getBlockSize(), 748 fs.getModificationTime(), 749 fs.getAccessTime(), 750 fs.getPermission(), 751 fs.getOwner(), 752 fs.getGroup(), 753 new Path(target), 754 f); 755 } catch (FileNotFoundException e) { 756 /* The exists method in the File class returns false for dangling 757 * links so we can get a FileNotFoundException for links that exist. 758 * It's also possible that we raced with a delete of the link. Use 759 * the readBasicFileAttributes method in java.nio.file.attributes 760 * when available. 761 */ 762 if (!target.isEmpty()) { 763 return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(), 764 "", "", new Path(target), f); 765 } 766 // f refers to a file or directory that does not exist 767 throw e; 768 } 769 } 770 /** 771 * Calls out to platform's native stat(1) implementation to get file metadata 772 * (permissions, user, group, atime, mtime, etc). This works around the lack 773 * of lstat(2) in Java 6. 774 * 775 * Currently, the {@link Stat} class used to do this only supports Linux 776 * and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)} 777 * implementation (deprecated) remains further OS support is added. 778 * 779 * @param f File to stat 780 * @param dereference whether to dereference symlinks 781 * @return FileStatus of f 782 * @throws IOException 783 */ 784 private FileStatus getNativeFileLinkStatus(final Path f, 785 boolean dereference) throws IOException { 786 checkPath(f); 787 Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this); 788 FileStatus status = stat.getFileStatus(); 789 return status; 790 } 791 792 @Override 793 public Path getLinkTarget(Path f) throws IOException { 794 FileStatus fi = getFileLinkStatusInternal(f, false); 795 // return an unqualified symlink target 796 return fi.getSymlink(); 797 } 798 }