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