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 package org.apache.hadoop.fs; 020 021 import java.io.BufferedOutputStream; 022 import java.io.DataOutput; 023 import java.io.File; 024 import java.io.FileInputStream; 025 import java.io.FileNotFoundException; 026 import java.io.FileOutputStream; 027 import java.io.IOException; 028 import java.io.OutputStream; 029 import java.io.FileDescriptor; 030 import java.net.URI; 031 import java.nio.ByteBuffer; 032 import java.util.Arrays; 033 import java.util.EnumSet; 034 import java.util.StringTokenizer; 035 036 import org.apache.hadoop.classification.InterfaceAudience; 037 import org.apache.hadoop.classification.InterfaceStability; 038 import org.apache.hadoop.conf.Configuration; 039 import org.apache.hadoop.fs.permission.FsPermission; 040 import org.apache.hadoop.io.nativeio.NativeIO; 041 import org.apache.hadoop.util.Progressable; 042 import org.apache.hadoop.util.Shell; 043 import org.apache.hadoop.util.StringUtils; 044 045 /**************************************************************** 046 * Implement the FileSystem API for the raw local filesystem. 047 * 048 *****************************************************************/ 049 @InterfaceAudience.Public 050 @InterfaceStability.Stable 051 public class RawLocalFileSystem extends FileSystem { 052 static final URI NAME = URI.create("file:///"); 053 private Path workingDir; 054 055 public RawLocalFileSystem() { 056 workingDir = getInitialWorkingDirectory(); 057 } 058 059 private Path makeAbsolute(Path f) { 060 if (f.isAbsolute()) { 061 return f; 062 } else { 063 return new Path(workingDir, f); 064 } 065 } 066 067 /** Convert a path to a File. */ 068 public File pathToFile(Path path) { 069 checkPath(path); 070 if (!path.isAbsolute()) { 071 path = new Path(getWorkingDirectory(), path); 072 } 073 return new File(path.toUri().getPath()); 074 } 075 076 @Override 077 public URI getUri() { return NAME; } 078 079 @Override 080 public void initialize(URI uri, Configuration conf) throws IOException { 081 super.initialize(uri, conf); 082 setConf(conf); 083 } 084 085 class TrackingFileInputStream extends FileInputStream { 086 public TrackingFileInputStream(File f) throws IOException { 087 super(f); 088 } 089 090 @Override 091 public int read() throws IOException { 092 int result = super.read(); 093 if (result != -1) { 094 statistics.incrementBytesRead(1); 095 } 096 return result; 097 } 098 099 @Override 100 public int read(byte[] data) throws IOException { 101 int result = super.read(data); 102 if (result != -1) { 103 statistics.incrementBytesRead(result); 104 } 105 return result; 106 } 107 108 @Override 109 public int read(byte[] data, int offset, int length) throws IOException { 110 int result = super.read(data, offset, length); 111 if (result != -1) { 112 statistics.incrementBytesRead(result); 113 } 114 return result; 115 } 116 } 117 118 /******************************************************* 119 * For open()'s FSInputStream. 120 *******************************************************/ 121 class LocalFSFileInputStream extends FSInputStream implements HasFileDescriptor { 122 private FileInputStream fis; 123 private long position; 124 125 public LocalFSFileInputStream(Path f) throws IOException { 126 this.fis = new TrackingFileInputStream(pathToFile(f)); 127 } 128 129 @Override 130 public void seek(long pos) throws IOException { 131 fis.getChannel().position(pos); 132 this.position = pos; 133 } 134 135 @Override 136 public long getPos() throws IOException { 137 return this.position; 138 } 139 140 @Override 141 public boolean seekToNewSource(long targetPos) throws IOException { 142 return false; 143 } 144 145 /* 146 * Just forward to the fis 147 */ 148 @Override 149 public int available() throws IOException { return fis.available(); } 150 @Override 151 public void close() throws IOException { fis.close(); } 152 @Override 153 public boolean markSupported() { return false; } 154 155 @Override 156 public int read() throws IOException { 157 try { 158 int value = fis.read(); 159 if (value >= 0) { 160 this.position++; 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(byte[] b, int off, int len) throws IOException { 170 try { 171 int value = fis.read(b, off, len); 172 if (value > 0) { 173 this.position += value; 174 } 175 return value; 176 } catch (IOException e) { // unexpected exception 177 throw new FSError(e); // assume native fs error 178 } 179 } 180 181 @Override 182 public int read(long position, byte[] b, int off, int len) 183 throws IOException { 184 ByteBuffer bb = ByteBuffer.wrap(b, off, len); 185 try { 186 return fis.getChannel().read(bb, position); 187 } catch (IOException e) { 188 throw new FSError(e); 189 } 190 } 191 192 @Override 193 public long skip(long n) throws IOException { 194 long value = fis.skip(n); 195 if (value > 0) { 196 this.position += value; 197 } 198 return value; 199 } 200 201 @Override 202 public FileDescriptor getFileDescriptor() throws IOException { 203 return fis.getFD(); 204 } 205 } 206 207 @Override 208 public FSDataInputStream open(Path f, int bufferSize) throws IOException { 209 if (!exists(f)) { 210 throw new FileNotFoundException(f.toString()); 211 } 212 return new FSDataInputStream(new BufferedFSInputStream( 213 new LocalFSFileInputStream(f), bufferSize)); 214 } 215 216 /********************************************************* 217 * For create()'s FSOutputStream. 218 *********************************************************/ 219 class LocalFSFileOutputStream extends OutputStream { 220 private FileOutputStream fos; 221 222 private LocalFSFileOutputStream(Path f, boolean append) throws IOException { 223 this.fos = new FileOutputStream(pathToFile(f), append); 224 } 225 226 /* 227 * Just forward to the fos 228 */ 229 @Override 230 public void close() throws IOException { fos.close(); } 231 @Override 232 public void flush() throws IOException { fos.flush(); } 233 @Override 234 public void write(byte[] b, int off, int len) throws IOException { 235 try { 236 fos.write(b, off, len); 237 } catch (IOException e) { // unexpected exception 238 throw new FSError(e); // assume native fs error 239 } 240 } 241 242 @Override 243 public void write(int b) throws IOException { 244 try { 245 fos.write(b); 246 } catch (IOException e) { // unexpected exception 247 throw new FSError(e); // assume native fs error 248 } 249 } 250 } 251 252 @Override 253 public FSDataOutputStream append(Path f, int bufferSize, 254 Progressable progress) throws IOException { 255 if (!exists(f)) { 256 throw new FileNotFoundException("File " + f + " not found"); 257 } 258 if (getFileStatus(f).isDirectory()) { 259 throw new IOException("Cannot append to a diretory (=" + f + " )"); 260 } 261 return new FSDataOutputStream(new BufferedOutputStream( 262 new LocalFSFileOutputStream(f, true), bufferSize), statistics); 263 } 264 265 @Override 266 public FSDataOutputStream create(Path f, boolean overwrite, int bufferSize, 267 short replication, long blockSize, Progressable progress) 268 throws IOException { 269 return create(f, overwrite, true, bufferSize, replication, blockSize, progress); 270 } 271 272 private FSDataOutputStream create(Path f, boolean overwrite, 273 boolean createParent, int bufferSize, short replication, long blockSize, 274 Progressable progress) throws IOException { 275 if (exists(f) && !overwrite) { 276 throw new IOException("File already exists: "+f); 277 } 278 Path parent = f.getParent(); 279 if (parent != null && !mkdirs(parent)) { 280 throw new IOException("Mkdirs failed to create " + parent.toString()); 281 } 282 return new FSDataOutputStream(new BufferedOutputStream( 283 new LocalFSFileOutputStream(f, false), bufferSize), statistics); 284 } 285 286 @Override 287 @Deprecated 288 public FSDataOutputStream createNonRecursive(Path f, FsPermission permission, 289 EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize, 290 Progressable progress) throws IOException { 291 if (exists(f) && !flags.contains(CreateFlag.OVERWRITE)) { 292 throw new IOException("File already exists: "+f); 293 } 294 return new FSDataOutputStream(new BufferedOutputStream( 295 new LocalFSFileOutputStream(f, false), bufferSize), statistics); 296 } 297 298 @Override 299 public FSDataOutputStream create(Path f, FsPermission permission, 300 boolean overwrite, int bufferSize, short replication, long blockSize, 301 Progressable progress) throws IOException { 302 303 FSDataOutputStream out = create(f, 304 overwrite, bufferSize, replication, blockSize, progress); 305 setPermission(f, permission); 306 return out; 307 } 308 309 @Override 310 public FSDataOutputStream createNonRecursive(Path f, FsPermission permission, 311 boolean overwrite, 312 int bufferSize, short replication, long blockSize, 313 Progressable progress) throws IOException { 314 FSDataOutputStream out = create(f, 315 overwrite, false, bufferSize, replication, blockSize, progress); 316 setPermission(f, permission); 317 return out; 318 } 319 320 @Override 321 public boolean rename(Path src, Path dst) throws IOException { 322 // Attempt rename using Java API. 323 File srcFile = pathToFile(src); 324 File dstFile = pathToFile(dst); 325 if (srcFile.renameTo(dstFile)) { 326 return true; 327 } 328 329 // Enforce POSIX rename behavior that a source directory replaces an existing 330 // destination if the destination is an empty directory. On most platforms, 331 // this is already handled by the Java API call above. Some platforms 332 // (notably Windows) do not provide this behavior, so the Java API call above 333 // fails. Delete destination and attempt rename again. 334 if (this.exists(dst)) { 335 FileStatus sdst = this.getFileStatus(dst); 336 if (sdst.isDirectory() && dstFile.list().length == 0) { 337 if (LOG.isDebugEnabled()) { 338 LOG.debug("Deleting empty destination and renaming " + src + " to " + 339 dst); 340 } 341 if (this.delete(dst, false) && srcFile.renameTo(dstFile)) { 342 return true; 343 } 344 } 345 } 346 347 // The fallback behavior accomplishes the rename by a full copy. 348 if (LOG.isDebugEnabled()) { 349 LOG.debug("Falling through to a copy of " + src + " to " + dst); 350 } 351 return FileUtil.copy(this, src, this, dst, true, getConf()); 352 } 353 354 /** 355 * Delete the given path to a file or directory. 356 * @param p the path to delete 357 * @param recursive to delete sub-directories 358 * @return true if the file or directory and all its contents were deleted 359 * @throws IOException if p is non-empty and recursive is false 360 */ 361 @Override 362 public boolean delete(Path p, boolean recursive) throws IOException { 363 File f = pathToFile(p); 364 if (f.isFile()) { 365 return f.delete(); 366 } else if (!recursive && f.isDirectory() && 367 (FileUtil.listFiles(f).length != 0)) { 368 throw new IOException("Directory " + f.toString() + " is not empty"); 369 } 370 return FileUtil.fullyDelete(f); 371 } 372 373 @Override 374 public FileStatus[] listStatus(Path f) throws IOException { 375 File localf = pathToFile(f); 376 FileStatus[] results; 377 378 if (!localf.exists()) { 379 throw new FileNotFoundException("File " + f + " does not exist"); 380 } 381 if (localf.isFile()) { 382 return new FileStatus[] { 383 new RawLocalFileStatus(localf, getDefaultBlockSize(f), this) }; 384 } 385 386 File[] names = localf.listFiles(); 387 if (names == null) { 388 return null; 389 } 390 results = new FileStatus[names.length]; 391 int j = 0; 392 for (int i = 0; i < names.length; i++) { 393 try { 394 results[j] = getFileStatus(new Path(names[i].getAbsolutePath())); 395 j++; 396 } catch (FileNotFoundException e) { 397 // ignore the files not found since the dir list may have have changed 398 // since the names[] list was generated. 399 } 400 } 401 if (j == names.length) { 402 return results; 403 } 404 return Arrays.copyOf(results, j); 405 } 406 407 /** 408 * Creates the specified directory hierarchy. Does not 409 * treat existence as an error. 410 */ 411 @Override 412 public boolean mkdirs(Path f) throws IOException { 413 if(f == null) { 414 throw new IllegalArgumentException("mkdirs path arg is null"); 415 } 416 Path parent = f.getParent(); 417 File p2f = pathToFile(f); 418 if(parent != null) { 419 File parent2f = pathToFile(parent); 420 if(parent2f != null && parent2f.exists() && !parent2f.isDirectory()) { 421 throw new FileAlreadyExistsException("Parent path is not a directory: " 422 + parent); 423 } 424 } 425 return (parent == null || mkdirs(parent)) && 426 (p2f.mkdir() || p2f.isDirectory()); 427 } 428 429 @Override 430 public boolean mkdirs(Path f, FsPermission permission) throws IOException { 431 boolean b = mkdirs(f); 432 if(b) { 433 setPermission(f, permission); 434 } 435 return b; 436 } 437 438 439 @Override 440 protected boolean primitiveMkdir(Path f, FsPermission absolutePermission) 441 throws IOException { 442 boolean b = mkdirs(f); 443 setPermission(f, absolutePermission); 444 return b; 445 } 446 447 448 @Override 449 public Path getHomeDirectory() { 450 return this.makeQualified(new Path(System.getProperty("user.home"))); 451 } 452 453 /** 454 * Set the working directory to the given directory. 455 */ 456 @Override 457 public void setWorkingDirectory(Path newDir) { 458 workingDir = makeAbsolute(newDir); 459 checkPath(workingDir); 460 461 } 462 463 @Override 464 public Path getWorkingDirectory() { 465 return workingDir; 466 } 467 468 @Override 469 protected Path getInitialWorkingDirectory() { 470 return this.makeQualified(new Path(System.getProperty("user.dir"))); 471 } 472 473 @Override 474 public FsStatus getStatus(Path p) throws IOException { 475 File partition = pathToFile(p == null ? new Path("/") : p); 476 //File provides getUsableSpace() and getFreeSpace() 477 //File provides no API to obtain used space, assume used = total - free 478 return new FsStatus(partition.getTotalSpace(), 479 partition.getTotalSpace() - partition.getFreeSpace(), 480 partition.getFreeSpace()); 481 } 482 483 // In the case of the local filesystem, we can just rename the file. 484 @Override 485 public void moveFromLocalFile(Path src, Path dst) throws IOException { 486 rename(src, dst); 487 } 488 489 // We can write output directly to the final location 490 @Override 491 public Path startLocalOutput(Path fsOutputFile, Path tmpLocalFile) 492 throws IOException { 493 return fsOutputFile; 494 } 495 496 // It's in the right place - nothing to do. 497 @Override 498 public void completeLocalOutput(Path fsWorkingFile, Path tmpLocalFile) 499 throws IOException { 500 } 501 502 @Override 503 public void close() throws IOException { 504 super.close(); 505 } 506 507 @Override 508 public String toString() { 509 return "LocalFS"; 510 } 511 512 @Override 513 public FileStatus getFileStatus(Path f) throws IOException { 514 File path = pathToFile(f); 515 if (path.exists()) { 516 return new RawLocalFileStatus(pathToFile(f), getDefaultBlockSize(f), this); 517 } else { 518 throw new FileNotFoundException("File " + f + " does not exist"); 519 } 520 } 521 522 static class RawLocalFileStatus extends FileStatus { 523 /* We can add extra fields here. It breaks at least CopyFiles.FilePair(). 524 * We recognize if the information is already loaded by check if 525 * onwer.equals(""). 526 */ 527 private boolean isPermissionLoaded() { 528 return !super.getOwner().equals(""); 529 } 530 531 RawLocalFileStatus(File f, long defaultBlockSize, FileSystem fs) { 532 super(f.length(), f.isDirectory(), 1, defaultBlockSize, 533 f.lastModified(), new Path(f.getPath()).makeQualified(fs.getUri(), 534 fs.getWorkingDirectory())); 535 } 536 537 @Override 538 public FsPermission getPermission() { 539 if (!isPermissionLoaded()) { 540 loadPermissionInfo(); 541 } 542 return super.getPermission(); 543 } 544 545 @Override 546 public String getOwner() { 547 if (!isPermissionLoaded()) { 548 loadPermissionInfo(); 549 } 550 return super.getOwner(); 551 } 552 553 @Override 554 public String getGroup() { 555 if (!isPermissionLoaded()) { 556 loadPermissionInfo(); 557 } 558 return super.getGroup(); 559 } 560 561 /// loads permissions, owner, and group from `ls -ld` 562 private void loadPermissionInfo() { 563 IOException e = null; 564 try { 565 String output = FileUtil.execCommand(new File(getPath().toUri()), 566 Shell.getGetPermissionCommand()); 567 StringTokenizer t = 568 new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX); 569 //expected format 570 //-rw------- 1 username groupname ... 571 String permission = t.nextToken(); 572 if (permission.length() > 10) { //files with ACLs might have a '+' 573 permission = permission.substring(0, 10); 574 } 575 setPermission(FsPermission.valueOf(permission)); 576 t.nextToken(); 577 578 String owner = t.nextToken(); 579 // If on windows domain, token format is DOMAIN\\user and we want to 580 // extract only the user name 581 if (Shell.WINDOWS) { 582 int i = owner.indexOf('\\'); 583 if (i != -1) 584 owner = owner.substring(i + 1); 585 } 586 setOwner(owner); 587 588 setGroup(t.nextToken()); 589 } catch (Shell.ExitCodeException ioe) { 590 if (ioe.getExitCode() != 1) { 591 e = ioe; 592 } else { 593 setPermission(null); 594 setOwner(null); 595 setGroup(null); 596 } 597 } catch (IOException ioe) { 598 e = ioe; 599 } finally { 600 if (e != null) { 601 throw new RuntimeException("Error while running command to get " + 602 "file permissions : " + 603 StringUtils.stringifyException(e)); 604 } 605 } 606 } 607 608 @Override 609 public void write(DataOutput out) throws IOException { 610 if (!isPermissionLoaded()) { 611 loadPermissionInfo(); 612 } 613 super.write(out); 614 } 615 } 616 617 /** 618 * Use the command chown to set owner. 619 */ 620 @Override 621 public void setOwner(Path p, String username, String groupname) 622 throws IOException { 623 FileUtil.setOwner(pathToFile(p), username, groupname); 624 } 625 626 /** 627 * Use the command chmod to set permission. 628 */ 629 @Override 630 public void setPermission(Path p, FsPermission permission) 631 throws IOException { 632 if (NativeIO.isAvailable()) { 633 NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(), 634 permission.toShort()); 635 } else { 636 String perm = String.format("%04o", permission.toShort()); 637 Shell.execCommand(Shell.getSetPermissionCommand(perm, false, 638 FileUtil.makeShellPath(pathToFile(p), true))); 639 } 640 } 641 642 /** 643 * Sets the {@link Path}'s last modified time <em>only</em> to the given 644 * valid time. 645 * 646 * @param mtime the modification time to set (only if greater than zero). 647 * @param atime currently ignored. 648 * @throws IOException if setting the last modified time fails. 649 */ 650 @Override 651 public void setTimes(Path p, long mtime, long atime) throws IOException { 652 File f = pathToFile(p); 653 if(mtime >= 0) { 654 if(!f.setLastModified(mtime)) { 655 throw new IOException( 656 "couldn't set last-modified time to " + 657 mtime + 658 " for " + 659 f.getAbsolutePath()); 660 } 661 } 662 } 663 664 private static String execCommand(File f, String... cmd) throws IOException { 665 String[] args = new String[cmd.length + 1]; 666 System.arraycopy(cmd, 0, args, 0, cmd.length); 667 args[cmd.length] = FileUtil.makeShellPath(f, true); 668 String output = Shell.execCommand(args); 669 return output; 670 } 671 672 }