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() > FsPermission.MAX_PERMISSION_LENGTH) { 567 //files with ACLs might have a '+' 568 permission = permission.substring(0, 569 FsPermission.MAX_PERMISSION_LENGTH); 570 } 571 setPermission(FsPermission.valueOf(permission)); 572 t.nextToken(); 573 574 String owner = t.nextToken(); 575 // If on windows domain, token format is DOMAIN\\user and we want to 576 // extract only the user name 577 if (Shell.WINDOWS) { 578 int i = owner.indexOf('\\'); 579 if (i != -1) 580 owner = owner.substring(i + 1); 581 } 582 setOwner(owner); 583 584 setGroup(t.nextToken()); 585 } catch (Shell.ExitCodeException ioe) { 586 if (ioe.getExitCode() != 1) { 587 e = ioe; 588 } else { 589 setPermission(null); 590 setOwner(null); 591 setGroup(null); 592 } 593 } catch (IOException ioe) { 594 e = ioe; 595 } finally { 596 if (e != null) { 597 throw new RuntimeException("Error while running command to get " + 598 "file permissions : " + 599 StringUtils.stringifyException(e)); 600 } 601 } 602 } 603 604 @Override 605 public void write(DataOutput out) throws IOException { 606 if (!isPermissionLoaded()) { 607 loadPermissionInfo(); 608 } 609 super.write(out); 610 } 611 } 612 613 /** 614 * Use the command chown to set owner. 615 */ 616 @Override 617 public void setOwner(Path p, String username, String groupname) 618 throws IOException { 619 FileUtil.setOwner(pathToFile(p), username, groupname); 620 } 621 622 /** 623 * Use the command chmod to set permission. 624 */ 625 @Override 626 public void setPermission(Path p, FsPermission permission) 627 throws IOException { 628 if (NativeIO.isAvailable()) { 629 NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(), 630 permission.toShort()); 631 } else { 632 String perm = String.format("%04o", permission.toShort()); 633 Shell.execCommand(Shell.getSetPermissionCommand(perm, false, 634 FileUtil.makeShellPath(pathToFile(p), true))); 635 } 636 } 637 638 /** 639 * Sets the {@link Path}'s last modified time <em>only</em> to the given 640 * valid time. 641 * 642 * @param mtime the modification time to set (only if greater than zero). 643 * @param atime currently ignored. 644 * @throws IOException if setting the last modified time fails. 645 */ 646 @Override 647 public void setTimes(Path p, long mtime, long atime) throws IOException { 648 File f = pathToFile(p); 649 if(mtime >= 0) { 650 if(!f.setLastModified(mtime)) { 651 throw new IOException( 652 "couldn't set last-modified time to " + 653 mtime + 654 " for " + 655 f.getAbsolutePath()); 656 } 657 } 658 } 659 660 @Override 661 public boolean supportsSymlinks() { 662 return true; 663 } 664 665 @SuppressWarnings("deprecation") 666 @Override 667 public void createSymlink(Path target, Path link, boolean createParent) 668 throws IOException { 669 if (!FileSystem.areSymlinksEnabled()) { 670 throw new UnsupportedOperationException("Symlinks not supported"); 671 } 672 final String targetScheme = target.toUri().getScheme(); 673 if (targetScheme != null && !"file".equals(targetScheme)) { 674 throw new IOException("Unable to create symlink to non-local file "+ 675 "system: "+target.toString()); 676 } 677 if (createParent) { 678 mkdirs(link.getParent()); 679 } 680 681 // NB: Use createSymbolicLink in java.nio.file.Path once available 682 int result = FileUtil.symLink(target.toString(), 683 makeAbsolute(link).toString()); 684 if (result != 0) { 685 throw new IOException("Error " + result + " creating symlink " + 686 link + " to " + target); 687 } 688 } 689 690 /** 691 * Return a FileStatus representing the given path. If the path refers 692 * to a symlink return a FileStatus representing the link rather than 693 * the object the link refers to. 694 */ 695 @Override 696 public FileStatus getFileLinkStatus(final Path f) throws IOException { 697 FileStatus fi = getFileLinkStatusInternal(f, false); 698 // getFileLinkStatus is supposed to return a symlink with a 699 // qualified path 700 if (fi.isSymlink()) { 701 Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(), 702 fi.getPath(), fi.getSymlink()); 703 fi.setSymlink(targetQual); 704 } 705 return fi; 706 } 707 708 /** 709 * Public {@link FileStatus} methods delegate to this function, which in turn 710 * either call the new {@link Stat} based implementation or the deprecated 711 * methods based on platform support. 712 * 713 * @param f Path to stat 714 * @param dereference whether to dereference the final path component if a 715 * symlink 716 * @return FileStatus of f 717 * @throws IOException 718 */ 719 private FileStatus getFileLinkStatusInternal(final Path f, 720 boolean dereference) throws IOException { 721 if (!useDeprecatedFileStatus) { 722 return getNativeFileLinkStatus(f, dereference); 723 } else if (dereference) { 724 return deprecatedGetFileStatus(f); 725 } else { 726 return deprecatedGetFileLinkStatusInternal(f); 727 } 728 } 729 730 /** 731 * Deprecated. Remains for legacy support. Should be removed when {@link Stat} 732 * gains support for Windows and other operating systems. 733 */ 734 @Deprecated 735 private FileStatus deprecatedGetFileLinkStatusInternal(final Path f) 736 throws IOException { 737 String target = FileUtil.readLink(new File(f.toString())); 738 739 try { 740 FileStatus fs = getFileStatus(f); 741 // If f refers to a regular file or directory 742 if (target.isEmpty()) { 743 return fs; 744 } 745 // Otherwise f refers to a symlink 746 return new FileStatus(fs.getLen(), 747 false, 748 fs.getReplication(), 749 fs.getBlockSize(), 750 fs.getModificationTime(), 751 fs.getAccessTime(), 752 fs.getPermission(), 753 fs.getOwner(), 754 fs.getGroup(), 755 new Path(target), 756 f); 757 } catch (FileNotFoundException e) { 758 /* The exists method in the File class returns false for dangling 759 * links so we can get a FileNotFoundException for links that exist. 760 * It's also possible that we raced with a delete of the link. Use 761 * the readBasicFileAttributes method in java.nio.file.attributes 762 * when available. 763 */ 764 if (!target.isEmpty()) { 765 return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(), 766 "", "", new Path(target), f); 767 } 768 // f refers to a file or directory that does not exist 769 throw e; 770 } 771 } 772 /** 773 * Calls out to platform's native stat(1) implementation to get file metadata 774 * (permissions, user, group, atime, mtime, etc). This works around the lack 775 * of lstat(2) in Java 6. 776 * 777 * Currently, the {@link Stat} class used to do this only supports Linux 778 * and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)} 779 * implementation (deprecated) remains further OS support is added. 780 * 781 * @param f File to stat 782 * @param dereference whether to dereference symlinks 783 * @return FileStatus of f 784 * @throws IOException 785 */ 786 private FileStatus getNativeFileLinkStatus(final Path f, 787 boolean dereference) throws IOException { 788 checkPath(f); 789 Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this); 790 FileStatus status = stat.getFileStatus(); 791 return status; 792 } 793 794 @Override 795 public Path getLinkTarget(Path f) throws IOException { 796 FileStatus fi = getFileLinkStatusInternal(f, false); 797 // return an unqualified symlink target 798 return fi.getSymlink(); 799 } 800 }