IoUtils.java revision 2bad9bff258de6275bd3847e5e9f3169b0a93c61
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
17package libcore.io;
18
19import java.io.Closeable;
20import java.io.File;
21import java.io.FileDescriptor;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.io.RandomAccessFile;
25import java.net.BindException;
26import java.net.ConnectException;
27import java.net.Inet4Address;
28import java.net.Inet6Address;
29import java.net.InetAddress;
30import java.net.InetSocketAddress;
31import java.net.NetworkInterface;
32import java.net.Socket;
33import java.net.SocketAddress;
34import java.net.SocketException;
35import java.net.SocketOptions;
36import java.net.SocketTimeoutException;
37import java.net.UnknownHostException;
38import java.nio.charset.Charsets;
39import java.util.Arrays;
40import libcore.util.MutableInt;
41import static libcore.io.OsConstants.*;
42
43// TODO: kill this!
44import org.apache.harmony.luni.platform.Platform;
45
46public final class IoUtils {
47    private IoUtils() {
48    }
49
50    /**
51     * Implements java.io/java.net "available" semantics.
52     */
53    public static int available(FileDescriptor fd) throws IOException {
54        try {
55            MutableInt available = new MutableInt(0);
56            int rc = Libcore.os.ioctlInt(fd, FIONREAD, available);
57            if (available.value < 0) {
58                // If the fd refers to a regular file, the result is the difference between
59                // the file size and the file position. This may be negative if the position
60                // is past the end of the file. If the fd refers to a special file masquerading
61                // as a regular file, the result may be negative because the special file
62                // may appear to have zero size and yet a previous read call may have
63                // read some amount of data and caused the file position to be advanced.
64                available.value = 0;
65            }
66            return available.value;
67        } catch (ErrnoException errnoException) {
68            if (errnoException.errno == ENOTTY) {
69                // The fd is unwilling to opine about its read buffer.
70                return 0;
71            }
72            throw errnoException.rethrowAsIOException();
73        }
74    }
75
76    /**
77     * java.io only throws FileNotFoundException when opening files, regardless of what actually
78     * went wrong. Additionally, java.io is more restrictive than POSIX when it comes to opening
79     * directories: POSIX says read-only is okay, but java.io doesn't even allow that. We also
80     * have an Android-specific hack to alter the default permissions.
81     */
82    public static FileDescriptor open(String path, int flags) throws FileNotFoundException {
83        FileDescriptor fd = null;
84        try {
85            // On Android, we don't want default permissions to allow global access.
86            int mode = ((flags & O_ACCMODE) == O_RDONLY) ? 0 : 0600;
87            fd = Libcore.os.open(path, flags, mode);
88            if (fd.valid()) {
89                // Posix open(2) fails with EISDIR only if you ask for write permission.
90                // Java disallows reading directories too.
91                boolean isDirectory = false;
92                if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
93                    throw new ErrnoException("open", EISDIR);
94                }
95            }
96            return fd;
97        } catch (ErrnoException errnoException) {
98            try {
99                if (fd != null) {
100                    close(fd);
101                }
102            } catch (IOException ignored) {
103            }
104            FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage());
105            ex.initCause(errnoException);
106            throw ex;
107        }
108    }
109
110    /**
111     * java.io thinks that a read at EOF is an error and should return -1, contrary to traditional
112     * Unix practice where you'd read until you got 0 bytes (and any future read would return -1).
113     */
114    public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
115        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
116        if (byteCount == 0) {
117            return 0;
118        }
119        try {
120            int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount);
121            if (readCount == 0) {
122                return -1;
123            }
124            return readCount;
125        } catch (ErrnoException errnoException) {
126            if (errnoException.errno == EAGAIN) {
127                // We return 0 rather than throw if we try to read from an empty non-blocking pipe.
128                return 0;
129            }
130            throw errnoException.rethrowAsIOException();
131        }
132    }
133
134    /**
135     * java.io always writes every byte it's asked to, or fails with an error. (That is, unlike
136     * Unix it never just writes as many bytes as happens to be convenient.)
137     */
138    public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
139        Arrays.checkOffsetAndCount(bytes.length, byteOffset, byteCount);
140        if (byteCount == 0) {
141            return;
142        }
143        try {
144            while (byteCount > 0) {
145                int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount);
146                byteCount -= bytesWritten;
147                byteOffset += bytesWritten;
148            }
149        } catch (ErrnoException errnoException) {
150            throw errnoException.rethrowAsIOException();
151        }
152    }
153
154    public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException {
155        if (address instanceof Inet6Address && ((Inet6Address) address).getScopeId() == 0) {
156            // Linux won't let you bind a link-local address without a scope id. Find one.
157            NetworkInterface nif = NetworkInterface.getByInetAddress(address);
158            if (nif == null) {
159                throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
160            }
161            try {
162                address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
163            } catch (UnknownHostException ex) {
164                throw new AssertionError(ex); // Can't happen.
165            }
166        }
167        try {
168            Libcore.os.bind(fd, address, port);
169        } catch (ErrnoException errnoException) {
170            throw new BindException(errnoException.getMessage(), errnoException);
171        }
172    }
173
174    /**
175     * Calls close(2) on 'fd'. Also resets the internal int to -1. Does nothing if 'fd' is null
176     * or invalid.
177     */
178    public static void close(FileDescriptor fd) throws IOException {
179        try {
180            if (fd != null && fd.valid()) {
181                Libcore.os.close(fd);
182            }
183        } catch (ErrnoException errnoException) {
184            throw errnoException.rethrowAsIOException();
185        }
186    }
187
188    /**
189     * Closes 'closeable', ignoring any exceptions. Does nothing if 'closeable' is null.
190     */
191    public static void closeQuietly(Closeable closeable) {
192        if (closeable != null) {
193            try {
194                closeable.close();
195            } catch (IOException ignored) {
196            }
197        }
198    }
199
200    /**
201     * Closes 'fd', ignoring any exceptions. Does nothing if 'fd' is null or invalid.
202     */
203    public static void closeQuietly(FileDescriptor fd) {
204        try {
205            IoUtils.close(fd);
206        } catch (IOException ignored) {
207        }
208    }
209
210    /**
211     * Closes 'socket', ignoring any exceptions. Does nothing if 'socket' is null.
212     */
213    public static void closeQuietly(Socket socket) {
214        if (socket != null) {
215            try {
216                socket.close();
217            } catch (Exception ignored) {
218            }
219        }
220    }
221
222    /**
223     * Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout
224     * means this method won't throw SocketTimeoutException.
225     */
226    public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
227        try {
228            return IoUtils.connect(fd, inetAddress, port, 0);
229        } catch (SocketTimeoutException ex) {
230            throw new AssertionError(ex); // Can't happen for a connect without a timeout.
231        }
232    }
233
234    /**
235     * Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'.
236     * Use timeoutMs == 0 for a blocking connect with no timeout.
237     */
238    public static boolean connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
239        try {
240            return connectErrno(fd, inetAddress, port, timeoutMs);
241        } catch (ErrnoException errnoException) {
242            throw new ConnectException(connectDetail(inetAddress, port, timeoutMs, errnoException), errnoException);
243        } catch (SocketException ex) {
244            throw ex; // We don't want to doubly wrap these.
245        } catch (SocketTimeoutException ex) {
246            throw ex; // We don't want to doubly wrap these.
247        } catch (IOException ex) {
248            throw new SocketException(ex);
249        }
250    }
251
252    private static boolean connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws IOException {
253        // With no timeout, just call connect(2) directly.
254        if (timeoutMs == 0) {
255            Libcore.os.connect(fd, inetAddress, port);
256            return true;
257        }
258
259        // With a timeout, we set the socket to non-blocking, connect(2), and then loop
260        // using poll(2) to decide whether we're connected, whether we should keep waiting,
261        // or whether we've seen a permanent failure and should give up.
262        long finishTimeMs = System.currentTimeMillis() + timeoutMs;
263        IoUtils.setBlocking(fd, false);
264        try {
265            try {
266                Libcore.os.connect(fd, inetAddress, port);
267                return true; // We connected immediately.
268            } catch (ErrnoException errnoException) {
269                if (errnoException.errno != EINPROGRESS) {
270                    throw errnoException;
271                }
272                // EINPROGRESS means we should keep trying...
273            }
274            int remainingTimeoutMs;
275            do {
276                remainingTimeoutMs = (int) (finishTimeMs - System.currentTimeMillis());
277                if (remainingTimeoutMs <= 0) {
278                    throw new SocketTimeoutException(connectDetail(inetAddress, port, timeoutMs, null));
279                }
280            } while (!IoUtils.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs));
281            return true; // Or we'd have thrown.
282        } finally {
283            IoUtils.setBlocking(fd, true);
284        }
285    }
286
287    private static String connectDetail(InetAddress inetAddress, int port, int timeoutMs, ErrnoException cause) {
288        String detail = "failed to connect to " + inetAddress + " (port " + port + ")";
289        if (timeoutMs > 0) {
290            detail += " after " + timeoutMs + "ms";
291        }
292        if (cause != null) {
293            detail += ": " + cause.getMessage();
294        }
295        return detail;
296    }
297
298    public static boolean isConnected(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs, int remainingTimeoutMs) throws IOException {
299        ErrnoException cause = null;
300        try {
301            StructPollfd[] pollFds = new StructPollfd[] { new StructPollfd() };
302            pollFds[0].fd = fd;
303            pollFds[0].events = (short) POLLOUT;
304            int rc = Libcore.os.poll(pollFds, remainingTimeoutMs);
305            if (rc == 0) {
306                return false; // Timeout.
307            }
308            int connectError = Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_ERROR);
309            if (connectError == 0) {
310                return true; // Success!
311            }
312            throw new ErrnoException("isConnected", connectError); // The connect(2) failed.
313        } catch (ErrnoException errnoException) {
314            if (errnoException.errno == EINTR) {
315                return false; // Punt and ask the caller to try again.
316            } else {
317                cause = errnoException;
318            }
319        }
320        // TODO: is it really helpful/necessary to throw so many different exceptions?
321        String detail = connectDetail(inetAddress, port, timeoutMs, cause);
322        if (cause.errno == ECONNRESET || cause.errno == ECONNREFUSED ||
323                cause.errno == EADDRNOTAVAIL || cause.errno == EADDRINUSE ||
324                cause.errno == ENETUNREACH) {
325            throw new ConnectException(detail, cause);
326        } else if (cause.errno == EACCES) {
327            throw new SecurityException(detail, cause);
328        } else if (cause.errno == ETIMEDOUT) {
329            throw new SocketTimeoutException(detail, cause);
330        }
331        throw new SocketException(detail, cause);
332    }
333
334    /**
335     * Sets 'fd' to be blocking or non-blocking, according to the state of 'blocking'.
336     */
337    public static void setBlocking(FileDescriptor fd, boolean blocking) throws IOException {
338        try {
339            int flags = Libcore.os.fcntlVoid(fd, F_GETFL);
340            if (!blocking) {
341                flags |= O_NONBLOCK;
342            } else {
343                flags &= ~O_NONBLOCK;
344            }
345            Libcore.os.fcntlLong(fd, F_SETFL, flags);
346        } catch (ErrnoException errnoException) {
347            throw errnoException.rethrowAsIOException();
348        }
349    }
350
351    public static FileDescriptor socket(boolean stream) throws SocketException {
352        FileDescriptor fd;
353        try {
354            fd = Libcore.os.socket(AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM, 0);
355
356            // The RFC (http://www.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults
357            // to 1. The Linux kernel (at least up to 2.6.38) accidentally defaults to 64 (which
358            // would be correct for the *unicast* hop limit).
359            // See http://www.spinics.net/lists/netdev/msg129022.html, though no patch appears to
360            // have been applied as a result of that discussion. If that bug is ever fixed, we can
361            // remove this code. Until then, we manually set the hop limit on IPv6 datagram sockets.
362            // (IPv4 is already correct.)
363            if (!stream) {
364                Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 1);
365            }
366
367            return fd;
368        } catch (ErrnoException errnoException) {
369            throw errnoException.rethrowAsSocketException();
370        }
371    }
372
373    // Socket options used by java.net but not exposed in SocketOptions.
374    public static final int JAVA_MCAST_JOIN_GROUP = 19;
375    public static final int JAVA_MCAST_LEAVE_GROUP = 20;
376    public static final int JAVA_IP_MULTICAST_TTL = 17;
377
378    /**
379     * java.net has its own socket options similar to the underlying Unix ones. We paper over the
380     * differences here.
381     */
382    public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException {
383        try {
384            return getSocketOptionErrno(fd, option);
385        } catch (ErrnoException errnoException) {
386            throw errnoException.rethrowAsSocketException();
387        }
388    }
389
390    private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws SocketException {
391        switch (option) {
392        case SocketOptions.IP_MULTICAST_IF:
393            // This is IPv4-only.
394            return Libcore.os.getsockoptInAddr(fd, IPPROTO_IP, IP_MULTICAST_IF);
395        case SocketOptions.IP_MULTICAST_IF2:
396            // This is IPv6-only.
397            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
398        case SocketOptions.IP_MULTICAST_LOOP:
399            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
400            // it doesn't matter which we return.
401            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
402        case IoUtils.JAVA_IP_MULTICAST_TTL:
403            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
404            // it doesn't matter which we return.
405            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
406        case SocketOptions.IP_TOS:
407            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
408            // it doesn't matter which we return.
409            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
410        case SocketOptions.SO_BROADCAST:
411            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_BROADCAST));
412        case SocketOptions.SO_KEEPALIVE:
413            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE));
414        case SocketOptions.SO_LINGER:
415            StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
416            if (!linger.isOn()) {
417                return false;
418            }
419            return linger.l_linger;
420        case SocketOptions.SO_OOBINLINE:
421            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE));
422        case SocketOptions.SO_RCVBUF:
423            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
424        case SocketOptions.SO_REUSEADDR:
425            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR));
426        case SocketOptions.SO_SNDBUF:
427            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
428        case SocketOptions.SO_TIMEOUT:
429            return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis();
430        case SocketOptions.TCP_NODELAY:
431            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
432        default:
433            throw new SocketException("Unknown socket option: " + option);
434        }
435    }
436
437    private static boolean booleanFromInt(int i) {
438        return (i != 0);
439    }
440
441    private static int booleanToInt(boolean b) {
442        return b ? 1 : 0;
443    }
444
445    /**
446     * java.net has its own socket options similar to the underlying Unix ones. We paper over the
447     * differences here.
448     */
449    public static void setSocketOption(FileDescriptor fd, int option, Object value) throws SocketException {
450        try {
451            setSocketOptionErrno(fd, option, value);
452        } catch (ErrnoException errnoException) {
453            throw errnoException.rethrowAsSocketException();
454        }
455    }
456
457    private static void setSocketOptionErrno(FileDescriptor fd, int option, Object value) throws SocketException {
458        switch (option) {
459        case SocketOptions.IP_MULTICAST_IF:
460            throw new UnsupportedOperationException("Use IP_MULTICAST_IF2 on Android");
461        case SocketOptions.IP_MULTICAST_IF2:
462            // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
463            Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, (Integer) value);
464            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (Integer) value);
465            return;
466        case SocketOptions.IP_MULTICAST_LOOP:
467            // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
468            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, booleanToInt((Boolean) value));
469            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, booleanToInt((Boolean) value));
470            return;
471        case IoUtils.JAVA_IP_MULTICAST_TTL:
472            // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
473            // IPv4 multicast TTL uses a byte.
474            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL, (Integer) value);
475            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (Integer) value);
476            return;
477        case SocketOptions.IP_TOS:
478            Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_TOS, (Integer) value);
479            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS, (Integer) value);
480            return;
481        case SocketOptions.SO_BROADCAST:
482            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_BROADCAST, booleanToInt((Boolean) value));
483            return;
484        case SocketOptions.SO_KEEPALIVE:
485            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE, booleanToInt((Boolean) value));
486            return;
487        case SocketOptions.SO_LINGER:
488            boolean on = false;
489            int seconds = 0;
490            if (value instanceof Integer) {
491                on = true;
492                seconds = Math.min((Integer) value, 65535);
493            }
494            StructLinger linger = new StructLinger(booleanToInt(on), seconds);
495            Libcore.os.setsockoptLinger(fd, SOL_SOCKET, SO_LINGER, linger);
496            return;
497        case SocketOptions.SO_OOBINLINE:
498            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE, booleanToInt((Boolean) value));
499            return;
500        case SocketOptions.SO_RCVBUF:
501            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, (Integer) value);
502            return;
503        case SocketOptions.SO_REUSEADDR:
504            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR, booleanToInt((Boolean) value));
505            return;
506        case SocketOptions.SO_SNDBUF:
507            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_SNDBUF, (Integer) value);
508            return;
509        case SocketOptions.SO_TIMEOUT:
510            int millis = (Integer) value;
511            StructTimeval tv = StructTimeval.fromMillis(millis);
512            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv);
513            return;
514        case SocketOptions.TCP_NODELAY:
515            Libcore.os.setsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY, booleanToInt((Boolean) value));
516            return;
517        case IoUtils.JAVA_MCAST_JOIN_GROUP:
518        case IoUtils.JAVA_MCAST_LEAVE_GROUP:
519            StructGroupReq groupReq = (StructGroupReq) value;
520            int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
521            int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
522            Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
523            return;
524        default:
525            throw new SocketException("Unknown socket option: " + option);
526        }
527    }
528
529    public static InetAddress getSocketLocalAddress(FileDescriptor fd) {
530        SocketAddress sa = Libcore.os.getsockname(fd);
531        InetSocketAddress isa = (InetSocketAddress) sa;
532        return isa.getAddress();
533    }
534
535    public static int getSocketLocalPort(FileDescriptor fd) {
536        SocketAddress sa = Libcore.os.getsockname(fd);
537        InetSocketAddress isa = (InetSocketAddress) sa;
538        return isa.getPort();
539    }
540
541    /**
542     * Returns the contents of 'path' as a byte array.
543     */
544    public static byte[] readFileAsByteArray(String path) throws IOException {
545        return readFileAsBytes(path).toByteArray();
546    }
547
548    /**
549     * Returns the contents of 'path' as a string. The contents are assumed to be UTF-8.
550     */
551    public static String readFileAsString(String path) throws IOException {
552        return readFileAsBytes(path).toString(Charsets.UTF_8);
553    }
554
555    private static UnsafeByteSequence readFileAsBytes(String path) throws IOException {
556        RandomAccessFile f = null;
557        try {
558            f = new RandomAccessFile(path, "r");
559            UnsafeByteSequence bytes = new UnsafeByteSequence((int) f.length());
560            byte[] buffer = new byte[8192];
561            while (true) {
562                int byteCount = f.read(buffer);
563                if (byteCount == -1) {
564                    return bytes;
565                }
566                bytes.write(buffer, 0, byteCount);
567            }
568        } finally {
569            IoUtils.closeQuietly(f);
570        }
571    }
572
573    /**
574     * Recursively delete everything in {@code dir}.
575     */
576    // TODO: this should specify paths as Strings rather than as Files
577    public static void deleteContents(File dir) throws IOException {
578        File[] files = dir.listFiles();
579        if (files == null) {
580            throw new IllegalArgumentException("not a directory: " + dir);
581        }
582        for (File file : files) {
583            if (file.isDirectory()) {
584                deleteContents(file);
585            }
586            if (!file.delete()) {
587                throw new IOException("failed to delete file: " + file);
588            }
589        }
590    }
591}
592