Skip to content

Commit 0a03609

Browse files
author
Stephen Colebourne
committed
IO-90 - Fix freeSpace to avoid infinite loops and other errors
includes some code from Thomas Ledoux git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk@437031 13f79535-47bb-0310-9956-ffa450edef68
1 parent eaa8e0c commit 0a03609

3 files changed

Lines changed: 259 additions & 200 deletions

File tree

RELEASE-NOTES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ Bug fixes from 1.2
3838
- FileSystemUtils.freeSpace/freeSpaceKb [IO-83]
3939
- These should now work on AIX and HP-UX
4040

41+
- FileSystemUtils.freeSpace/freeSpaceKb [IO-90]
42+
- Avoid infinite looping in Windows
43+
- Catch more errors with nice messages
44+
4145

4246
Enhancements from 1.2
4347
---------------------

src/java/org/apache/commons/io/FileSystemUtils.java

Lines changed: 164 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package org.apache.commons.io;
1717

1818
import java.io.BufferedReader;
19-
import java.io.InputStreamReader;
2019
import java.io.IOException;
20+
import java.io.InputStreamReader;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.List;
2224
import java.util.StringTokenizer;
2325

2426
/**
@@ -37,6 +39,7 @@
3739
* @author Thomas Ledoux
3840
* @author James Urie
3941
* @author Magnus Grimsell
42+
* @author Thomas Ledoux
4043
* @version $Id$
4144
* @since Commons IO 1.1
4245
*/
@@ -189,6 +192,7 @@ long freeSpaceOS(String path, int os, boolean kb) throws IOException {
189192
}
190193
}
191194

195+
//-----------------------------------------------------------------------
192196
/**
193197
* Find free space on the Windows platform using the 'dir' command.
194198
*
@@ -201,85 +205,82 @@ long freeSpaceWindows(String path) throws IOException {
201205
if (path.length() > 2 && path.charAt(1) == ':') {
202206
path = path.substring(0, 2); // seems to make it work
203207
}
204-
208+
205209
// build and run the 'dir' command
206-
String[] cmdAttrbs = new String[] {"cmd.exe", "/C", "dir /-c " + path};
207-
210+
String[] cmdAttribs = new String[] {"cmd.exe", "/C", "dir /-c " + path};
211+
208212
// read in the output of the command to an ArrayList
209-
BufferedReader in = null;
210-
String line = null;
211-
ArrayList lines = new ArrayList();
212-
try {
213-
in = openProcessStream(cmdAttrbs);
214-
line = in.readLine();
215-
while (line != null) {
216-
line = line.toLowerCase().trim();
217-
lines.add(line);
218-
line = in.readLine();
219-
}
220-
} finally {
221-
IOUtils.closeQuietly(in);
222-
}
223-
224-
if (lines.size() == 0) {
225-
// unknown problem, throw exception
226-
throw new IOException(
227-
"Command line 'dir /c' did not return any info " +
228-
"for command '" + cmdAttrbs[2] + "'");
229-
}
230-
213+
List lines = performCommand(cmdAttribs, Integer.MAX_VALUE);
214+
231215
// now iterate over the lines we just read and find the LAST
232216
// non-empty line (the free space bytes should be in the last element
233217
// of the ArrayList anyway, but this will ensure it works even if it's
234218
// not, still assuming it is on the last non-blank line)
235-
long bytes = -1;
236-
int i = lines.size() - 1;
237-
int bytesStart = 0;
238-
int bytesEnd = 0;
239-
outerLoop: while (i > 0) {
240-
line = (String) lines.get(i);
219+
for (int i = lines.size() - 1; i >= 0; i--) {
220+
String line = (String) lines.get(i);
241221
if (line.length() > 0) {
242-
// found it, so now read from the end of the line to find the
243-
// last numeric character on the line, then continue until we
244-
// find the first non-numeric character, and everything between
245-
// that and the last numeric character inclusive is our free
246-
// space bytes count
247-
int j = line.length() - 1;
248-
innerLoop1: while (j >= 0) {
249-
char c = line.charAt(j);
250-
if (Character.isDigit(c)) {
251-
// found the last numeric character, this is the end of
252-
// the free space bytes count
253-
bytesEnd = j + 1;
254-
break innerLoop1;
255-
}
256-
j--;
257-
}
258-
innerLoop2: while (j >= 0) {
259-
char c = line.charAt(j);
260-
if (!Character.isDigit(c) && c != ',' && c != '.') {
261-
// found the next non-numeric character, this is the
262-
// beginning of the free space bytes count
263-
bytesStart = j + 1;
264-
break innerLoop2;
265-
}
266-
j--;
267-
}
268-
break outerLoop;
222+
return parseDir(line, path);
269223
}
270224
}
225+
// all lines are blank
226+
throw new IOException(
227+
"Command line 'dir /-c' did not return any info " +
228+
"for path '" + path + "'");
229+
}
271230

231+
/**
232+
* Parses the Windows dir response last line
233+
*
234+
* @param line the line to parse
235+
* @param path the path that was sent
236+
* @return the number of bytes
237+
* @throws IOException if an error occurs
238+
*/
239+
long parseDir(String line, String path) throws IOException {
240+
// read from the end of the line to find the last numeric
241+
// character on the line, then continue until we find the first
242+
// non-numeric character, and everything between that and the last
243+
// numeric character inclusive is our free space bytes count
244+
int bytesStart = 0;
245+
int bytesEnd = 0;
246+
int j = line.length() - 1;
247+
innerLoop1: while (j >= 0) {
248+
char c = line.charAt(j);
249+
if (Character.isDigit(c)) {
250+
// found the last numeric character, this is the end of
251+
// the free space bytes count
252+
bytesEnd = j + 1;
253+
break innerLoop1;
254+
}
255+
j--;
256+
}
257+
innerLoop2: while (j >= 0) {
258+
char c = line.charAt(j);
259+
if (!Character.isDigit(c) && c != ',' && c != '.') {
260+
// found the next non-numeric character, this is the
261+
// beginning of the free space bytes count
262+
bytesStart = j + 1;
263+
break innerLoop2;
264+
}
265+
j--;
266+
}
267+
if (j < 0) {
268+
throw new IOException(
269+
"Command line 'dir /-c' did not return valid info " +
270+
"for path '" + path + "'");
271+
}
272+
272273
// remove commas and dots in the bytes count
273274
StringBuffer buf = new StringBuffer(line.substring(bytesStart, bytesEnd));
274275
for (int k = 0; k < buf.length(); k++) {
275276
if (buf.charAt(k) == ',' || buf.charAt(k) == '.') {
276277
buf.deleteCharAt(k--);
277278
}
278279
}
279-
bytes = Long.parseLong(buf.toString());
280-
return bytes;
280+
return parseBytes(buf.toString(), path);
281281
}
282282

