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