001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io; 018 019import java.io.BufferedReader; 020import java.io.File; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.io.OutputStream; 025import java.nio.charset.Charset; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.List; 029import java.util.Locale; 030import java.util.StringTokenizer; 031 032/** 033 * General File System utilities. 034 * <p> 035 * This class provides static utility methods for general file system 036 * functions not provided via the JDK {@link java.io.File File} class. 037 * <p> 038 * The current functions provided are: 039 * <ul> 040 * <li>Get the free space on a drive 041 * </ul> 042 * 043 * @since 1.1 044 * @deprecated As of 2.6 deprecated without replacement. Use equivalent 045 * methods in {@link java.nio.file.FileStore} instead, e.g. 046 * <code>Files.getFileStore(Paths.get("/home")).getUsableSpace()</code> 047 * or iterate over <code>FileSystems.getDefault().getFileStores()</code> 048 */ 049@Deprecated 050public class FileSystemUtils { 051 052 /** Singleton instance, used mainly for testing. */ 053 private static final FileSystemUtils INSTANCE = new FileSystemUtils(); 054 055 /** Operating system state flag for error. */ 056 private static final int INIT_PROBLEM = -1; 057 /** Operating system state flag for neither Unix nor Windows. */ 058 private static final int OTHER = 0; 059 /** Operating system state flag for Windows. */ 060 private static final int WINDOWS = 1; 061 /** Operating system state flag for Unix. */ 062 private static final int UNIX = 2; 063 /** Operating system state flag for Posix flavour Unix. */ 064 private static final int POSIX_UNIX = 3; 065 066 /** The operating system flag. */ 067 private static final int OS; 068 069 /** The path to df */ 070 private static final String DF; 071 072 static { 073 int os = OTHER; 074 String dfPath = "df"; 075 try { 076 String osName = System.getProperty("os.name"); 077 if (osName == null) { 078 throw new IOException("os.name not found"); 079 } 080 osName = osName.toLowerCase(Locale.ENGLISH); 081 // match 082 if (osName.contains("windows")) { 083 os = WINDOWS; 084 } else if (osName.contains("linux") || 085 osName.contains("mpe/ix") || 086 osName.contains("freebsd") || 087 osName.contains("openbsd") || 088 osName.contains("irix") || 089 osName.contains("digital unix") || 090 osName.contains("unix") || 091 osName.contains("mac os x")) { 092 os = UNIX; 093 } else if (osName.contains("sun os") || 094 osName.contains("sunos") || 095 osName.contains("solaris")) { 096 os = POSIX_UNIX; 097 dfPath = "/usr/xpg4/bin/df"; 098 } else if (osName.contains("hp-ux") || 099 osName.contains("aix")) { 100 os = POSIX_UNIX; 101 } else { 102 os = OTHER; 103 } 104 105 } catch (final Exception ex) { 106 os = INIT_PROBLEM; 107 } 108 OS = os; 109 DF = dfPath; 110 } 111 112 /** 113 * Instances should NOT be constructed in standard programming. 114 */ 115 public FileSystemUtils() { 116 super(); 117 } 118 119 //----------------------------------------------------------------------- 120 /** 121 * Returns the free space on a drive or volume by invoking 122 * the command line. 123 * This method does not normalize the result, and typically returns 124 * bytes on Windows, 512 byte units on OS X and kilobytes on Unix. 125 * As this is not very useful, this method is deprecated in favour 126 * of {@link #freeSpaceKb(String)} which returns a result in kilobytes. 127 * <p> 128 * Note that some OS's are NOT currently supported, including OS/390, 129 * OpenVMS. 130 * <pre> 131 * FileSystemUtils.freeSpace("C:"); // Windows 132 * FileSystemUtils.freeSpace("/volume"); // *nix 133 * </pre> 134 * The free space is calculated via the command line. 135 * It uses 'dir /-c' on Windows and 'df' on *nix. 136 * 137 * @param path the path to get free space for, not null, not empty on Unix 138 * @return the amount of free drive space on the drive or volume 139 * @throws IllegalArgumentException if the path is invalid 140 * @throws IllegalStateException if an error occurred in initialisation 141 * @throws IOException if an error occurs when finding the free space 142 * @since 1.1, enhanced OS support in 1.2 and 1.3 143 * @deprecated Use freeSpaceKb(String) 144 * Deprecated from 1.3, may be removed in 2.0 145 */ 146 @Deprecated 147 public static long freeSpace(final String path) throws IOException { 148 return INSTANCE.freeSpaceOS(path, OS, false, -1); 149 } 150 151 //----------------------------------------------------------------------- 152 /** 153 * Returns the free space on a drive or volume in kibibytes (1024 bytes) 154 * by invoking the command line. 155 * <pre> 156 * FileSystemUtils.freeSpaceKb("C:"); // Windows 157 * FileSystemUtils.freeSpaceKb("/volume"); // *nix 158 * </pre> 159 * The free space is calculated via the command line. 160 * It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix. 161 * <p> 162 * In order to work, you must be running Windows, or have a implementation of 163 * Unix df that supports GNU format when passed -k (or -kP). If you are going 164 * to rely on this code, please check that it works on your OS by running 165 * some simple tests to compare the command line with the output from this class. 166 * If your operating system isn't supported, please raise a JIRA call detailing 167 * the exact result from df -k and as much other detail as possible, thanks. 168 * 169 * @param path the path to get free space for, not null, not empty on Unix 170 * @return the amount of free drive space on the drive or volume in kilobytes 171 * @throws IllegalArgumentException if the path is invalid 172 * @throws IllegalStateException if an error occurred in initialisation 173 * @throws IOException if an error occurs when finding the free space 174 * @since 1.2, enhanced OS support in 1.3 175 * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}. 176 */ 177 @Deprecated 178 public static long freeSpaceKb(final String path) throws IOException { 179 return freeSpaceKb(path, -1); 180 } 181 /** 182 * Returns the free space on a drive or volume in kibibytes (1024 bytes) 183 * by invoking the command line. 184 * <pre> 185 * FileSystemUtils.freeSpaceKb("C:"); // Windows 186 * FileSystemUtils.freeSpaceKb("/volume"); // *nix 187 * </pre> 188 * The free space is calculated via the command line. 189 * It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix. 190 * <p> 191 * In order to work, you must be running Windows, or have a implementation of 192 * Unix df that supports GNU format when passed -k (or -kP). If you are going 193 * to rely on this code, please check that it works on your OS by running 194 * some simple tests to compare the command line with the output from this class. 195 * If your operating system isn't supported, please raise a JIRA call detailing 196 * the exact result from df -k and as much other detail as possible, thanks. 197 * 198 * @param path the path to get free space for, not null, not empty on Unix 199 * @param timeout The timeout amount in milliseconds or no timeout if the value 200 * is zero or less 201 * @return the amount of free drive space on the drive or volume in kilobytes 202 * @throws IllegalArgumentException if the path is invalid 203 * @throws IllegalStateException if an error occurred in initialisation 204 * @throws IOException if an error occurs when finding the free space 205 * @since 2.0 206 * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}. 207 */ 208 @Deprecated 209 public static long freeSpaceKb(final String path, final long timeout) throws IOException { 210 return INSTANCE.freeSpaceOS(path, OS, true, timeout); 211 } 212 213 /** 214 * Returns the free space for the working directory 215 * in kibibytes (1024 bytes) by invoking the command line. 216 * <p> 217 * Identical to: 218 * <pre> 219 * freeSpaceKb(new File(".").getAbsolutePath()) 220 * </pre> 221 * @return the amount of free drive space on the drive or volume in kilobytes 222 * @throws IllegalStateException if an error occurred in initialisation 223 * @throws IOException if an error occurs when finding the free space 224 * @since 2.0 225 * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}. 226 */ 227 @Deprecated 228 public static long freeSpaceKb() throws IOException { 229 return freeSpaceKb(-1); 230 } 231 232 /** 233 * Returns the free space for the working directory 234 * in kibibytes (1024 bytes) by invoking the command line. 235 * <p> 236 * Identical to: 237 * <pre> 238 * freeSpaceKb(new File(".").getAbsolutePath()) 239 * </pre> 240 * @param timeout The timeout amount in milliseconds or no timeout if the value 241 * is zero or less 242 * @return the amount of free drive space on the drive or volume in kilobytes 243 * @throws IllegalStateException if an error occurred in initialisation 244 * @throws IOException if an error occurs when finding the free space 245 * @since 2.0 246 * @deprecated As of 2.6 deprecated without replacement. Please use {@link java.nio.file.FileStore#getUsableSpace()}. 247 */ 248 @Deprecated 249 public static long freeSpaceKb(final long timeout) throws IOException { 250 return freeSpaceKb(new File(".").getAbsolutePath(), timeout); 251 } 252 253 //----------------------------------------------------------------------- 254 /** 255 * Returns the free space on a drive or volume in a cross-platform manner. 256 * Note that some OS's are NOT currently supported, including OS/390. 257 * <pre> 258 * FileSystemUtils.freeSpace("C:"); // Windows 259 * FileSystemUtils.freeSpace("/volume"); // *nix 260 * </pre> 261 * The free space is calculated via the command line. 262 * It uses 'dir /-c' on Windows and 'df' on *nix. 263 * 264 * @param path the path to get free space for, not null, not empty on Unix 265 * @param os the operating system code 266 * @param kb whether to normalize to kilobytes 267 * @param timeout The timeout amount in milliseconds or no timeout if the value 268 * is zero or less 269 * @return the amount of free drive space on the drive or volume 270 * @throws IllegalArgumentException if the path is invalid 271 * @throws IllegalStateException if an error occurred in initialisation 272 * @throws IOException if an error occurs when finding the free space 273 */ 274 long freeSpaceOS(final String path, final int os, final boolean kb, final long timeout) throws IOException { 275 if (path == null) { 276 throw new IllegalArgumentException("Path must not be null"); 277 } 278 switch (os) { 279 case WINDOWS: 280 return kb ? freeSpaceWindows(path, timeout) / FileUtils.ONE_KB : freeSpaceWindows(path, timeout); 281 case UNIX: 282 return freeSpaceUnix(path, kb, false, timeout); 283 case POSIX_UNIX: 284 return freeSpaceUnix(path, kb, true, timeout); 285 case OTHER: 286 throw new IllegalStateException("Unsupported operating system"); 287 default: 288 throw new IllegalStateException( 289 "Exception caught when determining operating system"); 290 } 291 } 292 293 //----------------------------------------------------------------------- 294 /** 295 * Find free space on the Windows platform using the 'dir' command. 296 * 297 * @param path the path to get free space for, including the colon 298 * @param timeout The timeout amount in milliseconds or no timeout if the value 299 * is zero or less 300 * @return the amount of free drive space on the drive 301 * @throws IOException if an error occurs 302 */ 303 long freeSpaceWindows(final String path, final long timeout) throws IOException { 304 String normPath = FilenameUtils.normalize(path, false); 305 if (normPath == null) { 306 throw new IllegalArgumentException(path); 307 } 308 if (normPath.length() > 0 && normPath.charAt(0) != '"') { 309 normPath = "\"" + normPath + "\""; 310 } 311 312 // build and run the 'dir' command 313 final String[] cmdAttribs = new String[] {"cmd.exe", "/C", "dir /a /-c " + normPath}; 314 315 // read in the output of the command to an ArrayList 316 final List<String> lines = performCommand(cmdAttribs, Integer.MAX_VALUE, timeout); 317 318 // now iterate over the lines we just read and find the LAST 319 // non-empty line (the free space bytes should be in the last element 320 // of the ArrayList anyway, but this will ensure it works even if it's 321 // not, still assuming it is on the last non-blank line) 322 for (int i = lines.size() - 1; i >= 0; i--) { 323 final String line = lines.get(i); 324 if (line.length() > 0) { 325 return parseDir(line, normPath); 326 } 327 } 328 // all lines are blank 329 throw new IOException( 330 "Command line 'dir /-c' did not return any info " + 331 "for path '" + normPath + "'"); 332 } 333 334 /** 335 * Parses the Windows dir response last line 336 * 337 * @param line the line to parse 338 * @param path the path that was sent 339 * @return the number of bytes 340 * @throws IOException if an error occurs 341 */ 342 long parseDir(final String line, final String path) throws IOException { 343 // read from the end of the line to find the last numeric 344 // character on the line, then continue until we find the first 345 // non-numeric character, and everything between that and the last 346 // numeric character inclusive is our free space bytes count 347 int bytesStart = 0; 348 int bytesEnd = 0; 349 int j = line.length() - 1; 350 innerLoop1: while (j >= 0) { 351 final char c = line.charAt(j); 352 if (Character.isDigit(c)) { 353 // found the last numeric character, this is the end of 354 // the free space bytes count 355 bytesEnd = j + 1; 356 break innerLoop1; 357 } 358 j--; 359 } 360 innerLoop2: while (j >= 0) { 361 final char c = line.charAt(j); 362 if (!Character.isDigit(c) && c != ',' && c != '.') { 363 // found the next non-numeric character, this is the 364 // beginning of the free space bytes count 365 bytesStart = j + 1; 366 break innerLoop2; 367 } 368 j--; 369 } 370 if (j < 0) { 371 throw new IOException( 372 "Command line 'dir /-c' did not return valid info " + 373 "for path '" + path + "'"); 374 } 375 376 // remove commas and dots in the bytes count 377 final StringBuilder buf = new StringBuilder(line.substring(bytesStart, bytesEnd)); 378 for (int k = 0; k < buf.length(); k++) { 379 if (buf.charAt(k) == ',' || buf.charAt(k) == '.') { 380 buf.deleteCharAt(k--); 381 } 382 } 383 return parseBytes(buf.toString(), path); 384 } 385 386 //----------------------------------------------------------------------- 387 /** 388 * Find free space on the *nix platform using the 'df' command. 389 * 390 * @param path the path to get free space for 391 * @param kb whether to normalize to kilobytes 392 * @param posix whether to use the POSIX standard format flag 393 * @param timeout The timeout amount in milliseconds or no timeout if the value 394 * is zero or less 395 * @return the amount of free drive space on the volume 396 * @throws IOException if an error occurs 397 */ 398 long freeSpaceUnix(final String path, final boolean kb, final boolean posix, final long timeout) 399 throws IOException { 400 if (path.isEmpty()) { 401 throw new IllegalArgumentException("Path must not be empty"); 402 } 403 404 // build and run the 'dir' command 405 String flags = "-"; 406 if (kb) { 407 flags += "k"; 408 } 409 if (posix) { 410 flags += "P"; 411 } 412 final String[] cmdAttribs = 413 flags.length() > 1 ? new String[] {DF, flags, path} : new String[] {DF, path}; 414 415 // perform the command, asking for up to 3 lines (header, interesting, overflow) 416 final List<String> lines = performCommand(cmdAttribs, 3, timeout); 417 if (lines.size() < 2) { 418 // unknown problem, throw exception 419 throw new IOException( 420 "Command line '" + DF + "' did not return info as expected " + 421 "for path '" + path + "'- response was " + lines); 422 } 423 final String line2 = lines.get(1); // the line we're interested in 424 425 // Now, we tokenize the string. The fourth element is what we want. 426 StringTokenizer tok = new StringTokenizer(line2, " "); 427 if (tok.countTokens() < 4) { 428 // could be long Filesystem, thus data on third line 429 if (tok.countTokens() == 1 && lines.size() >= 3) { 430 final String line3 = lines.get(2); // the line may be interested in 431 tok = new StringTokenizer(line3, " "); 432 } else { 433 throw new IOException( 434 "Command line '" + DF + "' did not return data as expected " + 435 "for path '" + path + "'- check path is valid"); 436 } 437 } else { 438 tok.nextToken(); // Ignore Filesystem 439 } 440 tok.nextToken(); // Ignore 1K-blocks 441 tok.nextToken(); // Ignore Used 442 final String freeSpace = tok.nextToken(); 443 return parseBytes(freeSpace, path); 444 } 445 446 //----------------------------------------------------------------------- 447 /** 448 * Parses the bytes from a string. 449 * 450 * @param freeSpace the free space string 451 * @param path the path 452 * @return the number of bytes 453 * @throws IOException if an error occurs 454 */ 455 long parseBytes(final String freeSpace, final String path) throws IOException { 456 try { 457 final long bytes = Long.parseLong(freeSpace); 458 if (bytes < 0) { 459 throw new IOException( 460 "Command line '" + DF + "' did not find free space in response " + 461 "for path '" + path + "'- check path is valid"); 462 } 463 return bytes; 464 465 } catch (final NumberFormatException ex) { 466 throw new IOException( 467 "Command line '" + DF + "' did not return numeric data as expected " + 468 "for path '" + path + "'- check path is valid", ex); 469 } 470 } 471 472 //----------------------------------------------------------------------- 473 /** 474 * Performs the os command. 475 * 476 * @param cmdAttribs the command line parameters 477 * @param max The maximum limit for the lines returned 478 * @param timeout The timeout amount in milliseconds or no timeout if the value 479 * is zero or less 480 * @return the lines returned by the command, converted to lower-case 481 * @throws IOException if an error occurs 482 */ 483 List<String> performCommand(final String[] cmdAttribs, final int max, final long timeout) throws IOException { 484 // this method does what it can to avoid the 'Too many open files' error 485 // based on trial and error and these links: 486 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692 487 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4801027 488 // http://forum.java.sun.com/thread.jspa?threadID=533029&messageID=2572018 489 // however, its still not perfect as the JDK support is so poor 490 // (see commons-exec or Ant for a better multi-threaded multi-os solution) 491 492 final List<String> lines = new ArrayList<>(20); 493 Process proc = null; 494 InputStream in = null; 495 OutputStream out = null; 496 InputStream err = null; 497 BufferedReader inr = null; 498 try { 499 500 final Thread monitor = ThreadMonitor.start(timeout); 501 502 proc = openProcess(cmdAttribs); 503 in = proc.getInputStream(); 504 out = proc.getOutputStream(); 505 err = proc.getErrorStream(); 506 // default charset is most likely appropriate here 507 inr = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset())); 508 String line = inr.readLine(); 509 while (line != null && lines.size() < max) { 510 line = line.toLowerCase(Locale.ENGLISH).trim(); 511 lines.add(line); 512 line = inr.readLine(); 513 } 514 515 proc.waitFor(); 516 517 ThreadMonitor.stop(monitor); 518 519 if (proc.exitValue() != 0) { 520 // os command problem, throw exception 521 throw new IOException( 522 "Command line returned OS error code '" + proc.exitValue() + 523 "' for command " + Arrays.asList(cmdAttribs)); 524 } 525 if (lines.isEmpty()) { 526 // unknown problem, throw exception 527 throw new IOException( 528 "Command line did not return any info " + 529 "for command " + Arrays.asList(cmdAttribs)); 530 } 531 532 inr.close(); 533 inr = null; 534 535 in.close(); 536 in = null; 537 538 if (out != null) { 539 out.close(); 540 out = null; 541 } 542 543 if (err != null) { 544 err.close(); 545 err = null; 546 } 547 548 return lines; 549 550 } catch (final InterruptedException ex) { 551 throw new IOException( 552 "Command line threw an InterruptedException " + 553 "for command " + Arrays.asList(cmdAttribs) + " timeout=" + timeout, ex); 554 } finally { 555 IOUtils.closeQuietly(in); 556 IOUtils.closeQuietly(out); 557 IOUtils.closeQuietly(err); 558 IOUtils.closeQuietly(inr); 559 if (proc != null) { 560 proc.destroy(); 561 } 562 } 563 } 564 565 /** 566 * Opens the process to the operating system. 567 * 568 * @param cmdAttribs the command line parameters 569 * @return the process 570 * @throws IOException if an error occurs 571 */ 572 Process openProcess(final String[] cmdAttribs) throws IOException { 573 return Runtime.getRuntime().exec(cmdAttribs); 574 } 575 576}