283+
//-----------------------------------------------------------------------
283284
/**
284285
* Find free space on the *nix platform using the 'df' command.
285286
*
@@ -306,70 +307,126 @@ long freeSpaceUnix(String path, boolean kb, boolean posix) throws IOException {
306307
String[] cmdAttribs =
307308
(flags.length() > 1 ? new String[] {"df", flags, path} : new String[] {"df", path});
308309

309-
// read the output from the command until we come to the second line
310-
long bytes = -1;
311-
BufferedReader in = null;
312-
try {
313-
in = openProcessStream(cmdAttribs);
314-
String line1 = in.readLine(); // header line (ignore it)
315-
String line2 = in.readLine(); // the line we're interested in
316-
String line3 = in.readLine(); // possibly interesting line
317-
if (line2 == null) {
318-
// unknown problem, throw exception
310+
// perform the command, asking for up to 3 lines (header, interesting, overflow)
311+
List lines = performCommand(cmdAttribs, 3);
312+
if (lines.size() < 2) {
313+
// unknown problem, throw exception
314+
throw new IOException(
315+
"Command line 'df' did not return info as expected " +
316+
"for path '" + path + "'- response was " + lines);
317+
}
318+
String line2 = (String) lines.get(1); // the line we're interested in
319+
320+
// Now, we tokenize the string. The fourth element is what we want.
321+
StringTokenizer tok = new StringTokenizer(line2, " ");
322+
if (tok.countTokens() < 4) {
323+
// could be long Filesystem, thus data on third line
324+
if (tok.countTokens() == 1 && lines.size() >= 3) {
325+
String line3 = (String) lines.get(2); // the line may be interested in
326+
tok = new StringTokenizer(line3, " ");
327+
} else {
319328
throw new IOException(
320-
"Command line 'df' did not return info as expected " +
321-
"for path '" + path +
322-
"'- response on first line was '" + line1 + "'");
329+
"Command line 'df' did not return data as expected " +
330+
"for path '" + path + "'- check path is valid");
323331
}
324-
line2 = line2.trim();
332+
} else {
333+
tok.nextToken(); // Ignore Filesystem
334+
}
335+
tok.nextToken(); // Ignore 1K-blocks
336+
tok.nextToken(); // Ignore Used
337+
String freeSpace = tok.nextToken();
338+
return parseBytes(freeSpace, path);
339+
}
325340

326-
// Now, we tokenize the string. The fourth element is what we want.
327-
StringTokenizer tok = new StringTokenizer(line2, " ");
328-
if (tok.countTokens() < 4) {
329-
// could be long Filesystem, thus data on third line
330-
if (tok.countTokens() == 1 && line3 != null) {
331-
line3 = line3.trim();
332-
tok = new StringTokenizer(line3, " ");
333-
} else {
334-
throw new IOException(
335-
"Command line 'df' did not return data as expected " +
336-
"for path '" + path + "'- check path is valid");
337-
}
338-
} else {
339-
tok.nextToken(); // Ignore Filesystem
340-
}
341-
tok.nextToken(); // Ignore 1K-blocks
342-
tok.nextToken(); // Ignore Used
343-
String freeSpace = tok.nextToken();
344-
try {
345-
bytes = Long.parseLong(freeSpace);
346-
} catch (NumberFormatException ex) {
341+
//-----------------------------------------------------------------------
342+
/**
343+
* Parses the bytes from a string.
344+
*
345+
* @param freeSpace the free space string
346+
* @param path the path
347+
* @return the number of bytes
348+
* @throws IOException if an error occurs
349+
*/
350+
long parseBytes(String freeSpace, String path) throws IOException {
351+
try {
352+
long bytes = Long.parseLong(freeSpace);
353+
if (bytes < 0) {
347354
throw new IOException(
348-
"Command line 'df' did not return numeric data as expected " +
355+
"Command line 'df' did not find free space in response " +
349356
"for path '" + path + "'- check path is valid");
350357
}
358+
return bytes;
359+
360+
} catch (NumberFormatException ex) {
361+
throw new IOException(
362+
"Command line 'df' did not return numeric data as expected " +
363+
"for path '" + path + "'- check path is valid");
364+
}
365+
}
351366

