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