aboutsummaryrefslogtreecommitdiff
path: root/luni/src/main/java/libcore/io/IoBridge.java
blob: 8341bf53b02ef5246bd5cb421d54d09ee98a8565 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package libcore.io;

import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import android.system.StructGroupReq;
import android.system.StructLinger;
import android.system.StructPollfd;
import android.system.StructTimeval;

import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.BindException;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.NoRouteToHostException;
import java.net.PortUnreachableException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;

import libcore.util.ArrayUtils;

import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.system.OsConstants.*;

/**
 * Collection of utility methods to work with blocking and non-blocking I/O that wrap raw POSIX
 * system calls, e.g. {@link android.system.Os}. These wrappers are to signal other blocked I/O
 * threads and avoid boilerplate code of routine error checks when using raw system calls.
 *
 * <p>
 * For example, when using {@link Os#read(FileDescriptor, byte[], int, int)}, return value can
 * contain:
 * <ul>
 *   <li>{@code 0} which means EOF</li>
 *   <li>{@code N > 0} which means number of bytes read</li>
 *   <li>{@code -1} which means error, and {@link ErrnoException} is thrown</li>
 * </ul>
 *
 * <p>
 * {@link ErrnoException} in its turn can be one of:
 * <ul>
 *   <li>{@link android.system.OsConstants#EAGAIN} which means the file descriptor refers to a file
 *       or a socket, which has been marked nonblocking
 *       ({@link android.system.OsConstants#O_NONBLOCK}), and the read would block</li>
 *   <li>{@link android.system.OsConstants#EBADF} which means the file descriptor is not a valid
 *       file descriptor or is not open for reading</li>
 *   <li>{@link android.system.OsConstants#EFAULT} which means given buffer is outside accessible
 *       address space</li>
 *   <li>{@link android.system.OsConstants#EINTR} which means the call was interrupted by a signal
 *       before any data was read</li>
 *   <li>{@link android.system.OsConstants#EINVAL} which means the file descriptor is attached to
 *       an object which is unsuitable for reading; or the file was opened with the
 *       {@link android.system.OsConstants#O_DIRECT} flag, and either the address specified in
 *       {@code buffer}, the value specified in {@code count}, or the file {@code offset} is not
 *       suitably aligned</li>
 *   <li>{@link android.system.OsConstants#EIO} which means I/O error happened</li>
 *   <li>{@link android.system.OsConstants#EISDIR} which means the file descriptor refers to a
 *       directory</li>
 * </ul>
 *
 * All these errors require handling, and this class contains some wrapper methods that handle most
 * common cases, making usage of system calls more user friendly.
 *
 * @hide
 */
@libcore.api.CorePlatformApi
public final class IoBridge {

    private IoBridge() {
    }

