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