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