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