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 019package org.apache.hadoop.fs; 020 021import java.io.BufferedInputStream; 022import java.io.BufferedOutputStream; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.FileOutputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.OutputStream; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Enumeration; 032import java.util.List; 033import java.util.Map; 034import java.util.jar.Attributes; 035import java.util.jar.JarOutputStream; 036import java.util.jar.Manifest; 037import java.util.zip.GZIPInputStream; 038import java.util.zip.ZipEntry; 039import java.util.zip.ZipFile; 040 041import org.apache.commons.collections.map.CaseInsensitiveMap; 042import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 043import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.apache.hadoop.classification.InterfaceAudience; 047import org.apache.hadoop.classification.InterfaceStability; 048import org.apache.hadoop.conf.Configuration; 049import org.apache.hadoop.fs.permission.ChmodParser; 050import org.apache.hadoop.fs.permission.FsAction; 051import org.apache.hadoop.fs.permission.FsPermission; 052import org.apache.hadoop.io.IOUtils; 053import org.apache.hadoop.io.nativeio.NativeIO; 054import org.apache.hadoop.util.Shell; 055import org.apache.hadoop.util.Shell.ShellCommandExecutor; 056import org.apache.hadoop.util.StringUtils; 057 058/** 059 * A collection of file-processing util methods 060 */ 061@InterfaceAudience.Public 062@InterfaceStability.Evolving 063public class FileUtil { 064 065 private static final Log LOG = LogFactory.getLog(FileUtil.class); 066 067 /* The error code is defined in winutils to indicate insufficient 068 * privilege to create symbolic links. This value need to keep in 069 * sync with the constant of the same name in: 070 * "src\winutils\common.h" 071 * */ 072 public static final int SYMLINK_NO_PRIVILEGE = 2; 073 074 /** 075 * convert an array of FileStatus to an array of Path 076 * 077 * @param stats 078 * an array of FileStatus objects 079 * @return an array of paths corresponding to the input 080 */ 081 public static Path[] stat2Paths(FileStatus[] stats) { 082 if (stats == null) 083 return null; 084 Path[] ret = new Path[stats.length]; 085 for (int i = 0; i < stats.length; ++i) { 086 ret[i] = stats[i].getPath(); 087 } 088 return ret; 089 } 090 091 /** 092 * convert an array of FileStatus to an array of Path. 093 * If stats if null, return path 094 * @param stats 095 * an array of FileStatus objects 096 * @param path 097 * default path to return in stats is null 098 * @return an array of paths corresponding to the input 099 */ 100 public static Path[] stat2Paths(FileStatus[] stats, Path path) { 101 if (stats == null) 102 return new Path[]{path}; 103 else 104 return stat2Paths(stats); 105 } 106 107 /** 108 * Delete a directory and all its contents. If 109 * we return false, the directory may be partially-deleted. 110 * (1) If dir is symlink to a file, the symlink is deleted. The file pointed 111 * to by the symlink is not deleted. 112 * (2) If dir is symlink to a directory, symlink is deleted. The directory 113 * pointed to by symlink is not deleted. 114 * (3) If dir is a normal file, it is deleted. 115 * (4) If dir is a normal directory, then dir and all its contents recursively 116 * are deleted. 117 */ 118 public static boolean fullyDelete(final File dir) { 119 return fullyDelete(dir, false); 120 } 121 122 /** 123 * Delete a directory and all its contents. If 124 * we return false, the directory may be partially-deleted. 125 * (1) If dir is symlink to a file, the symlink is deleted. The file pointed 126 * to by the symlink is not deleted. 127 * (2) If dir is symlink to a directory, symlink is deleted. The directory 128 * pointed to by symlink is not deleted. 129 * (3) If dir is a normal file, it is deleted. 130 * (4) If dir is a normal directory, then dir and all its contents recursively 131 * are deleted. 132 * @param dir the file or directory to be deleted 133 * @param tryGrantPermissions true if permissions should be modified to delete a file. 134 * @return true on success false on failure. 135 */ 136 public static boolean fullyDelete(final File dir, boolean tryGrantPermissions) { 137 if (tryGrantPermissions) { 138 // try to chmod +rwx the parent folder of the 'dir': 139 File parent = dir.getParentFile(); 140 grantPermissions(parent); 141 } 142 if (deleteImpl(dir, false)) { 143 // dir is (a) normal file, (b) symlink to a file, (c) empty directory or 144 // (d) symlink to a directory 145 return true; 146 } 147 // handle nonempty directory deletion 148 if (!fullyDeleteContents(dir, tryGrantPermissions)) { 149 return false; 150 } 151 return deleteImpl(dir, true); 152 } 153 154 /** 155 * Returns the target of the given symlink. Returns the empty string if 156 * the given path does not refer to a symlink or there is an error 157 * accessing the symlink. 158 * @param f File representing the symbolic link. 159 * @return The target of the symbolic link, empty string on error or if not 160 * a symlink. 161 */ 162 public static String readLink(File f) { 163 /* NB: Use readSymbolicLink in java.nio.file.Path once available. Could 164 * use getCanonicalPath in File to get the target of the symlink but that 165 * does not indicate if the given path refers to a symlink. 166 */ 167 try { 168 return Shell.execCommand( 169 Shell.getReadlinkCommand(f.toString())).trim(); 170 } catch (IOException x) { 171 return ""; 172 } 173 } 174 175 /* 176 * Pure-Java implementation of "chmod +rwx f". 177 */ 178 private static void grantPermissions(final File f) { 179 FileUtil.setExecutable(f, true); 180 FileUtil.setReadable(f, true); 181 FileUtil.setWritable(f, true); 182 } 183 184 private static boolean deleteImpl(final File f, final boolean doLog) { 185 if (f == null) { 186 LOG.warn("null file argument."); 187 return false; 188 } 189 final boolean wasDeleted = f.delete(); 190 if (wasDeleted) { 191 return true; 192 } 193 final boolean ex = f.exists(); 194 if (doLog && ex) { 195 LOG.warn("Failed to delete file or dir [" 196 + f.getAbsolutePath() + "]: it still exists."); 197 } 198 return !ex; 199 } 200 201 /** 202 * Delete the contents of a directory, not the directory itself. If 203 * we return false, the directory may be partially-deleted. 204 * If dir is a symlink to a directory, all the contents of the actual 205 * directory pointed to by dir will be deleted. 206 */ 207 public static boolean fullyDeleteContents(final File dir) { 208 return fullyDeleteContents(dir, false); 209 } 210 211 /** 212 * Delete the contents of a directory, not the directory itself. If 213 * we return false, the directory may be partially-deleted. 214 * If dir is a symlink to a directory, all the contents of the actual 215 * directory pointed to by dir will be deleted. 216 * @param tryGrantPermissions if 'true', try grant +rwx permissions to this 217 * and all the underlying directories before trying to delete their contents. 218 */ 219 public static boolean fullyDeleteContents(final File dir, final boolean tryGrantPermissions) { 220 if (tryGrantPermissions) { 221 // to be able to list the dir and delete files from it 222 // we must grant the dir rwx permissions: 223 grantPermissions(dir); 224 } 225 boolean deletionSucceeded = true; 226 final File[] contents = dir.listFiles(); 227 if (contents != null) { 228 for (int i = 0; i < contents.length; i++) { 229 if (contents[i].isFile()) { 230 if (!deleteImpl(contents[i], true)) {// normal file or symlink to another file 231 deletionSucceeded = false; 232 continue; // continue deletion of other files/dirs under dir 233 } 234 } else { 235 // Either directory or symlink to another directory. 236 // Try deleting the directory as this might be a symlink 237 boolean b = false; 238 b = deleteImpl(contents[i], false); 239 if (b){ 240 //this was indeed a symlink or an empty directory 241 continue; 242 } 243 // if not an empty directory or symlink let 244 // fullydelete handle it. 245 if (!fullyDelete(contents[i], tryGrantPermissions)) { 246 deletionSucceeded = false; 247 // continue deletion of other files/dirs under dir 248 } 249 } 250 } 251 } 252 return deletionSucceeded; 253 } 254 255 /** 256 * Recursively delete a directory. 257 * 258 * @param fs {@link FileSystem} on which the path is present 259 * @param dir directory to recursively delete 260 * @throws IOException 261 * @deprecated Use {@link FileSystem#delete(Path, boolean)} 262 */ 263 @Deprecated 264 public static void fullyDelete(FileSystem fs, Path dir) 265 throws IOException { 266 fs.delete(dir, true); 267 } 268 269 // 270 // If the destination is a subdirectory of the source, then 271 // generate exception 272 // 273 private static void checkDependencies(FileSystem srcFS, 274 Path src, 275 FileSystem dstFS, 276 Path dst) 277 throws IOException { 278 if (srcFS == dstFS) { 279 String srcq = src.makeQualified(srcFS).toString() + Path.SEPARATOR; 280 String dstq = dst.makeQualified(dstFS).toString() + Path.SEPARATOR; 281 if (dstq.startsWith(srcq)) { 282 if (srcq.length() == dstq.length()) { 283 throw new IOException("Cannot copy " + src + " to itself."); 284 } else { 285 throw new IOException("Cannot copy " + src + " to its subdirectory " + 286 dst); 287 } 288 } 289 } 290 } 291 292 /** Copy files between FileSystems. */ 293 public static boolean copy(FileSystem srcFS, Path src, 294 FileSystem dstFS, Path dst, 295 boolean deleteSource, 296 Configuration conf) throws IOException { 297 return copy(srcFS, src, dstFS, dst, deleteSource, true, conf); 298 } 299 300 public static boolean copy(FileSystem srcFS, Path[] srcs, 301 FileSystem dstFS, Path dst, 302 boolean deleteSource, 303 boolean overwrite, Configuration conf) 304 throws IOException { 305 boolean gotException = false; 306 boolean returnVal = true; 307 StringBuilder exceptions = new StringBuilder(); 308 309 if (srcs.length == 1) 310 return copy(srcFS, srcs[0], dstFS, dst, deleteSource, overwrite, conf); 311 312 // Check if dest is directory 313 if (!dstFS.exists(dst)) { 314 throw new IOException("`" + dst +"': specified destination directory " + 315 "does not exist"); 316 } else { 317 FileStatus sdst = dstFS.getFileStatus(dst); 318 if (!sdst.isDirectory()) 319 throw new IOException("copying multiple files, but last argument `" + 320 dst + "' is not a directory"); 321 } 322 323 for (Path src : srcs) { 324 try { 325 if (!copy(srcFS, src, dstFS, dst, deleteSource, overwrite, conf)) 326 returnVal = false; 327 } catch (IOException e) { 328 gotException = true; 329 exceptions.append(e.getMessage()); 330 exceptions.append("\n"); 331 } 332 } 333 if (gotException) { 334 throw new IOException(exceptions.toString()); 335 } 336 return returnVal; 337 } 338 339 /** Copy files between FileSystems. */ 340 public static boolean copy(FileSystem srcFS, Path src, 341 FileSystem dstFS, Path dst, 342 boolean deleteSource, 343 boolean overwrite, 344 Configuration conf) throws IOException { 345 FileStatus fileStatus = srcFS.getFileStatus(src); 346 return copy(srcFS, fileStatus, dstFS, dst, deleteSource, overwrite, conf); 347 } 348 349 /** Copy files between FileSystems. */ 350 public static boolean copy(FileSystem srcFS, FileStatus srcStatus, 351 FileSystem dstFS, Path dst, 352 boolean deleteSource, 353 boolean overwrite, 354 Configuration conf) throws IOException { 355 Path src = srcStatus.getPath(); 356 dst = checkDest(src.getName(), dstFS, dst, overwrite); 357 if (srcStatus.isDirectory()) { 358 checkDependencies(srcFS, src, dstFS, dst); 359 if (!dstFS.mkdirs(dst)) { 360 return false; 361 } 362 FileStatus contents[] = srcFS.listStatus(src); 363 for (int i = 0; i < contents.length; i++) { 364 copy(srcFS, contents[i], dstFS, 365 new Path(dst, contents[i].getPath().getName()), 366 deleteSource, overwrite, conf); 367 } 368 } else if (srcStatus.isTable()) { 369 throw new IOException("Cannot copy MDP Tables"); 370 } else { 371 InputStream in=null; 372 OutputStream out = null; 373 try { 374 in = srcFS.open(src); 375 out = dstFS.create(dst, overwrite); 376 IOUtils.copyBytes(in, out, conf, true); 377 } catch (IOException e) { 378 IOUtils.closeStream(out); 379 IOUtils.closeStream(in); 380 throw e; 381 } 382 } 383 if (deleteSource) { 384 return srcFS.delete(src, true); 385 } else { 386 return true; 387 } 388 389 } 390 391 /** Copy all files in a directory to one output file (merge). */ 392 public static boolean copyMerge(FileSystem srcFS, Path srcDir, 393 FileSystem dstFS, Path dstFile, 394 boolean deleteSource, 395 Configuration conf, String addString) throws IOException { 396 dstFile = checkDest(srcDir.getName(), dstFS, dstFile, false); 397 398 if (!srcFS.getFileStatus(srcDir).isDirectory()) 399 return false; 400 401 OutputStream out = dstFS.create(dstFile); 402 403 try { 404 FileStatus contents[] = srcFS.listStatus(srcDir); 405 Arrays.sort(contents); 406 for (int i = 0; i < contents.length; i++) { 407 if (contents[i].isFile()) { 408 InputStream in = srcFS.open(contents[i].getPath()); 409 try { 410 IOUtils.copyBytes(in, out, conf, false); 411 if (addString!=null) 412 out.write(addString.getBytes("UTF-8")); 413 414 } finally { 415 in.close(); 416 } 417 } 418 } 419 } finally { 420 out.close(); 421 } 422 423 424 if (deleteSource) { 425 return srcFS.delete(srcDir, true); 426 } else { 427 return true; 428 } 429 } 430 431 /** Copy local files to a FileSystem. */ 432 public static boolean copy(File src, 433 FileSystem dstFS, Path dst, 434 boolean deleteSource, 435 Configuration conf) throws IOException { 436 dst = checkDest(src.getName(), dstFS, dst, false); 437 438 if (src.isDirectory()) { 439 if (!dstFS.mkdirs(dst)) { 440 return false; 441 } 442 File contents[] = listFiles(src); 443 for (int i = 0; i < contents.length; i++) { 444 copy(contents[i], dstFS, new Path(dst, contents[i].getName()), 445 deleteSource, conf); 446 } 447 } else if (src.isFile()) { 448 InputStream in = null; 449 OutputStream out =null; 450 try { 451 in = new FileInputStream(src); 452 out = dstFS.create(dst); 453 IOUtils.copyBytes(in, out, conf); 454 } catch (IOException e) { 455 IOUtils.closeStream( out ); 456 IOUtils.closeStream( in ); 457 throw e; 458 } 459 } else { 460 throw new IOException(src.toString() + 461 ": No such file or directory"); 462 } 463 if (deleteSource) { 464 return FileUtil.fullyDelete(src); 465 } else { 466 return true; 467 } 468 } 469 470 /** Copy FileSystem files to local files. */ 471 public static boolean copy(FileSystem srcFS, Path src, 472 File dst, boolean deleteSource, 473 Configuration conf) throws IOException { 474 FileStatus filestatus = srcFS.getFileStatus(src); 475 return copy(srcFS, filestatus, dst, deleteSource, conf); 476 } 477 478 /** Copy FileSystem files to local files. */ 479 private static boolean copy(FileSystem srcFS, FileStatus srcStatus, 480 File dst, boolean deleteSource, 481 Configuration conf) throws IOException { 482 Path src = srcStatus.getPath(); 483 if (srcStatus.isDirectory()) { 484 if (!dst.mkdirs()) { 485 return false; 486 } 487 FileStatus contents[] = srcFS.listStatus(src); 488 for (int i = 0; i < contents.length; i++) { 489 copy(srcFS, contents[i], 490 new File(dst, contents[i].getPath().getName()), 491 deleteSource, conf); 492 } 493 } else { 494 InputStream in = srcFS.open(src); 495 IOUtils.copyBytes(in, new FileOutputStream(dst), conf); 496 } 497 if (deleteSource) { 498 return srcFS.delete(src, true); 499 } else { 500 return true; 501 } 502 } 503 504 private static Path checkDest(String srcName, FileSystem dstFS, Path dst, 505 boolean overwrite) throws IOException { 506 if (dstFS.exists(dst)) { 507 FileStatus sdst = dstFS.getFileStatus(dst); 508 if (sdst.isDirectory()) { 509 if (null == srcName) { 510 throw new IOException("Target " + dst + " is a directory"); 511 } 512 return checkDest(null, dstFS, new Path(dst, srcName), overwrite); 513 } else if (!overwrite) { 514 throw new IOException("Target " + dst + " already exists"); 515 } 516 } 517 return dst; 518 } 519 520 /** 521 * Convert a os-native filename to a path that works for the shell. 522 * @param filename The filename to convert 523 * @return The unix pathname 524 * @throws IOException on windows, there can be problems with the subprocess 525 */ 526 public static String makeShellPath(String filename) throws IOException { 527 return filename; 528 } 529 530 /** 531 * Convert a os-native filename to a path that works for the shell. 532 * @param file The filename to convert 533 * @return The unix pathname 534 * @throws IOException on windows, there can be problems with the subprocess 535 */ 536 public static String makeShellPath(File file) throws IOException { 537 return makeShellPath(file, false); 538 } 539 540 /** 541 * Convert a os-native filename to a path that works for the shell. 542 * @param file The filename to convert 543 * @param makeCanonicalPath 544 * Whether to make canonical path for the file passed 545 * @return The unix pathname 546 * @throws IOException on windows, there can be problems with the subprocess 547 */ 548 public static String makeShellPath(File file, boolean makeCanonicalPath) 549 throws IOException { 550 if (makeCanonicalPath) { 551 return makeShellPath(file.getCanonicalPath()); 552 } else { 553 return makeShellPath(file.toString()); 554 } 555 } 556 557 /** 558 * Takes an input dir and returns the du on that local directory. Very basic 559 * implementation. 560 * 561 * @param dir 562 * The input dir to get the disk space of this local dir 563 * @return The total disk space of the input local directory 564 */ 565 public static long getDU(File dir) { 566 long size = 0; 567 if (!dir.exists()) 568 return 0; 569 if (!dir.isDirectory()) { 570 return dir.length(); 571 } else { 572 File[] allFiles = dir.listFiles(); 573 if(allFiles != null) { 574 for (int i = 0; i < allFiles.length; i++) { 575 boolean isSymLink; 576 try { 577 isSymLink = org.apache.commons.io.FileUtils.isSymlink(allFiles[i]); 578 } catch(IOException ioe) { 579 isSymLink = true; 580 } 581 if(!isSymLink) { 582 size += getDU(allFiles[i]); 583 } 584 } 585 } 586 return size; 587 } 588 } 589 590 /** 591 * Given a File input it will unzip the file in a the unzip directory 592 * passed as the second parameter 593 * @param inFile The zip file as input 594 * @param unzipDir The unzip directory where to unzip the zip file. 595 * @throws IOException 596 */ 597 public static void unZip(File inFile, File unzipDir) throws IOException { 598 Enumeration<? extends ZipEntry> entries; 599 ZipFile zipFile = new ZipFile(inFile); 600 601 try { 602 entries = zipFile.entries(); 603 while (entries.hasMoreElements()) { 604 ZipEntry entry = entries.nextElement(); 605 if (!entry.isDirectory()) { 606 InputStream in = zipFile.getInputStream(entry); 607 try { 608 File file = new File(unzipDir, entry.getName()); 609 if (!file.getParentFile().mkdirs()) { 610 if (!file.getParentFile().isDirectory()) { 611 throw new IOException("Mkdirs failed to create " + 612 file.getParentFile().toString()); 613 } 614 } 615 OutputStream out = new FileOutputStream(file); 616 try { 617 byte[] buffer = new byte[8192]; 618 int i; 619 while ((i = in.read(buffer)) != -1) { 620 out.write(buffer, 0, i); 621 } 622 } finally { 623 out.close(); 624 } 625 } finally { 626 in.close(); 627 } 628 } 629 } 630 } finally { 631 zipFile.close(); 632 } 633 } 634 635 /** 636 * Given a Tar File as input it will untar the file in a the untar directory 637 * passed as the second parameter 638 * 639 * This utility will untar ".tar" files and ".tar.gz","tgz" files. 640 * 641 * @param inFile The tar file as input. 642 * @param untarDir The untar directory where to untar the tar file. 643 * @throws IOException 644 */ 645 public static void unTar(File inFile, File untarDir) throws IOException { 646 if (!untarDir.mkdirs()) { 647 if (!untarDir.isDirectory()) { 648 throw new IOException("Mkdirs failed to create " + untarDir); 649 } 650 } 651 652 boolean gzipped = inFile.toString().endsWith("gz"); 653 if(Shell.WINDOWS) { 654 // Tar is not native to Windows. Use simple Java based implementation for 655 // tests and simple tar archives 656 unTarUsingJava(inFile, untarDir, gzipped); 657 } 658 else { 659 // spawn tar utility to untar archive for full fledged unix behavior such 660 // as resolving symlinks in tar archives 661 unTarUsingTar(inFile, untarDir, gzipped); 662 } 663 } 664 665 private static void unTarUsingTar(File inFile, File untarDir, 666 boolean gzipped) throws IOException { 667 StringBuffer untarCommand = new StringBuffer(); 668 if (gzipped) { 669 untarCommand.append(" gzip -dc '"); 670 untarCommand.append(FileUtil.makeShellPath(inFile)); 671 untarCommand.append("' | ("); 672 } 673 untarCommand.append("cd '"); 674 untarCommand.append(FileUtil.makeShellPath(untarDir)); 675 untarCommand.append("' ; "); 676 untarCommand.append("tar -xf "); 677 678 if (gzipped) { 679 untarCommand.append(" -)"); 680 } else { 681 untarCommand.append(FileUtil.makeShellPath(inFile)); 682 } 683 String[] shellCmd = { "bash", "-c", untarCommand.toString() }; 684 ShellCommandExecutor shexec = new ShellCommandExecutor(shellCmd); 685 shexec.execute(); 686 int exitcode = shexec.getExitCode(); 687 if (exitcode != 0) { 688 throw new IOException("Error untarring file " + inFile + 689 ". Tar process exited with exit code " + exitcode); 690 } 691 } 692 693 private static void unTarUsingJava(File inFile, File untarDir, 694 boolean gzipped) throws IOException { 695 InputStream inputStream = null; 696 TarArchiveInputStream tis = null; 697 try { 698 if (gzipped) { 699 inputStream = new BufferedInputStream(new GZIPInputStream( 700 new FileInputStream(inFile))); 701 } else { 702 inputStream = new BufferedInputStream(new FileInputStream(inFile)); 703 } 704 705 tis = new TarArchiveInputStream(inputStream); 706 707 for (TarArchiveEntry entry = tis.getNextTarEntry(); entry != null;) { 708 unpackEntries(tis, entry, untarDir); 709 entry = tis.getNextTarEntry(); 710 } 711 } finally { 712 IOUtils.cleanup(LOG, tis, inputStream); 713 } 714 } 715 716 private static void unpackEntries(TarArchiveInputStream tis, 717 TarArchiveEntry entry, File outputDir) throws IOException { 718 if (entry.isDirectory()) { 719 File subDir = new File(outputDir, entry.getName()); 720 if (!subDir.mkdir() && !subDir.isDirectory()) { 721 throw new IOException("Mkdirs failed to create tar internal dir " 722 + outputDir); 723 } 724 725 for (TarArchiveEntry e : entry.getDirectoryEntries()) { 726 unpackEntries(tis, e, subDir); 727 } 728 729 return; 730 } 731 732 File outputFile = new File(outputDir, entry.getName()); 733 if (!outputDir.exists()) { 734 if (!outputDir.mkdirs()) { 735 throw new IOException("Mkdirs failed to create tar internal dir " 736 + outputDir); 737 } 738 } 739 740 int count; 741 byte data[] = new byte[2048]; 742 BufferedOutputStream outputStream = new BufferedOutputStream( 743 new FileOutputStream(outputFile)); 744 745 while ((count = tis.read(data)) != -1) { 746 outputStream.write(data, 0, count); 747 } 748 749 outputStream.flush(); 750 outputStream.close(); 751 } 752 753 /** 754 * Class for creating hardlinks. 755 * Supports Unix, WindXP. 756 * @deprecated Use {@link org.apache.hadoop.fs.HardLink} 757 */ 758 @Deprecated 759 public static class HardLink extends org.apache.hadoop.fs.HardLink { 760 // This is a stub to assist with coordinated change between 761 // COMMON and HDFS projects. It will be removed after the 762 // corresponding change is committed to HDFS. 763 } 764 765 /** 766 * Create a soft link between a src and destination 767 * only on a local disk. HDFS does not support this. 768 * On Windows, when symlink creation fails due to security 769 * setting, we will log a warning. The return code in this 770 * case is 2. 771 * 772 * @param target the target for symlink 773 * @param linkname the symlink 774 * @return 0 on success 775 */ 776 public static int symLink(String target, String linkname) throws IOException{ 777 // Run the input paths through Java's File so that they are converted to the 778 // native OS form 779 File targetFile = new File( 780 Path.getPathWithoutSchemeAndAuthority(new Path(target)).toString()); 781 File linkFile = new File( 782 Path.getPathWithoutSchemeAndAuthority(new Path(linkname)).toString()); 783 784 // If not on Java7+, copy a file instead of creating a symlink since 785 // Java6 has close to no support for symlinks on Windows. Specifically 786 // File#length and File#renameTo do not work as expected. 787 // (see HADOOP-9061 for additional details) 788 // We still create symlinks for directories, since the scenario in this 789 // case is different. The directory content could change in which 790 // case the symlink loses its purpose (for example task attempt log folder 791 // is symlinked under userlogs and userlogs are generated afterwards). 792 if (Shell.WINDOWS && !Shell.isJava7OrAbove() && targetFile.isFile()) { 793 try { 794 LOG.warn("FileUtil#symlink: On Windows+Java6, copying file instead " + 795 "of creating a symlink. Copying " + target + " -> " + linkname); 796 797 if (!linkFile.getParentFile().exists()) { 798 LOG.warn("Parent directory " + linkFile.getParent() + 799 " does not exist."); 800 return 1; 801 } else { 802 org.apache.commons.io.FileUtils.copyFile(targetFile, linkFile); 803 } 804 } catch (IOException ex) { 805 LOG.warn("FileUtil#symlink failed to copy the file with error: " 806 + ex.getMessage()); 807 // Exit with non-zero exit code 808 return 1; 809 } 810 return 0; 811 } 812 813 String[] cmd = Shell.getSymlinkCommand( 814 targetFile.toString(), 815 linkFile.toString()); 816 817 ShellCommandExecutor shExec; 818 try { 819 if (Shell.WINDOWS && 820 linkFile.getParentFile() != null && 821 !new Path(target).isAbsolute()) { 822 // Relative links on Windows must be resolvable at the time of 823 // creation. To ensure this we run the shell command in the directory 824 // of the link. 825 // 826 shExec = new ShellCommandExecutor(cmd, linkFile.getParentFile()); 827 } else { 828 shExec = new ShellCommandExecutor(cmd); 829 } 830 shExec.execute(); 831 } catch (Shell.ExitCodeException ec) { 832 int returnVal = ec.getExitCode(); 833 if (Shell.WINDOWS && returnVal == SYMLINK_NO_PRIVILEGE) { 834 LOG.warn("Fail to create symbolic links on Windows. " 835 + "The default security settings in Windows disallow non-elevated " 836 + "administrators and all non-administrators from creating symbolic links. " 837 + "This behavior can be changed in the Local Security Policy management console"); 838 } else if (returnVal != 0) { 839 LOG.warn("Command '" + StringUtils.join(" ", cmd) + "' failed " 840 + returnVal + " with: " + ec.getMessage()); 841 } 842 return returnVal; 843 } catch (IOException e) { 844 if (LOG.isDebugEnabled()) { 845 LOG.debug("Error while create symlink " + linkname + " to " + target 846 + "." + " Exception: " + StringUtils.stringifyException(e)); 847 } 848 throw e; 849 } 850 return shExec.getExitCode(); 851 } 852 853 /** 854 * Change the permissions on a filename. 855 * @param filename the name of the file to change 856 * @param perm the permission string 857 * @return the exit code from the command 858 * @throws IOException 859 * @throws InterruptedException 860 */ 861 public static int chmod(String filename, String perm 862 ) throws IOException, InterruptedException { 863 return chmod(filename, perm, false); 864 } 865 866 /** 867 * Change the permissions on a file / directory, recursively, if 868 * needed. 869 * @param filename name of the file whose permissions are to change 870 * @param perm permission string 871 * @param recursive true, if permissions should be changed recursively 872 * @return the exit code from the command. 873 * @throws IOException 874 */ 875 public static int chmod(String filename, String perm, boolean recursive) 876 throws IOException { 877 878 if (NativeIO.isAvailable()) { 879 final Configuration conf = new Configuration(); 880 final FileSystem lfs = FileSystem.getLocal(conf); 881 final ChmodParser pp; 882 try { 883 pp = new ChmodParser(perm); 884 } catch (IllegalArgumentException e) { 885 throw new IOException("Invalid arg: " + perm, e); 886 } 887 final FileStatus fstat = lfs.getFileStatus(new Path(filename)); 888 889 chmod(lfs, fstat, pp, recursive); 890 return 0; 891 } else { 892 String [] cmd = Shell.getSetPermissionCommand(perm, recursive); 893 String[] args = new String[cmd.length + 1]; 894 System.arraycopy(cmd, 0, args, 0, cmd.length); 895 args[cmd.length] = new File(filename).getPath(); 896 ShellCommandExecutor shExec = new ShellCommandExecutor(args); 897 try { 898 shExec.execute(); 899 }catch(IOException e) { 900 if(LOG.isDebugEnabled()) { 901 LOG.debug("Error while changing permission : " + filename 902 +" Exception: " + StringUtils.stringifyException(e)); 903 } 904 } 905 return shExec.getExitCode(); 906 } 907 } 908 909 public static void chmod(FileSystem fs, FileStatus fstat, ChmodParser pp, 910 boolean recursive) throws IOException { 911 912 final short newMode = pp.applyNewPermission(fstat); 913 final short oldMode = fstat.getPermission().toShort(); 914 if (newMode != oldMode) { 915 fs.setPermission(fstat.getPath(), new FsPermission(newMode)); 916 } 917 918 if (recursive && fstat.isDir()) { 919 final FileStatus[] kids = fs.listStatus(fstat.getPath()); 920 for (FileStatus kidStatus : kids) { 921 chmod(fs, kidStatus, pp, recursive); 922 } 923 } 924 } 925 926 /** 927 * Set the ownership on a file / directory. User name and group name 928 * cannot both be null. 929 * @param file the file to change 930 * @param username the new user owner name 931 * @param groupname the new group owner name 932 * @throws IOException 933 */ 934 public static void setOwner(File file, String username, 935 String groupname) throws IOException { 936 if (username == null && groupname == null) { 937 throw new IOException("username == null && groupname == null"); 938 } 939 940 if (NativeIO.isAvailable()) { 941 NativeIO.POSIX.chown(file.getCanonicalPath(), username, groupname); 942 } else { 943 String arg = (username == null ? "" : username) 944 + (groupname == null ? "" : ":" + groupname); 945 String [] cmd = Shell.getSetOwnerCommand(arg); 946 execCommand(file, cmd); 947 } 948 } 949 950 /** 951 * Platform independent implementation for {@link File#setReadable(boolean)} 952 * File#setReadable does not work as expected on Windows. 953 * @param f input file 954 * @param readable 955 * @return true on success, false otherwise 956 */ 957 public static boolean setReadable(File f, boolean readable) { 958 if (Shell.WINDOWS) { 959 try { 960 String permission = readable ? "u+r" : "u-r"; 961 FileUtil.chmod(f.getCanonicalPath(), permission, false); 962 return true; 963 } catch (IOException ex) { 964 return false; 965 } 966 } else { 967 return f.setReadable(readable); 968 } 969 } 970 971 /** 972 * Platform independent implementation for {@link File#setWritable(boolean)} 973 * File#setWritable does not work as expected on Windows. 974 * @param f input file 975 * @param writable 976 * @return true on success, false otherwise 977 */ 978 public static boolean setWritable(File f, boolean writable) { 979 if (Shell.WINDOWS) { 980 try { 981 String permission = writable ? "u+w" : "u-w"; 982 FileUtil.chmod(f.getCanonicalPath(), permission, false); 983 return true; 984 } catch (IOException ex) { 985 return false; 986 } 987 } else { 988 return f.setWritable(writable); 989 } 990 } 991 992 /** 993 * Platform independent implementation for {@link File#setExecutable(boolean)} 994 * File#setExecutable does not work as expected on Windows. 995 * Note: revoking execute permission on folders does not have the same 996 * behavior on Windows as on Unix platforms. Creating, deleting or renaming 997 * a file within that folder will still succeed on Windows. 998 * @param f input file 999 * @param executable 1000 * @return true on success, false otherwise 1001 */ 1002 public static boolean setExecutable(File f, boolean executable) { 1003 if (Shell.WINDOWS) { 1004 try { 1005 String permission = executable ? "u+x" : "u-x"; 1006 FileUtil.chmod(f.getCanonicalPath(), permission, false); 1007 return true; 1008 } catch (IOException ex) { 1009 return false; 1010 } 1011 } else { 1012 return f.setExecutable(executable); 1013 } 1014 } 1015 1016 /** 1017 * Platform independent implementation for {@link File#canRead()} 1018 * @param f input file 1019 * @return On Unix, same as {@link File#canRead()} 1020 * On Windows, true if process has read access on the path 1021 */ 1022 public static boolean canRead(File f) { 1023 if (Shell.WINDOWS) { 1024 try { 1025 return NativeIO.Windows.access(f.getCanonicalPath(), 1026 NativeIO.Windows.AccessRight.ACCESS_READ); 1027 } catch (IOException e) { 1028 return false; 1029 } 1030 } else { 1031 return f.canRead(); 1032 } 1033 } 1034 1035 /** 1036 * Platform independent implementation for {@link File#canWrite()} 1037 * @param f input file 1038 * @return On Unix, same as {@link File#canWrite()} 1039 * On Windows, true if process has write access on the path 1040 */ 1041 public static boolean canWrite(File f) { 1042 if (Shell.WINDOWS) { 1043 try { 1044 return NativeIO.Windows.access(f.getCanonicalPath(), 1045 NativeIO.Windows.AccessRight.ACCESS_WRITE); 1046 } catch (IOException e) { 1047 return false; 1048 } 1049 } else { 1050 return f.canWrite(); 1051 } 1052 } 1053 1054 /** 1055 * Platform independent implementation for {@link File#canExecute()} 1056 * @param f input file 1057 * @return On Unix, same as {@link File#canExecute()} 1058 * On Windows, true if process has execute access on the path 1059 */ 1060 public static boolean canExecute(File f) { 1061 if (Shell.WINDOWS) { 1062 try { 1063 return NativeIO.Windows.access(f.getCanonicalPath(), 1064 NativeIO.Windows.AccessRight.ACCESS_EXECUTE); 1065 } catch (IOException e) { 1066 return false; 1067 } 1068 } else { 1069 return f.canExecute(); 1070 } 1071 } 1072 1073 /** 1074 * Set permissions to the required value. Uses the java primitives instead 1075 * of forking if group == other. 1076 * @param f the file to change 1077 * @param permission the new permissions 1078 * @throws IOException 1079 */ 1080 public static void setPermission(File f, FsPermission permission 1081 ) throws IOException { 1082 FsAction user = permission.getUserAction(); 1083 FsAction group = permission.getGroupAction(); 1084 FsAction other = permission.getOtherAction(); 1085 1086 // use the native/fork if the group/other permissions are different 1087 // or if the native is available or on Windows 1088 if (group != other || NativeIO.isAvailable() || Shell.WINDOWS) { 1089 execSetPermission(f, permission); 1090 return; 1091 } 1092 1093 boolean rv = true; 1094 1095 // read perms 1096 rv = f.setReadable(group.implies(FsAction.READ), false); 1097 checkReturnValue(rv, f, permission); 1098 if (group.implies(FsAction.READ) != user.implies(FsAction.READ)) { 1099 rv = f.setReadable(user.implies(FsAction.READ), true); 1100 checkReturnValue(rv, f, permission); 1101 } 1102 1103 // write perms 1104 rv = f.setWritable(group.implies(FsAction.WRITE), false); 1105 checkReturnValue(rv, f, permission); 1106 if (group.implies(FsAction.WRITE) != user.implies(FsAction.WRITE)) { 1107 rv = f.setWritable(user.implies(FsAction.WRITE), true); 1108 checkReturnValue(rv, f, permission); 1109 } 1110 1111 // exec perms 1112 rv = f.setExecutable(group.implies(FsAction.EXECUTE), false); 1113 checkReturnValue(rv, f, permission); 1114 if (group.implies(FsAction.EXECUTE) != user.implies(FsAction.EXECUTE)) { 1115 rv = f.setExecutable(user.implies(FsAction.EXECUTE), true); 1116 checkReturnValue(rv, f, permission); 1117 } 1118 } 1119 1120 private static void checkReturnValue(boolean rv, File p, 1121 FsPermission permission 1122 ) throws IOException { 1123 if (!rv) { 1124 throw new IOException("Failed to set permissions of path: " + p + 1125 " to " + 1126 String.format("%04o", permission.toShort())); 1127 } 1128 } 1129 1130 private static void execSetPermission(File f, 1131 FsPermission permission 1132 ) throws IOException { 1133 if (NativeIO.isAvailable()) { 1134 NativeIO.POSIX.chmod(f.getCanonicalPath(), permission.toShort()); 1135 } else { 1136 execCommand(f, Shell.getSetPermissionCommand( 1137 String.format("%04o", permission.toShort()), false)); 1138 } 1139 } 1140 1141 static String execCommand(File f, String... cmd) throws IOException { 1142 String[] args = new String[cmd.length + 1]; 1143 System.arraycopy(cmd, 0, args, 0, cmd.length); 1144 args[cmd.length] = f.getCanonicalPath(); 1145 String output = Shell.execCommand(args); 1146 return output; 1147 } 1148 1149 /** 1150 * Create a tmp file for a base file. 1151 * @param basefile the base file of the tmp 1152 * @param prefix file name prefix of tmp 1153 * @param isDeleteOnExit if true, the tmp will be deleted when the VM exits 1154 * @return a newly created tmp file 1155 * @exception IOException If a tmp file cannot created 1156 * @see java.io.File#createTempFile(String, String, File) 1157 * @see java.io.File#deleteOnExit() 1158 */ 1159 public static final File createLocalTempFile(final File basefile, 1160 final String prefix, 1161 final boolean isDeleteOnExit) 1162 throws IOException { 1163 File tmp = File.createTempFile(prefix + basefile.getName(), 1164 "", basefile.getParentFile()); 1165 if (isDeleteOnExit) { 1166 tmp.deleteOnExit(); 1167 } 1168 return tmp; 1169 } 1170 1171 /** 1172 * Move the src file to the name specified by target. 1173 * @param src the source file 1174 * @param target the target file 1175 * @exception IOException If this operation fails 1176 */ 1177 public static void replaceFile(File src, File target) throws IOException { 1178 /* renameTo() has two limitations on Windows platform. 1179 * src.renameTo(target) fails if 1180 * 1) If target already exists OR 1181 * 2) If target is already open for reading/writing. 1182 */ 1183 if (!src.renameTo(target)) { 1184 int retries = 5; 1185 while (target.exists() && !target.delete() && retries-- >= 0) { 1186 try { 1187 Thread.sleep(1000); 1188 } catch (InterruptedException e) { 1189 throw new IOException("replaceFile interrupted."); 1190 } 1191 } 1192 if (!src.renameTo(target)) { 1193 throw new IOException("Unable to rename " + src + 1194 " to " + target); 1195 } 1196 } 1197 } 1198 1199 /** 1200 * A wrapper for {@link File#listFiles()}. This java.io API returns null 1201 * when a dir is not a directory or for any I/O error. Instead of having 1202 * null check everywhere File#listFiles() is used, we will add utility API 1203 * to get around this problem. For the majority of cases where we prefer 1204 * an IOException to be thrown. 1205 * @param dir directory for which listing should be performed 1206 * @return list of files or empty list 1207 * @exception IOException for invalid directory or for a bad disk. 1208 */ 1209 public static File[] listFiles(File dir) throws IOException { 1210 File[] files = dir.listFiles(); 1211 if(files == null) { 1212 throw new IOException("Invalid directory or I/O error occurred for dir: " 1213 + dir.toString()); 1214 } 1215 return files; 1216 } 1217 1218 /** 1219 * A wrapper for {@link File#list()}. This java.io API returns null 1220 * when a dir is not a directory or for any I/O error. Instead of having 1221 * null check everywhere File#list() is used, we will add utility API 1222 * to get around this problem. For the majority of cases where we prefer 1223 * an IOException to be thrown. 1224 * @param dir directory for which listing should be performed 1225 * @return list of file names or empty string list 1226 * @exception IOException for invalid directory or for a bad disk. 1227 */ 1228 public static String[] list(File dir) throws IOException { 1229 String[] fileNames = dir.list(); 1230 if(fileNames == null) { 1231 throw new IOException("Invalid directory or I/O error occurred for dir: " 1232 + dir.toString()); 1233 } 1234 return fileNames; 1235 } 1236 1237 /** 1238 * Create a jar file at the given path, containing a manifest with a classpath 1239 * that references all specified entries. 1240 * 1241 * Some platforms may have an upper limit on command line length. For example, 1242 * the maximum command line length on Windows is 8191 characters, but the 1243 * length of the classpath may exceed this. To work around this limitation, 1244 * use this method to create a small intermediate jar with a manifest that 1245 * contains the full classpath. It returns the absolute path to the new jar, 1246 * which the caller may set as the classpath for a new process. 1247 * 1248 * Environment variable evaluation is not supported within a jar manifest, so 1249 * this method expands environment variables before inserting classpath entries 1250 * to the manifest. The method parses environment variables according to 1251 * platform-specific syntax (%VAR% on Windows, or $VAR otherwise). On Windows, 1252 * environment variables are case-insensitive. For example, %VAR% and %var% 1253 * evaluate to the same value. 1254 * 1255 * Specifying the classpath in a jar manifest does not support wildcards, so 1256 * this method expands wildcards internally. Any classpath entry that ends 1257 * with * is translated to all files at that path with extension .jar or .JAR. 1258 * 1259 * @param inputClassPath String input classpath to bundle into the jar manifest 1260 * @param pwd Path to working directory to save jar 1261 * @param callerEnv Map<String, String> caller's environment variables to use 1262 * for expansion 1263 * @return String absolute path to new jar 1264 * @throws IOException if there is an I/O error while writing the jar file 1265 */ 1266 public static String createJarWithClassPath(String inputClassPath, Path pwd, 1267 Map<String, String> callerEnv) throws IOException { 1268 // Replace environment variables, case-insensitive on Windows 1269 @SuppressWarnings("unchecked") 1270 Map<String, String> env = Shell.WINDOWS ? new CaseInsensitiveMap(callerEnv) : 1271 callerEnv; 1272 String[] classPathEntries = inputClassPath.split(File.pathSeparator); 1273 for (int i = 0; i < classPathEntries.length; ++i) { 1274 classPathEntries[i] = StringUtils.replaceTokens(classPathEntries[i], 1275 StringUtils.ENV_VAR_PATTERN, env); 1276 } 1277 File workingDir = new File(pwd.toString()); 1278 if (!workingDir.mkdirs()) { 1279 // If mkdirs returns false because the working directory already exists, 1280 // then this is acceptable. If it returns false due to some other I/O 1281 // error, then this method will fail later with an IOException while saving 1282 // the jar. 1283 LOG.debug("mkdirs false for " + workingDir + ", execution will continue"); 1284 } 1285 1286 // Append all entries 1287 List<String> classPathEntryList = new ArrayList<String>( 1288 classPathEntries.length); 1289 for (String classPathEntry: classPathEntries) { 1290 if (classPathEntry.length() == 0) { 1291 continue; 1292 } 1293 if (classPathEntry.endsWith("*")) { 1294 // Append all jars that match the wildcard 1295 Path globPath = new Path(classPathEntry).suffix("{.jar,.JAR}"); 1296 FileStatus[] wildcardJars = FileContext.getLocalFSFileContext().util() 1297 .globStatus(globPath); 1298 if (wildcardJars != null) { 1299 for (FileStatus wildcardJar: wildcardJars) { 1300 classPathEntryList.add(wildcardJar.getPath().toUri().toURL() 1301 .toExternalForm()); 1302 } 1303 } 1304 } else { 1305 // Append just this entry 1306 File fileCpEntry = null; 1307 if(!new Path(classPathEntry).isAbsolute()) { 1308 fileCpEntry = new File(workingDir, classPathEntry); 1309 } 1310 else { 1311 fileCpEntry = new File(classPathEntry); 1312 } 1313 String classPathEntryUrl = fileCpEntry.toURI().toURL() 1314 .toExternalForm(); 1315 1316 // File.toURI only appends trailing '/' if it can determine that it is a 1317 // directory that already exists. (See JavaDocs.) If this entry had a 1318 // trailing '/' specified by the caller, then guarantee that the 1319 // classpath entry in the manifest has a trailing '/', and thus refers to 1320 // a directory instead of a file. This can happen if the caller is 1321 // creating a classpath jar referencing a directory that hasn't been 1322 // created yet, but will definitely be created before running. 1323 if (classPathEntry.endsWith(Path.SEPARATOR) && 1324 !classPathEntryUrl.endsWith(Path.SEPARATOR)) { 1325 classPathEntryUrl = classPathEntryUrl + Path.SEPARATOR; 1326 } 1327 classPathEntryList.add(classPathEntryUrl); 1328 } 1329 } 1330 String jarClassPath = StringUtils.join(" ", classPathEntryList); 1331 1332 // Create the manifest 1333 Manifest jarManifest = new Manifest(); 1334 jarManifest.getMainAttributes().putValue( 1335 Attributes.Name.MANIFEST_VERSION.toString(), "1.0"); 1336 jarManifest.getMainAttributes().putValue( 1337 Attributes.Name.CLASS_PATH.toString(), jarClassPath); 1338 1339 // Write the manifest to output JAR file 1340 File classPathJar = File.createTempFile("classpath-", ".jar", workingDir); 1341 FileOutputStream fos = null; 1342 BufferedOutputStream bos = null; 1343 JarOutputStream jos = null; 1344 try { 1345 fos = new FileOutputStream(classPathJar); 1346 bos = new BufferedOutputStream(fos); 1347 jos = new JarOutputStream(bos, jarManifest); 1348 } finally { 1349 IOUtils.cleanup(LOG, jos, bos, fos); 1350 } 1351 1352 return classPathJar.getCanonicalPath(); 1353 } 1354}