1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.util; 17 18 import com.android.ddmlib.Log; 19 import com.android.tradefed.command.FatalHostError; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.result.LogDataType; 23 24 import java.io.BufferedInputStream; 25 import java.io.BufferedOutputStream; 26 import java.io.ByteArrayInputStream; 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.FileNotFoundException; 30 import java.io.FileOutputStream; 31 import java.io.FilenameFilter; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.nio.file.Files; 36 import java.nio.file.Paths; 37 import java.nio.file.attribute.PosixFilePermission; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.EnumSet; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Set; 46 import java.util.zip.ZipFile; 47 48 /** 49 * A helper class for file related operations 50 */ 51 public class FileUtil { 52 53 private static final String LOG_TAG = "FileUtil"; 54 /** 55 * The minimum allowed disk space in megabytes. File creation methods will throw 56 * {@link LowDiskSpaceException} if the usable disk space in desired partition is less than 57 * this amount. 58 */ 59 @Option(name = "min-disk-space", description = "The minimum allowed disk" 60 + " space in megabytes for file-creation methods. May be set to" 61 + " 0 to disable checking.") 62 private static long mMinDiskSpaceMb = 100; 63 64 private static final char[] SIZE_SPECIFIERS = { 65 ' ', 'K', 'M', 'G', 'T' 66 }; 67 68 private static String sChmod = "chmod"; 69 70 /** A map of {@link PosixFilePermission} to its corresponding Unix file mode */ 71 private static final Map<PosixFilePermission, Integer> PERM_MODE_MAP = new HashMap<>(); 72 static { 73 PERM_MODE_MAP.put(PosixFilePermission.OWNER_READ, 0b100000000); 74 PERM_MODE_MAP.put(PosixFilePermission.OWNER_WRITE, 0b010000000); 75 PERM_MODE_MAP.put(PosixFilePermission.OWNER_EXECUTE, 0b001000000); 76 PERM_MODE_MAP.put(PosixFilePermission.GROUP_READ, 0b000100000); 77 PERM_MODE_MAP.put(PosixFilePermission.GROUP_WRITE, 0b000010000); 78 PERM_MODE_MAP.put(PosixFilePermission.GROUP_EXECUTE, 0b000001000); 79 PERM_MODE_MAP.put(PosixFilePermission.OTHERS_READ, 0b000000100); 80 PERM_MODE_MAP.put(PosixFilePermission.OTHERS_WRITE, 0b000000010); 81 PERM_MODE_MAP.put(PosixFilePermission.OTHERS_EXECUTE, 0b000000001); 82 } 83 84 public static final int FILESYSTEM_FILENAME_MAX_LENGTH = 255; 85 86 /** 87 * Exposed for testing. Allows to modify the chmod binary name we look for, in order to tests 88 * system with no chmod support. 89 */ 90 protected static void setChmodBinary(String chmodName) { 91 sChmod = chmodName; 92 } 93 94 /** 95 * Thrown if usable disk space is below minimum threshold. 96 */ 97 @SuppressWarnings("serial") 98 public static class LowDiskSpaceException extends FatalHostError { 99 100 LowDiskSpaceException(String msg, Throwable cause) { 101 super(msg, cause); 102 } 103 104 LowDiskSpaceException(String msg) { 105 super(msg); 106 } 107 108 } 109 110 /** 111 * Method to create a chain of directories, and set them all group execute/read/writable as they 112 * are created, by calling {@link #chmodGroupRWX(File)}. Essentially a version of 113 * {@link File#mkdirs()} that also runs {@link #chmod(File, String)}. 114 * 115 * @param file the name of the directory to create, possibly with containing directories that 116 * don't yet exist. 117 * @return {@code true} if {@code file} exists and is a directory, {@code false} otherwise. 118 */ 119 public static boolean mkdirsRWX(File file) { 120 File parent = file.getParentFile(); 121 122 if (parent != null && !parent.isDirectory()) { 123 // parent doesn't exist. recurse upward, which should both mkdir and chmod 124 if (!mkdirsRWX(parent)) { 125 // Couldn't mkdir parent, fail 126 Log.w(LOG_TAG, String.format("Failed to mkdir parent dir %s.", parent)); 127 return false; 128 } 129 } 130 131 // by this point the parent exists. Try to mkdir file 132 if (file.isDirectory() || file.mkdir()) { 133 // file should exist. Try chmod and complain if that fails, but keep going 134 boolean setPerms = chmodGroupRWX(file); 135 if (!setPerms) { 136 Log.w(LOG_TAG, String.format("Failed to set dir %s to be group accessible.", file)); 137 } 138 } 139 140 return file.isDirectory(); 141 } 142 143 public static boolean chmodRWXRecursively(File file) { 144 boolean success = true; 145 if (!file.setExecutable(true, false)) { 146 CLog.w("Failed to set %s executable.", file.getAbsolutePath()); 147 success = false; 148 } 149 if (!file.setWritable(true, false)) { 150 CLog.w("Failed to set %s writable.", file.getAbsolutePath()); 151 success = false; 152 } 153 if (!file.setReadable(true, false)) { 154 CLog.w("Failed to set %s readable", file.getAbsolutePath()); 155 success = false; 156 } 157 158 if (file.isDirectory()) { 159 File[] children = file.listFiles(); 160 for (File child : children) { 161 if (!chmodRWXRecursively(child)) { 162 success = false; 163 } 164 } 165 166 } 167 return success; 168 } 169 170 public static boolean chmod(File file, String perms) { 171 Log.d(LOG_TAG, String.format("Attempting to chmod %s to %s", 172 file.getAbsolutePath(), perms)); 173 CommandResult result = 174 RunUtil.getDefault().runTimedCmd(10 * 1000, sChmod, perms, file.getAbsolutePath()); 175 return result.getStatus().equals(CommandStatus.SUCCESS); 176 } 177 178 /** 179 * Performs a best effort attempt to make given file group readable and writable. 180 * <p/> 181 * Note that the execute permission is required to make directories accessible. See 182 * {@link #chmodGroupRWX(File)}. 183 * <p/> 184 * If 'chmod' system command is not supported by underlying OS, will set file to writable by 185 * all. 186 * 187 * @param file the {@link File} to make owner and group writable 188 * @return <code>true</code> if file was successfully made group writable, <code>false</code> 189 * otherwise 190 */ 191 public static boolean chmodGroupRW(File file) { 192 if (chmodExists()) { 193 if (chmod(file, "ug+rw")) { 194 return true; 195 } else { 196 Log.d(LOG_TAG, String.format("Failed chmod on %s", file.getAbsolutePath())); 197 return false; 198 } 199 } else { 200 Log.d(LOG_TAG, String.format("chmod not available; " 201 + "attempting to set %s globally RW", file.getAbsolutePath())); 202 return file.setWritable(true, false /* false == writable for all */) && 203 file.setReadable(true, false /* false == readable for all */); 204 } 205 } 206 207 /** 208 * Performs a best effort attempt to make given file group executable, readable, and writable. 209 * <p/> 210 * If 'chmod' system command is not supported by underlying OS, will attempt to set permissions 211 * for all users. 212 * 213 * @param file the {@link File} to make owner and group writable 214 * @return <code>true</code> if permissions were set successfully, <code>false</code> otherwise 215 */ 216 public static boolean chmodGroupRWX(File file) { 217 if (chmodExists()) { 218 if (chmod(file, "ug+rwx")) { 219 return true; 220 } else { 221 Log.d(LOG_TAG, String.format("Failed chmod on %s", file.getAbsolutePath())); 222 return false; 223 } 224 } else { 225 Log.d(LOG_TAG, String.format("chmod not available; " 226 + "attempting to set %s globally RWX", file.getAbsolutePath())); 227 return file.setExecutable(true, false /* false == executable for all */) && 228 file.setWritable(true, false /* false == writable for all */) && 229 file.setReadable(true, false /* false == readable for all */); 230 } 231 } 232 233 /** 234 * Internal helper to determine if 'chmod' is available on the system OS. 235 */ 236 protected static boolean chmodExists() { 237 // Silence the scary process exception when chmod is missing, we will log instead. 238 CommandResult result = RunUtil.getDefault().runTimedCmdSilently(10 * 1000, sChmod); 239 // We expect a status fail because 'chmod' requires arguments. 240 if (CommandStatus.FAILED.equals(result.getStatus()) && 241 result.getStderr().contains("chmod: missing operand")) { 242 return true; 243 } 244 CLog.w("Chmod is not supported by this OS."); 245 return false; 246 } 247 248 /** 249 * Recursively set read and exec (if folder) permissions for given file. 250 */ 251 public static void setReadableRecursive(File file) { 252 file.setReadable(true); 253 if (file.isDirectory()) { 254 file.setExecutable(true); 255 File[] children = file.listFiles(); 256 if (children != null) { 257 for (File childFile : file.listFiles()) { 258 setReadableRecursive(childFile); 259 } 260 } 261 } 262 } 263 264 /** 265 * Helper function to create a temp directory in the system default temporary file directory. 266 * 267 * @param prefix The prefix string to be used in generating the file's name; must be at least 268 * three characters long 269 * @return the created directory 270 * @throws IOException if file could not be created 271 */ 272 public static File createTempDir(String prefix) throws IOException { 273 return createTempDir(prefix, null); 274 } 275 276 /** 277 * Helper function to create a temp directory. 278 * 279 * @param prefix The prefix string to be used in generating the file's name; must be at least 280 * three characters long 281 * @param parentDir The parent directory in which the directory is to be created. If 282 * <code>null</code> the system default temp directory will be used. 283 * @return the created directory 284 * @throws IOException if file could not be created 285 */ 286 public static File createTempDir(String prefix, File parentDir) throws IOException { 287 // create a temp file with unique name, then make it a directory 288 if (parentDir != null) { 289 CLog.d("Creating temp directory at %s with prefix \"%s\"", 290 parentDir.getAbsolutePath(), prefix); 291 } 292 File tmpDir = File.createTempFile(prefix, "", parentDir); 293 return deleteFileAndCreateDirWithSameName(tmpDir); 294 } 295 296 private static File deleteFileAndCreateDirWithSameName(File tmpDir) throws IOException { 297 tmpDir.delete(); 298 return createDir(tmpDir); 299 } 300 301 private static File createDir(File tmpDir) throws IOException { 302 if (!tmpDir.mkdirs()) { 303 throw new IOException("unable to create directory"); 304 } 305 return tmpDir; 306 } 307 308 /** 309 * Helper function to create a named directory inside your temp folder. 310 * <p/> 311 * This directory will not have it's name randomized. If the directory already exists it will 312 * be returned. 313 * 314 * @param name The name of the directory to create in your tmp folder. 315 * @return the created directory 316 */ 317 public static File createNamedTempDir(String name) throws IOException { 318 File namedTmpDir = new File(System.getProperty("java.io.tmpdir"), name); 319 if (!namedTmpDir.exists()) { 320 createDir(namedTmpDir); 321 } 322 return namedTmpDir; 323 } 324 325 /** 326 * Helper wrapper function around {@link File#createTempFile(String, String)} that audits for 327 * potential out of disk space scenario. 328 * 329 * @see File#createTempFile(String, String) 330 * @throws LowDiskSpaceException if disk space on temporary partition is lower than minimum 331 * allowed 332 */ 333 public static File createTempFile(String prefix, String suffix) throws IOException { 334 return internalCreateTempFile(prefix, suffix, null); 335 } 336 337 /** 338 * Helper wrapper function around {@link File#createTempFile(String, String, File)} 339 * that audits for potential out of disk space scenario. 340 * 341 * @see File#createTempFile(String, String, File) 342 * @throws LowDiskSpaceException if disk space on partition is lower than minimum allowed 343 */ 344 public static File createTempFile(String prefix, String suffix, File parentDir) 345 throws IOException { 346 return internalCreateTempFile(prefix, suffix, parentDir); 347 } 348 349 /** 350 * Internal helper to create a temporary file. 351 */ 352 private static File internalCreateTempFile(String prefix, String suffix, File parentDir) 353 throws IOException { 354 // File.createTempFile add an additional random long in the name so we remove the length. 355 int overflowLength = prefix.length() + 19 - FILESYSTEM_FILENAME_MAX_LENGTH; 356 if (suffix != null) { 357 // suffix may be null 358 overflowLength += suffix.length(); 359 } 360 if (overflowLength > 0) { 361 CLog.w("Filename for prefix: %s and suffix: %s, would be too long for FileSystem," 362 + "truncating it.", prefix, suffix); 363 // We truncate from suffix in priority because File.createTempFile wants prefix to be 364 // at least 3 characters. 365 if (suffix.length() >= overflowLength) { 366 int temp = overflowLength; 367 overflowLength -= suffix.length(); 368 suffix = suffix.substring(temp, suffix.length()); 369 } else { 370 overflowLength -= suffix.length(); 371 suffix = ""; 372 } 373 if (overflowLength > 0) { 374 // Whatever remaining to remove after suffix has been truncating should be inside 375 // prefix, otherwise there would not be overflow. 376 prefix = prefix.substring(0, prefix.length() - overflowLength); 377 } 378 } 379 File returnFile = null; 380 if (parentDir != null) { 381 CLog.d("Creating temp file at %s with prefix \"%s\" suffix \"%s\"", 382 parentDir.getAbsolutePath(), prefix, suffix); 383 } 384 returnFile = File.createTempFile(prefix, suffix, parentDir); 385 verifyDiskSpace(returnFile); 386 return returnFile; 387 } 388 389 /** 390 * A helper method that hardlinks a file to another file 391 * 392 * @param origFile the original file 393 * @param destFile the destination file 394 * @throws IOException if failed to hardlink file 395 */ 396 public static void hardlinkFile(File origFile, File destFile) throws IOException { 397 // `ln src dest` will create a hardlink (note: not `ln -s src dest`, which creates symlink) 398 // note that this will fail across filesystem boundaries 399 // FIXME: should probably just fall back to normal copy if this fails 400 CommandResult result = linkFile(origFile, destFile, false); 401 if (!result.getStatus().equals(CommandStatus.SUCCESS)) { 402 throw new IOException(String.format( 403 "Failed to hardlink %s to %s. Across filesystem boundary?", 404 origFile.getAbsolutePath(), destFile.getAbsolutePath())); 405 } 406 } 407 408 /** 409 * A helper method that simlinks a file to another file 410 * 411 * @param origFile the original file 412 * @param destFile the destination file 413 * @throws IOException if failed to simlink file 414 */ 415 public static void simlinkFile(File origFile, File destFile) throws IOException { 416 CommandResult res = linkFile(origFile, destFile, true); 417 if (!CommandStatus.SUCCESS.equals(res.getStatus())) { 418 throw new IOException( 419 String.format( 420 "Error trying to simlink: %s\nstdout:%s\nstderr:%s", 421 res.getStatus(), res.getStdout(), res.getStderr())); 422 } 423 } 424 425 private static CommandResult linkFile(File origFile, File destFile, boolean simlink) 426 throws IOException { 427 if (!origFile.exists()) { 428 String link = simlink ? "simlink" : "hardlink"; 429 throw new IOException( 430 String.format( 431 "Cannot %s %s. File does not exist", link, origFile.getAbsolutePath())); 432 } 433 List<String> cmd = new ArrayList<>(); 434 cmd.add("ln"); 435 if (simlink) { 436 cmd.add("-s"); 437 } 438 cmd.add(origFile.getAbsolutePath()); 439 cmd.add(destFile.getAbsolutePath()); 440 CommandResult result = 441 RunUtil.getDefault().runTimedCmdSilently(10 * 1000, cmd.toArray(new String[0])); 442 return result; 443 } 444 445 /** 446 * Recursively hardlink folder contents. 447 * <p/> 448 * Only supports copying of files and directories - symlinks are not copied. If the destination 449 * directory does not exist, it will be created. 450 * 451 * @param sourceDir the folder that contains the files to copy 452 * @param destDir the destination folder 453 * @throws IOException 454 */ 455 public static void recursiveHardlink(File sourceDir, File destDir) throws IOException { 456 if (!destDir.isDirectory() && !destDir.mkdir()) { 457 throw new IOException(String.format("Could not create directory %s", 458 destDir.getAbsolutePath())); 459 } 460 for (File childFile : sourceDir.listFiles()) { 461 File destChild = new File(destDir, childFile.getName()); 462 if (childFile.isDirectory()) { 463 recursiveHardlink(childFile, destChild); 464 } else if (childFile.isFile()) { 465 hardlinkFile(childFile, destChild); 466 } 467 } 468 } 469 470 /** 471 * Recursively simlink folder contents. 472 * 473 * <p>Only supports copying of files and directories - symlinks are not copied. If the 474 * destination directory does not exist, it will be created. 475 * 476 * @param sourceDir the folder that contains the files to copy 477 * @param destDir the destination folder 478 * @throws IOException 479 */ 480 public static void recursiveSimlink(File sourceDir, File destDir) throws IOException { 481 if (!destDir.isDirectory() && !destDir.mkdir()) { 482 throw new IOException( 483 String.format("Could not create directory %s", destDir.getAbsolutePath())); 484 } 485 for (File childFile : sourceDir.listFiles()) { 486 File destChild = new File(destDir, childFile.getName()); 487 if (childFile.isDirectory()) { 488 recursiveSimlink(childFile, destChild); 489 } else if (childFile.isFile()) { 490 simlinkFile(childFile, destChild); 491 } 492 } 493 } 494 495 /** 496 * A helper method that copies a file's contents to a local file 497 * 498 * @param origFile the original file to be copied 499 * @param destFile the destination file 500 * @throws IOException if failed to copy file 501 */ 502 public static void copyFile(File origFile, File destFile) throws IOException { 503 writeToFile(new FileInputStream(origFile), destFile); 504 } 505 506 /** 507 * Recursively copy folder contents. 508 * <p/> 509 * Only supports copying of files and directories - symlinks are not copied. If the destination 510 * directory does not exist, it will be created. 511 * 512 * @param sourceDir the folder that contains the files to copy 513 * @param destDir the destination folder 514 * @throws IOException 515 */ 516 public static void recursiveCopy(File sourceDir, File destDir) throws IOException { 517 File[] childFiles = sourceDir.listFiles(); 518 if (childFiles == null) { 519 throw new IOException(String.format( 520 "Failed to recursively copy. Could not determine contents for directory '%s'", 521 sourceDir.getAbsolutePath())); 522 } 523 if (!destDir.isDirectory() && !destDir.mkdir()) { 524 throw new IOException(String.format("Could not create directory %s", 525 destDir.getAbsolutePath())); 526 } 527 for (File childFile : childFiles) { 528 File destChild = new File(destDir, childFile.getName()); 529 if (childFile.isDirectory()) { 530 recursiveCopy(childFile, destChild); 531 } else if (childFile.isFile()) { 532 copyFile(childFile, destChild); 533 } 534 } 535 } 536 537 /** 538 * A helper method for reading string data from a file 539 * 540 * @param sourceFile the file to read from 541 * @throws IOException 542 * @throws FileNotFoundException 543 */ 544 public static String readStringFromFile(File sourceFile) throws IOException { 545 FileInputStream is = null; 546 try { 547 // no need to buffer since StreamUtil does 548 is = new FileInputStream(sourceFile); 549 return StreamUtil.getStringFromStream(is); 550 } finally { 551 StreamUtil.close(is); 552 } 553 } 554 555 /** 556 * A helper method for writing string data to file 557 * 558 * @param inputString the input {@link String} 559 * @param destFile the destination file to write to 560 */ 561 public static void writeToFile(String inputString, File destFile) throws IOException { 562 writeToFile(inputString, destFile, false); 563 } 564 565 /** 566 * A helper method for writing or appending string data to file 567 * 568 * @param inputString the input {@link String} 569 * @param destFile the destination file to write or append to 570 * @param append append to end of file if true, overwrite otherwise 571 */ 572 public static void writeToFile(String inputString, File destFile, boolean append) 573 throws IOException { 574 writeToFile(new ByteArrayInputStream(inputString.getBytes()), destFile, append); 575 } 576 577 /** 578 * A helper method for writing stream data to file 579 * 580 * @param input the unbuffered input stream 581 * @param destFile the destination file to write to 582 */ 583 public static void writeToFile(InputStream input, File destFile) throws IOException { 584 writeToFile(input, destFile, false); 585 } 586 587 /** 588 * A helper method for writing stream data to file 589 * 590 * @param input the unbuffered input stream 591 * @param destFile the destination file to write or append to 592 * @param append append to end of file if true, overwrite otherwise 593 */ 594 public static void writeToFile( 595 InputStream input, File destFile, boolean append) throws IOException { 596 InputStream origStream = null; 597 OutputStream destStream = null; 598 try { 599 origStream = new BufferedInputStream(input); 600 destStream = new BufferedOutputStream(new FileOutputStream(destFile, append)); 601 StreamUtil.copyStreams(origStream, destStream); 602 } finally { 603 StreamUtil.close(origStream); 604 StreamUtil.flushAndCloseStream(destStream); 605 } 606 } 607 608 private static void verifyDiskSpace(File file) { 609 // Based on empirical testing File.getUsableSpace is a low cost operation (~ 100 us for 610 // local disk, ~ 100 ms for network disk). Therefore call it every time tmp file is 611 // created 612 long usableSpace = file.getUsableSpace(); 613 long minDiskSpace = mMinDiskSpaceMb * 1024 * 1024; 614 if (usableSpace < minDiskSpace) { 615 throw new LowDiskSpaceException(String.format( 616 "Available space on %s is %.2f MB. Min is %d MB", file.getAbsolutePath(), 617 file.getUsableSpace() / (1024.0 * 1024.0), mMinDiskSpaceMb)); 618 } 619 } 620 621 /** 622 * Recursively delete given file or directory and all its contents. 623 * 624 * @param rootDir the directory or file to be deleted; can be null 625 */ 626 public static void recursiveDelete(File rootDir) { 627 if (rootDir != null) { 628 if (rootDir.isDirectory()) { 629 File[] childFiles = rootDir.listFiles(); 630 if (childFiles != null) { 631 for (File child : childFiles) { 632 recursiveDelete(child); 633 } 634 } 635 } 636 rootDir.delete(); 637 } 638 } 639 640 /** 641 * Gets the extension for given file name. 642 * 643 * @param fileName 644 * @return the extension or empty String if file has no extension 645 */ 646 public static String getExtension(String fileName) { 647 int index = fileName.lastIndexOf('.'); 648 if (index == -1) { 649 return ""; 650 } else { 651 return fileName.substring(index); 652 } 653 } 654 655 /** 656 * Gets the base name, without extension, of given file name. 657 * <p/> 658 * e.g. getBaseName("file.txt") will return "file" 659 * 660 * @param fileName 661 * @return the base name 662 */ 663 public static String getBaseName(String fileName) { 664 int index = fileName.lastIndexOf('.'); 665 if (index == -1) { 666 return fileName; 667 } else { 668 return fileName.substring(0, index); 669 } 670 } 671 672 /** 673 * Utility method to do byte-wise content comparison of two files. 674 * 675 * @return <code>true</code> if file contents are identical 676 */ 677 public static boolean compareFileContents(File file1, File file2) throws IOException { 678 BufferedInputStream stream1 = null; 679 BufferedInputStream stream2 = null; 680 681 boolean result = true; 682 try { 683 stream1 = new BufferedInputStream(new FileInputStream(file1)); 684 stream2 = new BufferedInputStream(new FileInputStream(file2)); 685 boolean eof = false; 686 while (!eof) { 687 int byte1 = stream1.read(); 688 int byte2 = stream2.read(); 689 if (byte1 != byte2) { 690 result = false; 691 break; 692 } 693 eof = byte1 == -1; 694 } 695 } finally { 696 StreamUtil.close(stream1); 697 StreamUtil.close(stream2); 698 } 699 return result; 700 } 701 702 /** 703 * Helper method which constructs a unique file on temporary disk, whose name corresponds as 704 * closely as possible to the file name given by the remote file path 705 * 706 * @param remoteFilePath the '/' separated remote path to construct the name from 707 * @param parentDir the parent directory to create the file in. <code>null</code> to use the 708 * default temporary directory 709 */ 710 public static File createTempFileForRemote(String remoteFilePath, File parentDir) 711 throws IOException { 712 String[] segments = remoteFilePath.split("/"); 713 // take last segment as base name 714 String remoteFileName = segments[segments.length - 1]; 715 String prefix = getBaseName(remoteFileName); 716 if (prefix.length() < 3) { 717 // prefix must be at least 3 characters long 718 prefix = prefix + "XXX"; 719 } 720 String fileExt = getExtension(remoteFileName); 721 722 // create a unique file name. Add a underscore to prefix so file name is more readable 723 // e.g. myfile_57588758.img rather than myfile57588758.img 724 File tmpFile = FileUtil.createTempFile(prefix + "_", fileExt, parentDir); 725 return tmpFile; 726 } 727 728 /** 729 * Try to delete a file. Intended for use when cleaning up 730 * in {@code finally} stanzas. 731 * 732 * @param file may be null. 733 */ 734 public static void deleteFile(File file) { 735 if (file != null) { 736 file.delete(); 737 } 738 } 739 740 /** 741 * Helper method to build a system-dependent File 742 * 743 * @param parentDir the parent directory to use. 744 * @param pathSegments the relative path segments to use 745 * @return the {@link File} representing given path, with each <var>pathSegment</var> 746 * separated by {@link File#separatorChar} 747 */ 748 public static File getFileForPath(File parentDir, String... pathSegments) { 749 return new File(parentDir, getPath(pathSegments)); 750 } 751 752 /** 753 * Helper method to build a system-dependent relative path 754 * 755 * @param pathSegments the relative path segments to use 756 * @return the {@link String} representing given path, with each <var>pathSegment</var> 757 * separated by {@link File#separatorChar} 758 */ 759 public static String getPath(String... pathSegments) { 760 StringBuilder pathBuilder = new StringBuilder(); 761 boolean isFirst = true; 762 for (String path : pathSegments) { 763 if (!isFirst) { 764 pathBuilder.append(File.separatorChar); 765 } else { 766 isFirst = false; 767 } 768 pathBuilder.append(path); 769 } 770 return pathBuilder.toString(); 771 } 772 773 /** 774 * Recursively search given directory for first file with given name 775 * 776 * @param dir the directory to search 777 * @param fileName the name of the file to search for 778 * @return the {@link File} or <code>null</code> if it could not be found 779 */ 780 public static File findFile(File dir, String fileName) { 781 if (dir.listFiles() != null) { 782 for (File file : dir.listFiles()) { 783 if (file.getName().equals(fileName)) { 784 return file; 785 } else if (file.isDirectory()) { 786 File result = findFile(file, fileName); 787 if (result != null) { 788 return result; 789 } 790 } 791 } 792 } 793 return null; 794 } 795 796 /** 797 * Recursively find all directories under the given {@code rootDir} 798 * 799 * @param rootDir the root directory to search in 800 * @param relativeParent An optional parent for all {@link File}s returned. If not specified, 801 * all {@link File}s will be relative to {@code rootDir}. 802 * @return An set of {@link File}s, representing all directories under {@code rootDir}, 803 * including {@code rootDir} itself. If {@code rootDir} is null, an empty set is 804 * returned. 805 */ 806 public static Set<File> findDirsUnder(File rootDir, File relativeParent) { 807 Set<File> dirs = new HashSet<File>(); 808 if (rootDir != null) { 809 if (!rootDir.isDirectory()) { 810 throw new IllegalArgumentException("Can't find dirs under '" + rootDir 811 + "'. It's not a directory."); 812 } 813 File thisDir = new File(relativeParent, rootDir.getName()); 814 dirs.add(thisDir); 815 for (File file : rootDir.listFiles()) { 816 if (file.isDirectory()) { 817 dirs.addAll(findDirsUnder(file, thisDir)); 818 } 819 } 820 } 821 return dirs; 822 } 823 824 /** 825 * Convert the given file size in bytes to a more readable format in X.Y[KMGT] format. 826 * 827 * @param sizeLong file size in bytes 828 * @return descriptive string of file size 829 */ 830 public static String convertToReadableSize(long sizeLong) { 831 832 double size = sizeLong; 833 for (int i = 0; i < SIZE_SPECIFIERS.length; i++) { 834 if (size < 1024) { 835 return String.format("%.1f%c", size, SIZE_SPECIFIERS[i]); 836 } 837 size /= 1024f; 838 } 839 throw new IllegalArgumentException( 840 String.format("Passed a file size of %.2f, I cannot count that high", size)); 841 } 842 843 /** 844 * The inverse of {@link #convertToReadableSize(long)}. Converts the readable format described 845 * in {@link #convertToReadableSize(long)} to a byte value. 846 * 847 * @param sizeString the string description of the size. 848 * @return the size in bytes 849 * @throws IllegalArgumentException if cannot recognize size 850 */ 851 public static long convertSizeToBytes(String sizeString) throws IllegalArgumentException { 852 if (sizeString.isEmpty()) { 853 throw new IllegalArgumentException("invalid empty string"); 854 } 855 char sizeSpecifier = sizeString.charAt(sizeString.length() - 1); 856 long multiplier = findMultiplier(sizeSpecifier); 857 try { 858 String numberString = sizeString; 859 if (multiplier != 1) { 860 // strip off last char 861 numberString = sizeString.substring(0, sizeString.length() - 1); 862 } 863 return multiplier * Long.parseLong(numberString); 864 } catch (NumberFormatException e) { 865 throw new IllegalArgumentException(String.format("Unrecognized size %s", sizeString)); 866 } 867 } 868 869 private static long findMultiplier(char sizeSpecifier) { 870 long multiplier = 1; 871 for (int i = 1; i < SIZE_SPECIFIERS.length; i++) { 872 multiplier *= 1024; 873 if (sizeSpecifier == SIZE_SPECIFIERS[i]) { 874 return multiplier; 875 } 876 } 877 // not found 878 return 1; 879 } 880 881 /** 882 * Returns all jar files found in given directory 883 */ 884 public static List<File> collectJars(File dir) { 885 List<File> list = new ArrayList<File>(); 886 File[] jarFiles = dir.listFiles(new JarFilter()); 887 if (jarFiles != null) { 888 list.addAll(Arrays.asList(dir.listFiles(new JarFilter()))); 889 } 890 return list; 891 } 892 893 private static class JarFilter implements FilenameFilter { 894 /** 895 * {@inheritDoc} 896 */ 897 @Override 898 public boolean accept(File dir, String name) { 899 return name.endsWith(".jar"); 900 } 901 } 902 903 904 // Backwards-compatibility section 905 /** 906 * Utility method to extract entire contents of zip file into given directory 907 * 908 * @param zipFile the {@link ZipFile} to extract 909 * @param destDir the local dir to extract file to 910 * @throws IOException if failed to extract file 911 * @deprecated Moved to {@link ZipUtil#extractZip(ZipFile, File)}. 912 */ 913 @Deprecated 914 public static void extractZip(ZipFile zipFile, File destDir) throws IOException { 915 ZipUtil.extractZip(zipFile, destDir); 916 } 917 918 /** 919 * Utility method to extract one specific file from zip file into a tmp file 920 * 921 * @param zipFile the {@link ZipFile} to extract 922 * @param filePath the filePath of to extract 923 * @return the {@link File} or null if not found 924 * @throws IOException if failed to extract file 925 * @deprecated Moved to {@link ZipUtil#extractFileFromZip(ZipFile, String)}. 926 */ 927 @Deprecated 928 public static File extractFileFromZip(ZipFile zipFile, String filePath) throws IOException { 929 return ZipUtil.extractFileFromZip(zipFile, filePath); 930 } 931 932 /** 933 * Utility method to create a temporary zip file containing the given directory and 934 * all its contents. 935 * 936 * @param dir the directory to zip 937 * @return a temporary zip {@link File} containing directory contents 938 * @throws IOException if failed to create zip file 939 * @deprecated Moved to {@link ZipUtil#createZip(File)}. 940 */ 941 @Deprecated 942 public static File createZip(File dir) throws IOException { 943 return ZipUtil.createZip(dir); 944 } 945 946 /** 947 * Utility method to create a zip file containing the given directory and 948 * all its contents. 949 * 950 * @param dir the directory to zip 951 * @param zipFile the zip file to create - it should not already exist 952 * @throws IOException if failed to create zip file 953 * @deprecated Moved to {@link ZipUtil#createZip(File, File)}. 954 */ 955 @Deprecated 956 public static void createZip(File dir, File zipFile) throws IOException { 957 ZipUtil.createZip(dir, zipFile); 958 } 959 960 /** 961 * Close an open {@link ZipFile}, ignoring any exceptions. 962 * 963 * @param zipFile the file to close 964 * @deprecated Moved to {@link ZipUtil#closeZip(ZipFile)}. 965 */ 966 @Deprecated 967 public static void closeZip(ZipFile zipFile) { 968 ZipUtil.closeZip(zipFile); 969 } 970 971 /** 972 * Helper method to create a gzipped version of a single file. 973 * 974 * @param file the original file 975 * @param gzipFile the file to place compressed contents in 976 * @throws IOException 977 * @deprecated Moved to {@link ZipUtil#gzipFile(File, File)}. 978 */ 979 @Deprecated 980 public static void gzipFile(File file, File gzipFile) throws IOException { 981 ZipUtil.gzipFile(file, gzipFile); 982 } 983 984 /** 985 * Helper method to calculate md5 for a file. 986 * 987 * @param file 988 * @return md5 of the file 989 * @throws IOException 990 */ 991 public static String calculateMd5(File file) throws IOException { 992 FileInputStream inputSource = new FileInputStream(file); 993 return StreamUtil.calculateMd5(inputSource); 994 } 995 996 /** 997 * Converts an integer representing unix mode to a set of {@link PosixFilePermission}s 998 */ 999 public static Set<PosixFilePermission> unixModeToPosix(int mode) { 1000 Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class); 1001 for (PosixFilePermission pfp : EnumSet.allOf(PosixFilePermission.class)) { 1002 int m = PERM_MODE_MAP.get(pfp); 1003 if ((m & mode) == m) { 1004 result.add(pfp); 1005 } 1006 } 1007 return result; 1008 } 1009 1010 /** 1011 * Get all file paths of files in the given directory with name matching the given filter 1012 * 1013 * @param dir {@link File} object of the directory to search for files recursively 1014 * @param filter {@link String} of the regex to match file names 1015 * @return a set of {@link String} of the file paths 1016 */ 1017 public static Set<String> findFiles(File dir, String filter) throws IOException { 1018 Set<String> files = new HashSet<String>(); 1019 Files.walk(Paths.get(dir.getAbsolutePath())) 1020 .filter(path -> new File(path.toString()).getName().matches(filter)) 1021 .forEach(path -> files.add(path.toString())); 1022 return files; 1023 } 1024 1025 /** 1026 * Get file's content type based it's extension. 1027 * @param filePath the file path 1028 * @return content type 1029 */ 1030 public static String getContentType(String filePath) { 1031 int index = filePath.lastIndexOf('.'); 1032 String ext = ""; 1033 if (index >= 0) { 1034 ext = filePath.substring(index + 1); 1035 } 1036 LogDataType[] dataTypes = LogDataType.values(); 1037 for (LogDataType dataType: dataTypes) { 1038 if (ext.equals(dataType.getFileExt())) { 1039 return dataType.getContentType(); 1040 } 1041 } 1042 return LogDataType.UNKNOWN.getContentType(); 1043 } 1044 } 1045