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