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