367+
/**
368+
* Performs the os command.
369+
*
370+
* @param cmdAttribs the command line parameters
371+
* @return the parsed data
372+
* @throws IOException if an error occurs
373+
*/
374+
List performCommand(String[] cmdAttribs, int max) throws IOException {
375+
List lines = new ArrayList();
376+
BufferedReader in = null;
377+
try {
378+
Process proc = openProcess(cmdAttribs);
379+
in = openProcessStream(proc);
380+
String line = in.readLine();
381+
while (line != null && lines.size() < max) {
382+
line = line.toLowerCase().trim();
383+
lines.add(line);
384+
line = in.readLine();
385+
}
386+
387+
proc.waitFor();
388+
if (proc.exitValue() != 0) {
389+
// os command problem, throw exception
390+
throw new IOException(
391+
"Command line returned OS error code '" + proc.exitValue() +
392+
"' for command " + Arrays.asList(cmdAttribs));
393+
}
394+
if (lines.size() == 0) {
395+
// unknown problem, throw exception
396+
throw new IOException(
397+
"Command line did not return any info " +
398+
"for command " + Arrays.asList(cmdAttribs));
399+
}
400+
return lines;
401+
402+
} catch (InterruptedException ex) {
403+
throw new IOException(
404+
"Command line threw an InterruptedException '" + ex.getMessage() +
405+
"' for command " + Arrays.asList(cmdAttribs));
352406
} finally {
353407
IOUtils.closeQuietly(in);
354408
}
409+
}
355410

356-
if (bytes < 0) {
357-
throw new IOException(
358-
"Command line 'df' did not find free space in response " +
359-
"for path '" + path + "'- check path is valid");
360-
}
361-
return bytes;
411+
/**
412+
* Opens the process to the operating system.
413+
*
414+
* @param cmdAttribs the command line parameters
415+
* @return the process
416+
* @throws IOException if an error occurs
417+
*/
418+
Process openProcess(String[] cmdAttribs) throws IOException {
419+
return Runtime.getRuntime().exec(cmdAttribs);
362420
}
363421

364422
/**
365-
* Opens the stream to be operating system.
423+
* Opens the stream to the operating system.
366424
*
367-
* @param params the command parameters
425+
* @param proc the process
368426
* @return a reader
369427
* @throws IOException if an error occurs
370428
*/
371-
BufferedReader openProcessStream(String[] params) throws IOException {
372-
Process proc = Runtime.getRuntime().exec(params);
429+
BufferedReader openProcessStream(Process proc) throws IOException {
373430
return new BufferedReader(
374431
new InputStreamReader(proc.getInputStream()));
375432
}

0 commit comments

Comments
 (0)