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