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