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