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.File; 027import java.io.FileInputStream; 028import java.io.FileNotFoundException; 029import java.io.FileOutputStream; 030import java.io.IOException; 031import java.io.OutputStream; 032import java.io.FileDescriptor; 033import java.net.URI; 034import java.nio.ByteBuffer; 035import java.util.Arrays; 036import java.util.EnumSet; 037import java.util.StringTokenizer; 038 039import org.apache.hadoop.classification.InterfaceAudience; 040import org.apache.hadoop.classification.InterfaceStability; 041import org.apache.hadoop.conf.Configuration; 042import org.apache.hadoop.fs.permission.FsPermission; 043import org.apache.hadoop.io.nativeio.NativeIO; 044import org.apache.hadoop.util.Progressable; 045import org.apache.hadoop.util.Shell; 046import 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 054public 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 throw new FileNotFoundException(f + ": null file list"); 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 if (NativeIO.isAvailable()) { 558 loadNativePermissionInfo(); 559 } else { 560 IOException e = null; 561 try { 562 String output = FileUtil.execCommand(new File(getPath().toUri()), 563 Shell.getGetPermissionCommand()); 564 StringTokenizer t = 565 new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX); 566 //expected format 567 //-rw------- 1 username groupname ... 568 String permission = t.nextToken(); 569 if (permission.length() > FsPermission.MAX_PERMISSION_LENGTH) { 570 //files with ACLs might have a '+' 571 permission = permission.substring(0, 572 FsPermission.MAX_PERMISSION_LENGTH); 573 } 574 setPermission(FsPermission.valueOf(permission)); 575 t.nextToken(); 576 577 String owner = t.nextToken(); 578 // If on windows domain, token format is DOMAIN\\user and we want to 579 // extract only the user name 580 if (Shell.WINDOWS) { 581 int i = owner.indexOf('\\'); 582 if (i != -1) 583 owner = owner.substring(i + 1); 584 } 585 setOwner(owner); 586 587 setGroup(t.nextToken()); 588 } catch (Shell.ExitCodeException ioe) { 589 if (ioe.getExitCode() != 1) { 590 e = ioe; 591 } else { 592 setPermission(null); 593 setOwner(null); 594 setGroup(null); 595 } 596 } catch (IOException ioe) { 597 e = ioe; 598 } finally { 599 if (e != null) { 600 throw new RuntimeException("Error while running command to get " + 601 "file permissions : " + 602 StringUtils.stringifyException(e)); 603 } 604 } 605 } 606 } 607 608 private void loadNativePermissionInfo() { 609 if (NativeIO.isAvailable()) { 610 final String pstr = getPath().toUri().getPath(); 611 FileDescriptor fd; 612 try { 613 fd = NativeIO.POSIX.open(pstr, NativeIO.POSIX.O_RDONLY, 0); 614 } catch (IOException maybeDir) { 615 try { 616 fd = NativeIO.POSIX.open(pstr, NativeIO.POSIX.O_DIRECTORY, 0); 617 } catch (IOException e) { 618 if (LOG.isErrorEnabled()) { 619 LOG.error("Failed to fstat on: " + pstr, e); 620 } 621 throw new RuntimeException(pstr, e); 622 } 623 } 624 625 final FileInputStream dummy = new FileInputStream(fd); 626 try { 627 final NativeIO.POSIX.Stat st = NativeIO.POSIX.getFstat(fd); 628 setPermission(new FsPermission((short)st.getMode())); 629 setOwner(st.getOwner()); 630 setGroup(st.getGroup()); 631 } catch (IOException e) { 632 if (LOG.isInfoEnabled()) { 633 LOG.info("Native fstat failed.", e); 634 } 635 } finally { 636 try { 637 dummy.close(); 638 } catch (IOException e) { 639 if (LOG.isInfoEnabled()) { 640 LOG.info("Failed to close dummy descriptor.", e); 641 } 642 } 643 } 644 } 645 } 646 647 @Override 648 public void write(DataOutput out) throws IOException { 649 if (!isPermissionLoaded()) { 650 loadPermissionInfo(); 651 } 652 super.write(out); 653 } 654 } 655 656 /** 657 * Use the command chown to set owner. 658 */ 659 @Override 660 public void setOwner(Path p, String username, String groupname) 661 throws IOException { 662 FileUtil.setOwner(pathToFile(p), username, groupname); 663 } 664 665 /** 666 * Use the command chmod to set permission. 667 */ 668 @Override 669 public void setPermission(Path p, FsPermission permission) 670 throws IOException { 671 if (NativeIO.isAvailable()) { 672 NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(), 673 permission.toShort()); 674 } else { 675 String perm = String.format("%05o", permission.toShort()); 676 Shell.execCommand(Shell.getSetPermissionCommand(perm, false, 677 FileUtil.makeShellPath(pathToFile(p), true))); 678 } 679 } 680 681 /** 682 * Sets the {@link Path}'s last modified time <em>only</em> to the given 683 * valid time. 684 * 685 * @param mtime the modification time to set (only if greater than zero). 686 * @param atime currently ignored. 687 * @throws IOException if setting the last modified time fails. 688 */ 689 @Override 690 public void setTimes(Path p, long mtime, long atime) throws IOException { 691 File f = pathToFile(p); 692 if(mtime >= 0) { 693 if(!f.setLastModified(mtime)) { 694 throw new IOException( 695 "couldn't set last-modified time to " + 696 mtime + 697 " for " + 698 f.getAbsolutePath()); 699 } 700 } 701 } 702 703 @Override 704 public boolean supportsSymlinks() { 705 return true; 706 } 707 708 @SuppressWarnings("deprecation") 709 @Override 710 public void createSymlink(Path oldPath, Path newPath, boolean createParent) 711 throws IOException { 712 if (!FileSystem.areSymlinksEnabled()) { 713 throw new UnsupportedOperationException("Symlinks not supported"); 714 } 715 716 if (oldPath == null) { 717 throw new RuntimeException("Target cannot be null"); 718 } 719 720 if (newPath == null) { 721 throw new RuntimeException("Link cannot be null"); 722 } 723 724 final String target = pathToFile(oldPath).toString(); 725 final String link = pathToFile(newPath).toString(); 726 727 if (createParent) { 728 // Create any missing ancestors 729 Path parentOfNewPath = newPath.getParent(); 730 if (parentOfNewPath != null) { 731 if (!mkdirs(parentOfNewPath)) { 732 throw new IOException("Mkdirs failed " + parentOfNewPath.toString()); 733 } 734 } 735 } 736 737 if (NativeIO.isAvailable()) { 738 if (LOG.isDebugEnabled()) { 739 LOG.debug("NativeIO.symlink: " + target + " <- " + link); 740 } 741 NativeIO.POSIX.symlink(target, link); 742 } else { 743 FileUtil.symLink(target, link); 744 } 745 } 746 747 /** 748 * Return a FileStatus representing the given path. If the path refers 749 * to a symlink return a FileStatus representing the link rather than 750 * the object the link refers to. 751 */ 752 @Override 753 public FileStatus getFileLinkStatus(final Path f) throws IOException { 754 FileStatus fi = getFileLinkStatusInternal(f, false); 755 // getFileLinkStatus is supposed to return a symlink with a 756 // qualified path 757 if (fi.isSymlink()) { 758 Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(), 759 fi.getPath(), fi.getSymlink()); 760 fi.setSymlink(targetQual); 761 } 762 return fi; 763 } 764 765 /** 766 * Public {@link FileStatus} methods delegate to this function, which in turn 767 * either call the new {@link Stat} based implementation or the deprecated 768 * methods based on platform support. 769 * 770 * @param f Path to stat 771 * @param dereference whether to dereference the final path component if a 772 * symlink 773 * @return FileStatus of f 774 * @throws IOException 775 */ 776 private FileStatus getFileLinkStatusInternal(final Path f, 777 boolean dereference) throws IOException { 778 if (!useDeprecatedFileStatus) { 779 return getNativeFileLinkStatus(f, dereference); 780 } else if (dereference) { 781 return deprecatedGetFileStatus(f); 782 } else { 783 return deprecatedGetFileLinkStatusInternal(f); 784 } 785 } 786 787 /** 788 * Deprecated. Remains for legacy support. Should be removed when {@link Stat} 789 * gains support for Windows and other operating systems. 790 */ 791 @Deprecated 792 private FileStatus deprecatedGetFileLinkStatusInternal(final Path f) 793 throws IOException { 794 String target = FileUtil.readLink(new File(f.toString())); 795 796 try { 797 FileStatus fs = getFileStatus(f); 798 // If f refers to a regular file or directory 799 if (target.isEmpty()) { 800 return fs; 801 } 802 // Otherwise f refers to a symlink 803 return new FileStatus(fs.getLen(), 804 false, 805 fs.getReplication(), 806 fs.getBlockSize(), 807 fs.getModificationTime(), 808 fs.getAccessTime(), 809 fs.getPermission(), 810 fs.getOwner(), 811 fs.getGroup(), 812 new Path(target), 813 f); 814 } catch (FileNotFoundException e) { 815 /* The exists method in the File class returns false for dangling 816 * links so we can get a FileNotFoundException for links that exist. 817 * It's also possible that we raced with a delete of the link. Use 818 * the readBasicFileAttributes method in java.nio.file.attributes 819 * when available. 820 */ 821 if (!target.isEmpty()) { 822 return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(), 823 "", "", new Path(target), f); 824 } 825 // f refers to a file or directory that does not exist 826 throw e; 827 } 828 } 829 /** 830 * Calls out to platform's native stat(1) implementation to get file metadata 831 * (permissions, user, group, atime, mtime, etc). This works around the lack 832 * of lstat(2) in Java 6. 833 * 834 * Currently, the {@link Stat} class used to do this only supports Linux 835 * and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)} 836 * implementation (deprecated) remains further OS support is added. 837 * 838 * @param f File to stat 839 * @param dereference whether to dereference symlinks 840 * @return FileStatus of f 841 * @throws IOException 842 */ 843 private FileStatus getNativeFileLinkStatus(final Path f, 844 boolean dereference) throws IOException { 845 checkPath(f); 846 Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this); 847 FileStatus status = stat.getFileStatus(); 848 return status; 849 } 850 851 @Override 852 public Path getLinkTarget(Path f) throws IOException { 853 FileStatus fi = getFileLinkStatusInternal(f, false); 854 // return an unqualified symlink target 855 return fi.getSymlink(); 856 } 857}