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 */ 017 package org.apache.commons.io; 018 019 import java.io.BufferedReader; 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.io.InputStreamReader; 023 import java.io.OutputStream; 024 import java.util.ArrayList; 025 import java.util.Arrays; 026 import java.util.List; 027 import java.util.StringTokenizer; 028 029 /** 030 * General File System utilities. 031 * <p> 032 * This class provides static utility methods for general file system 033 * functions not provided via the JDK {@link java.io.File File} class. 034 * <p> 035 * The current functions provided are: 036 * <ul> 037 * <li>Get the free space on a drive 038 * </ul> 039 * 040 * @author Frank W. Zammetti 041 * @author Stephen Colebourne 042 * @author Thomas Ledoux 043 * @author James Urie 044 * @author Magnus Grimsell 045 * @author Thomas Ledoux 046 * @version $Id: FileSystemUtils.java 453889 2006-10-07 11:56:25Z scolebourne $ 047 * @since Commons IO 1.1 048 */ 049 public class FileSystemUtils { 050 051 /** Singleton instance, used mainly for testing. */ 052 private static final FileSystemUtils INSTANCE = new FileSystemUtils(); 053 054 /** Operating system state flag for error. */ 055 private static final int INIT_PROBLEM = -1; 056 /** Operating system state flag for neither Unix nor Windows. */ 057 private static final int OTHER = 0; 058 /** Operating system state flag for Windows. */ 059 private static final int WINDOWS = 1; 060 /** Operating system state flag for Unix. */ 061 private static final int UNIX = 2; 062 /** Operating system state flag for Posix flavour Unix. */ 063 private static final int POSIX_UNIX = 3; 064 065 /** The operating system flag. */ 066 private static final int OS; 067 static { 068 int os = OTHER; 069 try { 070 String osName = System.getProperty("os.name"); 071 if (osName == null) { 072 throw new IOException("os.name not found"); 073 } 074 osName = osName.toLowerCase(); 075 // match 076 if (osName.indexOf("windows") != -1) { 077 os = WINDOWS; 078 } else if (osName.indexOf("linux") != -1 || 079 osName.indexOf("sun os") != -1 || 080 osName.indexOf("sunos") != -1 || 081 osName.indexOf("solaris") != -1 || 082 osName.indexOf("mpe/ix") != -1 || 083 osName.indexOf("freebsd") != -1 || 084 osName.indexOf("irix") != -1 || 085 osName.indexOf("digital unix") != -1 || 086 osName.indexOf("unix") != -1 || 087 osName.indexOf("mac os x") != -1) { 088 os = UNIX; 089 } else if (osName.indexOf("hp-ux") != -1 || 090 osName.indexOf("aix") != -1) { 091 os = POSIX_UNIX; 092 } else { 093 os = OTHER; 094 } 095 096 } catch (Exception ex) { 097 os = INIT_PROBLEM; 098 } 099 OS = os; 100 } 101 102 /** 103 * Instances should NOT be constructed in standard programming. 104 */ 105 public FileSystemUtils() { 106 super(); 107 } 108 109 //----------------------------------------------------------------------- 110 /** 111 * Returns the free space on a drive or volume by invoking 112 * the command line. 113 * This method does not normalize the result, and typically returns 114 * bytes on Windows, 512 byte units on OS X and kilobytes on Unix. 115 * As this is not very useful, this method is deprecated in favour 116 * of {@link #freeSpaceKb(String)} which returns a result in kilobytes. 117 * <p> 118 * Note that some OS's are NOT currently supported, including OS/390, 119 * OpenVMS and and SunOS 5. (SunOS is supported by <code>freeSpaceKb</code>.) 120 * <pre> 121 * FileSystemUtils.freeSpace("C:"); // Windows 122 * FileSystemUtils.freeSpace("/volume"); // *nix 123 * </pre> 124 * The free space is calculated via the command line. 125 * It uses 'dir /-c' on Windows and 'df' on *nix. 126 * 127 * @param path the path to get free space for, not null, not empty on Unix 128 * @return the amount of free drive space on the drive or volume 129 * @throws IllegalArgumentException if the path is invalid 130 * @throws IllegalStateException if an error occurred in initialisation 131 * @throws IOException if an error occurs when finding the free space 132 * @since Commons IO 1.1, enhanced OS support in 1.2 and 1.3 133 * @deprecated Use freeSpaceKb(String) 134 * Deprecated from 1.3, may be removed in 2.0 135 */ 136 public static long freeSpace(String path) throws IOException { 137 return INSTANCE.freeSpaceOS(path, OS, false); 138 } 139 140 //----------------------------------------------------------------------- 141 /** 142 * Returns the free space on a drive or volume in kilobytes by invoking 143 * the command line. 144 * <pre> 145 * FileSystemUtils.freeSpaceKb("C:"); // Windows 146 * FileSystemUtils.freeSpaceKb("/volume"); // *nix 147 * </pre> 148 * The free space is calculated via the command line. 149 * It uses 'dir /-c' on Windows, 'df -kP' on AIX/HP-UX and 'df -k' on other Unix. 150 * <p> 151 * In order to work, you must be running Windows, or have a implementation of 152 * Unix df that supports GNU format when passed -k (or -kP). If you are going 153 * to rely on this code, please check that it works on your OS by running 154 * some simple tests to compare the command line with the output from this class. 155 * If your operating system isn't supported, please raise a JIRA call detailing 156 * the exact result from df -k and as much other detail as possible, thanks. 157 * 158 * @param path the path to get free space for, not null, not empty on Unix 159 * @return the amount of free drive space on the drive or volume in kilobytes 160 * @throws IllegalArgumentException if the path is invalid 161 * @throws IllegalStateException if an error occurred in initialisation 162 * @throws IOException if an error occurs when finding the free space 163 * @since Commons IO 1.2, enhanced OS support in 1.3 164 */ 165 public static long freeSpaceKb(String path) throws IOException { 166 return INSTANCE.freeSpaceOS(path, OS, true); 167 } 168 169 //----------------------------------------------------------------------- 170 /** 171 * Returns the free space on a drive or volume in a cross-platform manner. 172 * Note that some OS's are NOT currently supported, including OS/390. 173 * <pre> 174 * FileSystemUtils.freeSpace("C:"); // Windows 175 * FileSystemUtils.freeSpace("/volume"); // *nix 176 * </pre> 177 * The free space is calculated via the command line. 178 * It uses 'dir /-c' on Windows and 'df' on *nix. 179 * 180 * @param path the path to get free space for, not null, not empty on Unix 181 * @param os the operating system code 182 * @param kb whether to normalize to kilobytes 183 * @return the amount of free drive space on the drive or volume 184 * @throws IllegalArgumentException if the path is invalid 185 * @throws IllegalStateException if an error occurred in initialisation 186 * @throws IOException if an error occurs when finding the free space 187 */ 188 long freeSpaceOS(String path, int os, boolean kb) throws IOException { 189 if (path == null) { 190 throw new IllegalArgumentException("Path must not be empty"); 191 } 192 switch (os) { 193 case WINDOWS: 194 return (kb ? freeSpaceWindows(path) / 1024 : freeSpaceWindows(path)); 195 case UNIX: 196 return freeSpaceUnix(path, kb, false); 197 case POSIX_UNIX: 198 return freeSpaceUnix(path, kb, true); 199 case OTHER: 200 throw new IllegalStateException("Unsupported operating system"); 201 default: 202 throw new IllegalStateException( 203 "Exception caught when determining operating system"); 204 } 205 } 206 207 //----------------------------------------------------------------------- 208 /** 209 * Find free space on the Windows platform using the 'dir' command. 210 * 211 * @param path the path to get free space for, including the colon 212 * @return the amount of free drive space on the drive 213 * @throws IOException if an error occurs 214 */ 215 long freeSpaceWindows(String path) throws IOException { 216 path = FilenameUtils.normalize(path); 217 if (path.length() > 2 && path.charAt(1) == ':') { 218 path = path.substring(0, 2); // seems to make it work 219 } 220 221 // build and run the 'dir' command 222 String[] cmdAttribs = new String[] {"cmd.exe", "/C", "dir /-c " + path}; 223 224 // read in the output of the command to an ArrayList 225 List lines = performCommand(cmdAttribs, Integer.MAX_VALUE); 226 227 // now iterate over the lines we just read and find the LAST 228 // non-empty line (the free space bytes should be in the last element 229 // of the ArrayList anyway, but this will ensure it works even if it's 230 // not, still assuming it is on the last non-blank line) 231 for (int i = lines.size() - 1; i >= 0; i--) { 232 String line = (String) lines.get(i); 233 if (line.length() > 0) { 234 return parseDir(line, path); 235 } 236 } 237 // all lines are blank 238 throw new IOException( 239 "Command line 'dir /-c' did not return any info " + 240 "for path '" + path + "'"); 241 } 242 243 /** 244 * Parses the Windows dir response last line 245 * 246 * @param line the line to parse 247 * @param path the path that was sent 248 * @return the number of bytes 249 * @throws IOException if an error occurs 250 */ 251 long parseDir(String line, String path) throws IOException { 252 // read from the end of the line to find the last numeric 253 // character on the line, then continue until we find the first 254 // non-numeric character, and everything between that and the last 255 // numeric character inclusive is our free space bytes count 256 int bytesStart = 0; 257 int bytesEnd = 0; 258 int j = line.length() - 1; 259 innerLoop1: while (j >= 0) { 260 char c = line.charAt(j); 261 if (Character.isDigit(c)) { 262 // found the last numeric character, this is the end of 263 // the free space bytes count 264 bytesEnd = j + 1; 265 break innerLoop1; 266 } 267 j--; 268 } 269 innerLoop2: while (j >= 0) { 270 char c = line.charAt(j); 271 if (!Character.isDigit(c) && c != ',' && c != '.') { 272 // found the next non-numeric character, this is the 273 // beginning of the free space bytes count 274 bytesStart = j + 1; 275 break innerLoop2; 276 } 277 j--; 278 } 279 if (j < 0) { 280 throw new IOException( 281 "Command line 'dir /-c' did not return valid info " + 282 "for path '" + path + "'"); 283 } 284 285 // remove commas and dots in the bytes count 286 StringBuffer buf = new StringBuffer(line.substring(bytesStart, bytesEnd)); 287 for (int k = 0; k < buf.length(); k++) { 288 if (buf.charAt(k) == ',' || buf.charAt(k) == '.') { 289 buf.deleteCharAt(k--); 290 } 291 } 292 return parseBytes(buf.toString(), path); 293 } 294 295 //----------------------------------------------------------------------- 296 /** 297 * Find free space on the *nix platform using the 'df' command. 298 * 299 * @param path the path to get free space for 300 * @param kb whether to normalize to kilobytes 301 * @param posix whether to use the posix standard format flag 302 * @return the amount of free drive space on the volume 303 * @throws IOException if an error occurs 304 */ 305 long freeSpaceUnix(String path, boolean kb, boolean posix) throws IOException { 306 if (path.length() == 0) { 307 throw new IllegalArgumentException("Path must not be empty"); 308 } 309 path = FilenameUtils.normalize(path); 310 311 // build and run the 'dir' command 312 String flags = "-"; 313 if (kb) { 314 flags += "k"; 315 } 316 if (posix) { 317 flags += "P"; 318 } 319 String[] cmdAttribs = 320 (flags.length() > 1 ? new String[] {"df", flags, path} : new String[] {"df", path}); 321 322 // perform the command, asking for up to 3 lines (header, interesting, overflow) 323 List lines = performCommand(cmdAttribs, 3); 324 if (lines.size() < 2) { 325 // unknown problem, throw exception 326 throw new IOException( 327 "Command line 'df' did not return info as expected " + 328 "for path '" + path + "'- response was " + lines); 329 } 330 String line2 = (String) lines.get(1); // the line we're interested in 331 332 // Now, we tokenize the string. The fourth element is what we want. 333 StringTokenizer tok = new StringTokenizer(line2, " "); 334 if (tok.countTokens() < 4) { 335 // could be long Filesystem, thus data on third line 336 if (tok.countTokens() == 1 && lines.size() >= 3) { 337 String line3 = (String) lines.get(2); // the line may be interested in 338 tok = new StringTokenizer(line3, " "); 339 } else { 340 throw new IOException( 341 "Command line 'df' did not return data as expected " + 342 "for path '" + path + "'- check path is valid"); 343 } 344 } else { 345 tok.nextToken(); // Ignore Filesystem 346 } 347 tok.nextToken(); // Ignore 1K-blocks 348 tok.nextToken(); // Ignore Used 349 String freeSpace = tok.nextToken(); 350 return parseBytes(freeSpace, path); 351 } 352 353 //----------------------------------------------------------------------- 354 /** 355 * Parses the bytes from a string. 356 * 357 * @param freeSpace the free space string 358 * @param path the path 359 * @return the number of bytes 360 * @throws IOException if an error occurs 361 */ 362 long parseBytes(String freeSpace, String path) throws IOException { 363 try { 364 long bytes = Long.parseLong(freeSpace); 365 if (bytes < 0) { 366 throw new IOException( 367 "Command line 'df' did not find free space in response " + 368 "for path '" + path + "'- check path is valid"); 369 } 370 return bytes; 371 372 } catch (NumberFormatException ex) { 373 throw new IOException( 374 "Command line 'df' did not return numeric data as expected " + 375 "for path '" + path + "'- check path is valid"); 376 } 377 } 378 379 //----------------------------------------------------------------------- 380 /** 381 * Performs the os command. 382 * 383 * @param cmdAttribs the command line parameters 384 * @param max The maximum limit for the lines returned 385 * @return the parsed data 386 * @throws IOException if an error occurs 387 */ 388 List performCommand(String[] cmdAttribs, int max) throws IOException { 389 // this method does what it can to avoid the 'Too many open files' error 390 // based on trial and error and these links: 391 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4784692 392 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4801027 393 // http://forum.java.sun.com/thread.jspa?threadID=533029&messageID=2572018 394 // however, its still not perfect as the JDK support is so poor 395 // (see commond-exec or ant for a better multi-threaded multi-os solution) 396 397 List lines = new ArrayList(20); 398 Process proc = null; 399 InputStream in = null; 400 OutputStream out = null; 401 InputStream err = null; 402 BufferedReader inr = null; 403 try { 404 proc = openProcess(cmdAttribs); 405 in = proc.getInputStream(); 406 out = proc.getOutputStream(); 407 err = proc.getErrorStream(); 408 inr = new BufferedReader(new InputStreamReader(in)); 409 String line = inr.readLine(); 410 while (line != null && lines.size() < max) { 411 line = line.toLowerCase().trim(); 412 lines.add(line); 413 line = inr.readLine(); 414 } 415 416 proc.waitFor(); 417 if (proc.exitValue() != 0) { 418 // os command problem, throw exception 419 throw new IOException( 420 "Command line returned OS error code '" + proc.exitValue() + 421 "' for command " + Arrays.asList(cmdAttribs)); 422 } 423 if (lines.size() == 0) { 424 // unknown problem, throw exception 425 throw new IOException( 426 "Command line did not return any info " + 427 "for command " + Arrays.asList(cmdAttribs)); 428 } 429 return lines; 430 431 } catch (InterruptedException ex) { 432 throw new IOException( 433 "Command line threw an InterruptedException '" + ex.getMessage() + 434 "' for command " + Arrays.asList(cmdAttribs)); 435 } finally { 436 IOUtils.closeQuietly(in); 437 IOUtils.closeQuietly(out); 438 IOUtils.closeQuietly(err); 439 IOUtils.closeQuietly(inr); 440 if (proc != null) { 441 proc.destroy(); 442 } 443 } 444 } 445 446 /** 447 * Opens the process to the operating system. 448 * 449 * @param cmdAttribs the command line parameters 450 * @return the process 451 * @throws IOException if an error occurs 452 */ 453 Process openProcess(String[] cmdAttribs) throws IOException { 454 return Runtime.getRuntime().exec(cmdAttribs); 455 } 456 457 }