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