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