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