    /** @hide */
    public static int available(FileDescriptor fd) throws IOException {
        try {
            int available = Libcore.os.ioctlInt(fd, FIONREAD);
            if (available < 0) {
                // If the fd refers to a regular file, the result is the difference between
                // the file size and the file position. This may be negative if the position
                // is past the end of the file. If the fd refers to a special file masquerading
                // as a regular file, the result may be negative because the special file
                // may appear to have zero size and yet a previous read call may have
                // read some amount of data and caused the file position to be advanced.
                available = 0;
            }
            return available;
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == ENOTTY) {
                // The fd is unwilling to opine about its read buffer.
                return 0;
            }
            throw errnoException.rethrowAsIOException();
        }
    }

    /** @hide */
    public static void bind(FileDescriptor fd, InetAddress address, int port) throws SocketException {
        if (address instanceof Inet6Address) {
            Inet6Address inet6Address = (Inet6Address) address;
            if (inet6Address.getScopeId() == 0 && inet6Address.isLinkLocalAddress()) {
                // Linux won't let you bind a link-local address without a scope id.
                // Find one.
                NetworkInterface nif = NetworkInterface.getByInetAddress(address);
                if (nif == null) {
                    throw new SocketException("Can't bind to a link-local address without a scope id: " + address);
                }
                try {
                    address = Inet6Address.getByAddress(address.getHostName(), address.getAddress(), nif.getIndex());
                } catch (UnknownHostException ex) {
                    throw new AssertionError(ex); // Can't happen.
                }
            }
        }
        try {
            Libcore.os.bind(fd, address, port);
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == EADDRINUSE || errnoException.errno == EADDRNOTAVAIL ||
                errnoException.errno == EPERM || errnoException.errno == EACCES) {
                throw new BindException(errnoException.getMessage(), errnoException);
            } else {
                throw new SocketException(errnoException.getMessage(), errnoException);
            }
        }
    }


    /**
     * Connects socket 'fd' to 'inetAddress' on 'port', with no timeout. The lack of a timeout
     * means this method won't throw SocketTimeoutException.
     *
     * @hide
     */
    public static void connect(FileDescriptor fd, InetAddress inetAddress, int port) throws SocketException {
        try {
            IoBridge.connect(fd, inetAddress, port, 0);
        } catch (SocketTimeoutException ex) {
            throw new AssertionError(ex); // Can't happen for a connect without a timeout.
        }
    }

    /**
     * Connects socket 'fd' to 'inetAddress' on 'port', with a the given 'timeoutMs'.
     * Use timeoutMs == 0 for a blocking connect with no timeout.
     *
     * @hide
     */
    public static void connect(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws SocketException, SocketTimeoutException {
        try {
            connectErrno(fd, inetAddress, port, timeoutMs);
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == EHOSTUNREACH) {
                throw new NoRouteToHostException("Host unreachable");
            }
            if (errnoException.errno == EADDRNOTAVAIL) {
                throw new NoRouteToHostException("Address not available");
            }
            throw new ConnectException(createMessageForException(fd, inetAddress, port, timeoutMs,
                    errnoException), errnoException);
        } catch (SocketException ex) {
            throw ex; // We don't want to doubly wrap these.
        } catch (SocketTimeoutException ex) {
            throw ex; // We don't want to doubly wrap these.
        } catch (IOException ex) {
            throw new SocketException(ex);
        }
    }

    private static void connectErrno(FileDescriptor fd, InetAddress inetAddress, int port, int timeoutMs) throws ErrnoException, IOException {
        // With no timeout, just call connect(2) directly.
        if (timeoutMs <= 0) {
            Libcore.os.connect(fd, inetAddress, port);
            return;
        }

        // For connect with a timeout, we:
        //   1. set the socket to non-blocking,
        //   2. connect(2),
        //   3. loop using poll(2) to decide whether we're connected, whether we should keep
        //      waiting, or whether we've seen a permanent failure and should give up,
        //   4. set the socket back to blocking.

        // 1. set the socket to non-blocking.
        IoUtils.setBlocking(fd, false);

        // 2. call connect(2) non-blocking.
        long finishTimeNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
        try {
            Libcore.os.connect(fd, inetAddress, port);
            IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking.
            return; // We connected immediately.
        } catch (ErrnoException errnoException) {
            if (errnoException.errno != EINPROGRESS) {
                throw errnoException;
            }
            // EINPROGRESS means we should keep trying...
        }

        // 3. loop using poll(2).
        int remainingTimeoutMs;
        do {
            remainingTimeoutMs =
                    (int) TimeUnit.NANOSECONDS.toMillis(finishTimeNanos - System.nanoTime());
            if (remainingTimeoutMs <= 0) {
                throw new SocketTimeoutException(
                        createMessageForException(fd, inetAddress, port, timeoutMs, null));
            }
        } while (!IoBridge.isConnected(fd, inetAddress, port, timeoutMs, remainingTimeoutMs));
        IoUtils.setBlocking(fd, true); // 4. set the socket back to blocking.
    }

    /**
     * Constructs the message for an exception that the caller is about to throw.
     *
     * @hide
     */
    private static String createMessageForException(FileDescriptor fd, InetAddress inetAddress,
            int port, int timeoutMs, Exception causeOrNull) {
        // Figure out source address from fd.
        InetSocketAddress localAddress = null;
        try {
            localAddress = getLocalInetSocketAddress(fd);
        } catch (SocketException ignored) {
            // The caller is about to throw an exception, so this one would only distract.
        }

        StringBuilder sb = new StringBuilder("failed to connect")
              .append(" to ")
              .append(inetAddress)
              .append(" (port ")
              .append(port)
              .append(")");
        if (localAddress != null) {
            sb.append(" from ")
              .append(localAddress.getAddress())
              .append(" (port ")
              .append(localAddress.getPort())
              .append(")");
        }
        if (timeoutMs > 0) {
            sb.append(" after ")
              .append(timeoutMs)
              .append("ms");
        }
        if (causeOrNull != null) {
            sb.append(": ")
              .append(causeOrNull.getMessage());
        }
        return sb.toString();
    }

    /**
     * Closes the Unix file descriptor associated with the supplied file descriptor, resets the
     * internal int to -1, and sends a signal to any threads are currently blocking. In order for
     * the signal to be sent the blocked threads must have registered with the
     * {@link AsynchronousCloseMonitor} before they entered the blocking operation. {@code fd} will be
     * invalid after this call.
     *
     * <p>This method is a no-op if passed a {@code null} or already-closed file descriptor.
     *
     * @param fd file descriptor to be closed
     * @throws IOException if underlying system call fails with {@link ErrnoException}
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static void closeAndSignalBlockedThreads(FileDescriptor fd) throws IOException {
        if (fd == null) {
            return;
        }

        // fd is invalid after we call release$.
        // If multiple threads reach this point simultaneously, release$ is synchronized, so one
        // of them will receive the old fd, and the rest will get an empty FileDescriptor.
        FileDescriptor oldFd = fd.release$();
        if (!oldFd.valid()) {
            return;
        }

        AsynchronousCloseMonitor.signalBlockedThreads(oldFd);
        try {
            Libcore.os.close(oldFd);
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsIOException();
        }
    }

    /** @hide */
    @UnsupportedAppUsage
    public static boolean isConnected(FileDescriptor fd, InetAddress inetAddress, int port,
            int timeoutMs, int remainingTimeoutMs) throws IOException {
        ErrnoException cause;
        try {
            StructPollfd[] pollFds = new StructPollfd[] { new StructPollfd() };
            pollFds[0].fd = fd;
            pollFds[0].events = (short) POLLOUT;
            int rc = Libcore.os.poll(pollFds, remainingTimeoutMs);
            if (rc == 0) {
                return false; // Timeout.
            }
            int connectError = Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_ERROR);
            if (connectError == 0) {
                return true; // Success!
            }
            throw new ErrnoException("isConnected", connectError); // The connect(2) failed.
        } catch (ErrnoException errnoException) {
            if (!fd.valid()) {
                throw new SocketException("Socket closed");
            }
            cause = errnoException;
        }
        String detail = createMessageForException(fd, inetAddress, port, timeoutMs, cause);
        if (cause.errno == ETIMEDOUT) {
            SocketTimeoutException e = new SocketTimeoutException(detail);
            e.initCause(cause);
            throw e;
        }
        throw new ConnectException(detail, cause);
    }

    // Socket options used by java.net but not exposed in SocketOptions.
    /** @hide */
    public static final int JAVA_MCAST_JOIN_GROUP = 19;
    /** @hide */
    public static final int JAVA_MCAST_LEAVE_GROUP = 20;
    /** @hide */
    public static final int JAVA_IP_MULTICAST_TTL = 17;
    /** @hide */
    public static final int JAVA_IP_TTL = 25;

    /**
     * java.net has its own socket options similar to the underlying Unix ones. We paper over the
     * differences here.
     * @hide
     */
    public static Object getSocketOption(FileDescriptor fd, int option) throws SocketException {
        try {
            return getSocketOptionErrno(fd, option);
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsSocketException();
        }
    }

    private static Object getSocketOptionErrno(FileDescriptor fd, int option) throws ErrnoException, SocketException {
        switch (option) {
        case SocketOptions.IP_MULTICAST_IF:
        case SocketOptions.IP_MULTICAST_IF2:
            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF);
        case SocketOptions.IP_MULTICAST_LOOP:
            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
            // it doesn't matter which we return.
            // NOTE: getsockopt's return value means "isEnabled", while OpenJDK code java.net
            // requires a value that means "isDisabled" so we NEGATE the system call value here.
            return !booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP));
        case IoBridge.JAVA_IP_MULTICAST_TTL:
            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
            // it doesn't matter which we return.
            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS);
        case IoBridge.JAVA_IP_TTL:
            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
            // it doesn't matter which we return.
            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS);
        case SocketOptions.IP_TOS:
            // Since setting this from java.net always sets IPv4 and IPv6 to the same value,
            // it doesn't matter which we return.
            return Libcore.os.getsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS);
        case SocketOptions.SO_BROADCAST:
            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_BROADCAST));
        case SocketOptions.SO_KEEPALIVE:
            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE));
        case SocketOptions.SO_LINGER:
            StructLinger linger = Libcore.os.getsockoptLinger(fd, SOL_SOCKET, SO_LINGER);
            if (!linger.isOn()) {
                return false;
            }
            return linger.l_linger;
        case SocketOptions.SO_OOBINLINE:
            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE));
        case SocketOptions.SO_RCVBUF:
            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_RCVBUF);
        case SocketOptions.SO_REUSEADDR:
            return booleanFromInt(Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR));
        case SocketOptions.SO_SNDBUF:
            return Libcore.os.getsockoptInt(fd, SOL_SOCKET, SO_SNDBUF);
        case SocketOptions.SO_TIMEOUT:
            return (int) Libcore.os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO).toMillis();
        case SocketOptions.TCP_NODELAY:
            return booleanFromInt(Libcore.os.getsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY));
        case SocketOptions.SO_BINDADDR:
            return ((InetSocketAddress) Libcore.os.getsockname(fd)).getAddress();
        default:
            throw new SocketException("Unknown socket option: " + option);
        }
    }

    private static boolean booleanFromInt(int i) {
        return (i != 0);
    }

    private static int booleanToInt(boolean b) {
        return b ? 1 : 0;
    }

    /**
     * java.net has its own socket options similar to the underlying Unix ones. We paper over the
     * differences here.
     *
     * @hide
     */
    public static void setSocketOption(FileDescriptor fd, int option, Object value) throws SocketException {
        try {
            setSocketOptionErrno(fd, option, value);
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsSocketException();
        }
    }

    private static void setSocketOptionErrno(FileDescriptor fd, int option, Object value) throws ErrnoException, SocketException {
        switch (option) {
        case SocketOptions.IP_MULTICAST_IF:
            NetworkInterface nif = NetworkInterface.getByInetAddress((InetAddress) value);
            if (nif == null) {
                throw new SocketException(
                        "bad argument for IP_MULTICAST_IF : address not bound to any interface");
            }
            // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
            Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, nif.getIndex());
            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, nif.getIndex());
            return;
        case SocketOptions.IP_MULTICAST_IF2:
            // Although IPv6 was cleaned up to use int, IPv4 uses an ip_mreqn containing an int.
            Libcore.os.setsockoptIpMreqn(fd, IPPROTO_IP, IP_MULTICAST_IF, (Integer) value);
            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (Integer) value);
            return;
        case SocketOptions.IP_MULTICAST_LOOP:
            // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte.
            // NOTE: setsockopt's arguement value means "isEnabled", while OpenJDK code java.net
            // uses a value that means "isDisabled" so we NEGATE the system call value here.
            int enable = booleanToInt(!((Boolean) value));
            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_LOOP, enable);
            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, enable);
            return;
        case IoBridge.JAVA_IP_MULTICAST_TTL:
            // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int,
            // IPv4 multicast TTL uses a byte.
            Libcore.os.setsockoptByte(fd, IPPROTO_IP, IP_MULTICAST_TTL, (Integer) value);
            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (Integer) value);
            return;
        case IoBridge.JAVA_IP_TTL:
            Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_TTL, (Integer) value);
            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (Integer) value);
            return;
        case SocketOptions.IP_TOS:
            Libcore.os.setsockoptInt(fd, IPPROTO_IP, IP_TOS, (Integer) value);
            Libcore.os.setsockoptInt(fd, IPPROTO_IPV6, IPV6_TCLASS, (Integer) value);
            return;
        case SocketOptions.SO_BROADCAST:
            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_BROADCAST, booleanToInt((Boolean) value));
            return;
        case SocketOptions.SO_KEEPALIVE:
            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_KEEPALIVE, booleanToInt((Boolean) value));
            return;
        case SocketOptions.SO_LINGER:
            boolean on = false;
            int seconds = 0;
            if (value instanceof Integer) {
                on = true;
                seconds = Math.min((Integer) value, 65535);
            }
            StructLinger linger = new StructLinger(booleanToInt(on), seconds);
            Libcore.os.setsockoptLinger(fd, SOL_SOCKET, SO_LINGER, linger);
            return;
        case SocketOptions.SO_OOBINLINE:
            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_OOBINLINE, booleanToInt((Boolean) value));
            return;
        case SocketOptions.SO_RCVBUF:
            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, (Integer) value);
            return;
        case SocketOptions.SO_REUSEADDR:
            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_REUSEADDR, booleanToInt((Boolean) value));
            return;
        case SocketOptions.SO_SNDBUF:
            Libcore.os.setsockoptInt(fd, SOL_SOCKET, SO_SNDBUF, (Integer) value);
            return;
        case SocketOptions.SO_TIMEOUT:
            int millis = (Integer) value;
            StructTimeval tv = StructTimeval.fromMillis(millis);
            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv);
            return;
        case SocketOptions.TCP_NODELAY:
            Libcore.os.setsockoptInt(fd, IPPROTO_TCP, TCP_NODELAY, booleanToInt((Boolean) value));
            return;
        case IoBridge.JAVA_MCAST_JOIN_GROUP:
        case IoBridge.JAVA_MCAST_LEAVE_GROUP:
        {
            StructGroupReq groupReq = (StructGroupReq) value;
            int level = (groupReq.gr_group instanceof Inet4Address) ? IPPROTO_IP : IPPROTO_IPV6;
            int op = (option == JAVA_MCAST_JOIN_GROUP) ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP;
            Libcore.os.setsockoptGroupReq(fd, level, op, groupReq);
            return;
        }
        default:
            throw new SocketException("Unknown socket option: " + option);
        }
    }

    /**
     * Wrapper for {@link Os#open(String, int, int)} that behaves similar to {@link java.io.File}.
     * When a {@link java.io.File} is opened and there is an error, it throws
     * {@link java.io.FileNotFoundException} regardless of what went wrong, when POSIX
     * {@link Os#open(String, int, int)} throws more grained exceptions of what went wrong.
     *
     * <p>Additionally, attempt to open directory using {@link java.io.File} is also error, however
     * POSIX {@link Os#open(String, int, int)} for read-only directories is not error.
     *
     * @see <a href="https://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>.
     *
     * @param path  path of the file to be opened
     * @param flags bitmask of the access, file creation and file status flags
     * @return {@link FileDescriptor} of an opened file
     * @throws FileNotFoundException if there was error opening file under {@code path}
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static FileDescriptor open(String path, int flags) throws FileNotFoundException {
        FileDescriptor fd = null;
        try {
            fd = Libcore.os.open(path, flags, 0666);
            // Posix open(2) fails with EISDIR only if you ask for write permission.
            // Java disallows reading directories too.
            if (S_ISDIR(Libcore.os.fstat(fd).st_mode)) {
                throw new ErrnoException("open", EISDIR);
            }
            return fd;
        } catch (ErrnoException errnoException) {
            try {
                if (fd != null) {
                    closeAndSignalBlockedThreads(fd);
                }
            } catch (IOException ignored) {
            }
            FileNotFoundException ex = new FileNotFoundException(path + ": " + errnoException.getMessage());
            ex.initCause(errnoException);
            throw ex;
        }
    }

    /**
     * Wrapper for {@link Os#read(FileDescriptor, byte[], int, int)} that behaves similar to
     * {@link java.io.FileInputStream#read(byte[], int, int)} and
     * {@link java.io.FileReader#read(char[], int, int)} which interpret reading at {@code EOF} as
     * error, when POSIX system call returns {@code 0} (and future reads return {@code -1}).
     *
     * <p>@see <a href="https://man7.org/linux/man-pages/man2/read.2.html">read(2)</a>.
     *
     * @param fd         file descriptor to read from
     * @param bytes      buffer to put data read from {@code fd}
     * @param byteOffset offset in {@code bytes} buffer to put read data at
     * @param byteCount  number of bytes to read from {@code fd}
     * @return           number of bytes read, if read operation was successful
     * @throws IOException if underlying system call returned error
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static int read(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
        ArrayUtils.throwsIfOutOfBounds(bytes.length, byteOffset, byteCount);
        if (byteCount == 0) {
            return 0;
        }
        try {
            int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount);
            if (readCount == 0) {
                return -1;
            }
            return readCount;
        } catch (ErrnoException errnoException) {
            if (errnoException.errno == EAGAIN) {
                // We return 0 rather than throw if we try to read from an empty non-blocking pipe.
                return 0;
            }
            throw errnoException.rethrowAsIOException();
        }
    }

    /**
     * Wrapper for {@link Os#write(FileDescriptor, byte[], int, int)} that behaves similar to
     * {@link java.io.FileOutputStream#write(byte[], int, int)} and
     * {@link java.io.FileWriter#write(char[], int, int)} which always either write all requested
     * bytes, or fail with error; as opposed to POSIX write, when the number of bytes written may
     * be less than {@code bytes}. This may happen, for example, if there is insufficient space on
     * the underlying  physical medium, or the {@code RLIMIT_FSIZE} resource limit is encountered,
     * or the call was interrupted by a signal handler after having written less than {@code bytes}
     * bytes.
     *
     * <p>@see <a href="https://man7.org/linux/man-pages/man2/write.2.html">write(2)</a>.
     *
     * @param fd         file descriptor to write to
     * @param bytes      buffer containing the data to be written
     * @param byteOffset offset in {@code bytes} buffer to read written data from
     * @param byteCount  number of bytes to write to {@code fd}
     * @throws IOException if underlying system call returned error
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static void write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws IOException {
        ArrayUtils.throwsIfOutOfBounds(bytes.length, byteOffset, byteCount);
        if (byteCount == 0) {
            return;
        }
        try {
            while (byteCount > 0) {
                int bytesWritten = Libcore.os.write(fd, bytes, byteOffset, byteCount);
                byteCount -= bytesWritten;
                byteOffset += bytesWritten;
            }
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsIOException();
        }
    }

    /**
     * Wrapper around {@link Os#sendto(FileDescriptor, byte[], int, int, int, InetAddress, int)}
     * that allows sending data over both TCP and UDP socket; handles
     * {@link android.system.OsConstants#EAGAIN} and {@link android.system.OsConstants#ECONNREFUSED}
     * and behaves similar to and behaves similar to
     * {@link java.net.DatagramSocket#send(DatagramPacket)} and
     * {@link Socket#getOutputStream()#write(FileDescriptor, byte[], int, int)}.
     *
     * <p>See {@link android.system.OsConstants} for available flags.
     *
     * <p>@see <a href="https://man7.org/linux/man-pages/man2/send.2.html">send(2)</a>.
     *
     * @param fd          {@link FileDescriptor} of the socket to send data over
     * @param bytes       byte buffer containing the data to be sent
     * @param byteOffset  offset in {@code bytes} at which data to be sent starts
     * @param byteCount   number of bytes to be sent
     * @param flags       bitwise OR of zero or more of flags, like {@link android.system.OsConstants#MSG_DONTROUTE}
     * @param inetAddress destination address
     * @param port        destination port
     * @return            number of bytes sent on success
     * @throws IOException if underlying system call returned error
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static int sendto(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, InetAddress inetAddress, int port) throws IOException {
        boolean isDatagram = (inetAddress != null);
        if (!isDatagram && byteCount <= 0) {
            return 0;
        }
        int result;
        try {
            result = Libcore.os.sendto(fd, bytes, byteOffset, byteCount, flags, inetAddress, port);
        } catch (ErrnoException errnoException) {
            result = maybeThrowAfterSendto(isDatagram, errnoException);
        }
        return result;
    }

    /** @hide */
    public static int sendto(FileDescriptor fd, ByteBuffer buffer, int flags, InetAddress inetAddress, int port) throws IOException {
        boolean isDatagram = (inetAddress != null);
        if (!isDatagram && buffer.remaining() == 0) {
            return 0;
        }
        int result;
        try {
            result = Libcore.os.sendto(fd, buffer, flags, inetAddress, port);
        } catch (ErrnoException errnoException) {
            result = maybeThrowAfterSendto(isDatagram, errnoException);
        }
        return result;
    }

    private static int maybeThrowAfterSendto(boolean isDatagram, ErrnoException errnoException)
            throws IOException {
        if (isDatagram) {
            if (errnoException.errno == ECONNREFUSED) {
                throw new PortUnreachableException("ICMP Port Unreachable");
            }
        } else {
            if (errnoException.errno == EAGAIN) {
                // We were asked to write to a non-blocking socket, but were told
                // it would block, so report "no bytes written".
                return 0;
            }
        }
        throw errnoException.rethrowAsIOException();
    }

    /**
     * Wrapper around {@link Os#recvfrom(FileDescriptor, byte[], int, int, int, InetSocketAddress)}
     * that receives a message from both TCP and UDP socket; handles
     * {@link android.system.OsConstants#EAGAIN} and {@link android.system.OsConstants#ECONNREFUSED}
     * and behaves similar to {@link java.net.DatagramSocket#receive(DatagramPacket)} and
     * {@link Socket#getInputStream()#recvfrom(boolean, FileDescriptor, byte[], int, int, int, DatagramPacket, boolean)}.
     *
     * <p>If {@code packet} is not {@code null}, and the underlying protocol provides the source
     * address of the message, that source address is placed in the {@code packet}.
     *
     * @see <a href="https://man7.org/linux/man-pages/man2/recv.2.html">recv(2)</a>.
     *
     * @param isRead      {@code true} if some data been read already from {@code fd}
     * @param fd          socket to receive data from
     * @param bytes       buffer to put data read from {@code fd}
     * @param byteOffset  offset in {@code bytes} buffer to put read data at
     * @param byteCount   number of bytes to read from {@code fd}
     * @param flags       bitwise OR of zero or more of flags, like {@link android.system.OsConstants#MSG_DONTROUTE}
     * @param packet      {@link DatagramPacket} to fill with source address
     * @param isConnected {@code true} if socket {@code fd} is connected
     * @return            number of bytes read, if read operation was successful
     * @throws IOException if underlying system call returned error
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static int recvfrom(boolean isRead, FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
        int result;
        try {
            InetSocketAddress srcAddress = packet != null ? new InetSocketAddress() : null;
            result = Libcore.os.recvfrom(fd, bytes, byteOffset, byteCount, flags, srcAddress);
            result = postRecvfrom(isRead, packet, srcAddress, result);
        } catch (ErrnoException errnoException) {
            result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
        }
        return result;
    }

    /** @hide */
    public static int recvfrom(boolean isRead, FileDescriptor fd, ByteBuffer buffer, int flags, DatagramPacket packet, boolean isConnected) throws IOException {
        int result;
        try {
            InetSocketAddress srcAddress = packet != null ? new InetSocketAddress() : null;
            result = Libcore.os.recvfrom(fd, buffer, flags, srcAddress);
            result = postRecvfrom(isRead, packet, srcAddress, result);
        } catch (ErrnoException errnoException) {
            result = maybeThrowAfterRecvfrom(isRead, isConnected, errnoException);
        }
        return result;
    }

    private static int postRecvfrom(boolean isRead, DatagramPacket packet, InetSocketAddress srcAddress, int byteCount) {
        if (isRead && byteCount == 0) {
            return -1;
        }
        if (packet != null) {
            packet.setReceivedLength(byteCount);
            packet.setPort(srcAddress.getPort());

            // packet.address should only be changed when it is different from srcAddress.
            if (!srcAddress.getAddress().equals(packet.getAddress())) {
                packet.setAddress(srcAddress.getAddress());
            }
        }
        return byteCount;
    }

    private static int maybeThrowAfterRecvfrom(boolean isRead, boolean isConnected, ErrnoException errnoException) throws SocketException, SocketTimeoutException {
        if (isRead) {
            if (errnoException.errno == EAGAIN) {
                return 0;
            } else {
                throw errnoException.rethrowAsSocketException();
            }
        } else {
            if (isConnected && errnoException.errno == ECONNREFUSED) {
                throw new PortUnreachableException("ICMP Port Unreachable", errnoException);
            } else if (errnoException.errno == EAGAIN) {
                SocketTimeoutException e = new SocketTimeoutException();
                e.initCause(errnoException);
                throw e;
            } else {
                throw errnoException.rethrowAsSocketException();
            }
        }
    }

    /**
     * Creates an endpoint for communication and returns a file descriptor that refers
     * to that endpoint.
     *
     * <p>The {@code domain} specifies a communication domain; this selects the protocol
     * family which will be used for communication, e.g. {@link android.system.OsConstants#AF_UNIX}
     * {@link android.system.OsConstants#AF_INET}.
     *
     * <p>The socket has the indicated type, which specifies the communication semantics,
     * e.g. {@link android.system.OsConstants#SOCK_STREAM} or
     * {@link android.system.OsConstants#SOCK_DGRAM}.
     *
     * <p>The protocol specifies a particular protocol to be used with the
     * socket. Normally only a single protocol exists to support a
     * particular socket type within a given protocol family, in which
     * case protocol can be specified as {@code 0}.
     *
     * @see <a href="https://man7.org/linux/man-pages/man2/socket.2.html">socket(2)</a>.
     *
     * @param domain   socket domain
     * @param type     socket type
     * @param protocol socket protocol
     * @return {@link FileDescriptor} of an opened socket
     * @throws SocketException if underlying system call returned error
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static FileDescriptor socket(int domain, int type, int protocol) throws SocketException {
        FileDescriptor fd;
        try {
            fd = Libcore.os.socket(domain, type, protocol);

            return fd;
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsSocketException();
        }
    }

    /**
     * Wait for some event on a file descriptor, blocks until the event happened or timeout period
     * passed. See poll(2) and @link{android.system.Os.Poll}.
     *
     * @throws SocketException if poll(2) fails.
     * @throws SocketTimeoutException if the event has not happened before timeout period has passed.
     *
     * @hide
     */
    public static void poll(FileDescriptor fd, int events, int timeout)
            throws SocketException, SocketTimeoutException {
        StructPollfd[] pollFds = new StructPollfd[]{ new StructPollfd() };
        pollFds[0].fd = fd;
        pollFds[0].events = (short) events;

        try {
            int ret = android.system.Os.poll(pollFds, timeout);
            if (ret == 0) {
                throw new SocketTimeoutException("Poll timed out");
            }
        } catch (ErrnoException e) {
            e.rethrowAsSocketException();
        }
    }

    /**
     * Returns the current address to which the socket {@code fd} is bound.
     *
     * @see <a href="https://man7.org/linux/man-pages/man2/getsockname.2.html">getsockname(2)</a>.
     *
     * @param fd socket to get the bounded address of
     * @return current address to which the socket {@code fd} is bound
     * @throws SocketException if {@code fd} is not currently bound to an {@link InetSocketAddress}
     *
     * @hide
     */
    @libcore.api.CorePlatformApi
    public static InetSocketAddress getLocalInetSocketAddress(FileDescriptor fd)
            throws SocketException {
        try {
            SocketAddress socketAddress = Libcore.os.getsockname(fd);
            // When a Socket is pending closure because socket.close() was called but other threads
            // are still using it, the FileDescriptor can be dup2'ed to an AF_UNIX one; see the
            // deferred close logic in PlainSocketImpl.socketClose0(true) for details.
            // If socketAddress is not the expected type then we assume that the socket is being
            // closed, so we throw a SocketException (just like in the case of an ErrnoException).
            // http://b/64209834
            if ((socketAddress != null) && !(socketAddress instanceof InetSocketAddress)) {
                throw new SocketException("Socket assumed to be pending closure: Expected sockname "
                        + "to be an InetSocketAddress, got " + socketAddress.getClass());
            }
            return (InetSocketAddress) socketAddress;
        } catch (ErrnoException errnoException) {
            throw errnoException.rethrowAsSocketException();
        }
    }
}