| /* |
| * 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.android.system; |
| |
| import android.system.ErrnoException; |
| import android.system.Int64Ref; |
| import android.system.NetlinkSocketAddress; |
| import android.system.Os; |
| import android.system.OsConstants; |
| import android.system.PacketSocketAddress; |
| import android.system.StructCmsghdr; |
| import android.system.StructMsghdr; |
| import android.system.StructRlimit; |
| import android.system.StructStat; |
| import android.system.StructTimespec; |
| import android.system.StructTimeval; |
| import android.system.StructUcred; |
| import android.system.UnixSocketAddress; |
| import android.system.VmSocketAddress; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.DatagramPacket; |
| import java.net.DatagramSocket; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.NetworkInterface; |
| import java.net.ServerSocket; |
| import java.net.SocketAddress; |
| import java.net.SocketException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.time.Duration; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import libcore.io.IoUtils; |
| import libcore.testing.io.TestIoUtils; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import static android.system.OsConstants.*; |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertThrows; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeNoException; |
| import static org.junit.Assume.assumeTrue; |
| |
| import jdk.internal.misc.Unsafe; |
| |
| @RunWith(JUnit4.class) |
| public class OsTest { |
| |
| @Test |
| public void testIsSocket() throws Exception { |
| File f = new File("/dev/null"); |
| FileInputStream fis = new FileInputStream(f); |
| assertFalse(S_ISSOCK(Os.fstat(fis.getFD()).st_mode)); |
| fis.close(); |
| |
| ServerSocket s = new ServerSocket(); |
| assertTrue(S_ISSOCK(Os.fstat(s.getImpl().getFD$()).st_mode)); |
| s.close(); |
| } |
| |
| @Test |
| public void testFcntlInt() throws Exception { |
| File f = File.createTempFile("OsTest", "tst"); |
| FileInputStream fis = null; |
| try { |
| fis = new FileInputStream(f); |
| Os.fcntlInt(fis.getFD(), F_SETFD, FD_CLOEXEC); |
| int flags = Os.fcntlVoid(fis.getFD(), F_GETFD); |
| assertTrue((flags & FD_CLOEXEC) != 0); |
| } finally { |
| TestIoUtils.closeQuietly(fis); |
| f.delete(); |
| } |
| } |
| |
| @Test |
| public void testFcntlInt_udpSocket() throws Exception { |
| final FileDescriptor fd = Os.socket(AF_INET, SOCK_DGRAM, 0); |
| try { |
| assertEquals(0, (Os.fcntlVoid(fd, F_GETFL) & O_NONBLOCK)); |
| |
| // Verify that we can set file descriptor flags on sockets |
| Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM | O_NONBLOCK); |
| assertTrue((Os.fcntlVoid(fd, F_GETFL) & O_NONBLOCK) != 0); |
| |
| // Check that we can turn it off also. |
| Os.fcntlInt(fd, F_SETFL, SOCK_DGRAM); |
| assertEquals(0, (Os.fcntlVoid(fd, F_GETFL) & O_NONBLOCK)); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void testFcntlInt_invalidCmd() throws Exception { |
| final FileDescriptor fd = Os.socket(AF_INET, SOCK_DGRAM, 0); |
| try { |
| final int unknownCmd = -1; |
| Os.fcntlInt(fd, unknownCmd, 0); |
| fail("Expected failure due to invalid cmd"); |
| } catch (ErrnoException expected) { |
| assertEquals(EINVAL, expected.errno); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void testFcntlInt_nullFd() { |
| try { |
| Os.fcntlInt(null, F_SETFL, O_NONBLOCK); |
| fail("Expected failure due to null file descriptor"); |
| } catch (ErrnoException expected) { |
| assertEquals(EBADF, expected.errno); |
| } |
| } |
| |
| @Test |
| public void testUnixDomainSockets_in_file_system() throws Exception { |
| String path = System.getProperty("java.io.tmpdir") + "/test_unix_socket"; |
| new File(path).delete(); |
| checkUnixDomainSocket(UnixSocketAddress.createFileSystem(path), false); |
| } |
| |
| @Test |
| public void testUnixDomainSocket_abstract_name() throws Exception { |
| // Linux treats a sun_path starting with a NUL byte as an abstract name. See unix(7). |
| checkUnixDomainSocket(UnixSocketAddress.createAbstract("/abstract_name_unix_socket"), true); |
| } |
| |
| @Test |
| public void testUnixDomainSocket_unnamed() throws Exception { |
| final FileDescriptor fd = Os.socket(AF_UNIX, SOCK_STREAM, 0); |
| // unix(7) says an unbound socket is unnamed. |
| checkNoSockName(fd); |
| Os.close(fd); |
| } |
| |
| private void checkUnixDomainSocket(final UnixSocketAddress address, final boolean isAbstract) |
| throws Exception { |
| final FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0); |
| Os.bind(serverFd, address); |
| Os.listen(serverFd, 5); |
| |
| checkSockName(serverFd, isAbstract, address); |
| |
| Thread server = new Thread(new Runnable() { |
| public void run() { |
| try { |
| UnixSocketAddress peerAddress = UnixSocketAddress.createUnnamed(); |
| FileDescriptor clientFd = Os.accept(serverFd, peerAddress); |
| checkSockName(clientFd, isAbstract, address); |
| checkNoName(peerAddress); |
| |
| checkNoPeerName(clientFd); |
| |
| StructUcred credentials = Os.getsockoptUcred(clientFd, SOL_SOCKET, SO_PEERCRED); |
| assertEquals(Os.getpid(), credentials.pid); |
| assertEquals(Os.getuid(), credentials.uid); |
| assertEquals(Os.getgid(), credentials.gid); |
| |
| byte[] request = new byte[256]; |
| Os.read(clientFd, request, 0, request.length); |
| |
| String s = new String(request, StandardCharsets.UTF_8); |
| byte[] response = s.toUpperCase(Locale.ROOT).getBytes(StandardCharsets.UTF_8); |
| Os.write(clientFd, response, 0, response.length); |
| |
| Os.close(clientFd); |
| } catch (Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| }); |
| server.start(); |
| |
| FileDescriptor clientFd = Os.socket(AF_UNIX, SOCK_STREAM, 0); |
| |
| Os.connect(clientFd, address); |
| checkNoSockName(clientFd); |
| |
| String string = "hello, world!"; |
| |
| byte[] request = string.getBytes(StandardCharsets.UTF_8); |
| assertEquals(request.length, Os.write(clientFd, request, 0, request.length)); |
| |
| byte[] response = new byte[request.length]; |
| assertEquals(response.length, Os.read(clientFd, response, 0, response.length)); |
| |
| assertEquals(string.toUpperCase(Locale.ROOT), new String(response, StandardCharsets.UTF_8)); |
| |
| Os.close(clientFd); |
| } |
| |
| private static void checkSockName(FileDescriptor fd, boolean isAbstract, |
| UnixSocketAddress address) throws Exception { |
| UnixSocketAddress isa = (UnixSocketAddress) Os.getsockname(fd); |
| assertEquals(address, isa); |
| if (isAbstract) { |
| assertEquals(0, isa.getSunPath()[0]); |
| } |
| } |
| |
| private void checkNoName(UnixSocketAddress usa) { |
| assertEquals(0, usa.getSunPath().length); |
| } |
| |
| private void checkNoPeerName(FileDescriptor fd) throws Exception { |
| checkNoName((UnixSocketAddress) Os.getpeername(fd)); |
| } |
| |
| private void checkNoSockName(FileDescriptor fd) throws Exception { |
| checkNoName((UnixSocketAddress) Os.getsockname(fd)); |
| } |
| |
| @Test |
| public void test_strsignal() { |
| assertEquals("Killed", Os.strsignal(9)); |
| assertEquals("Unknown signal -1", Os.strsignal(-1)); |
| } |
| |
| @Test |
| public void test_byteBufferPositions_write_pwrite() throws Exception { |
| FileOutputStream fos = new FileOutputStream(new File("/dev/null")); |
| FileDescriptor fd = fos.getFD(); |
| final byte[] contents = "goodbye, cruel world".getBytes(StandardCharsets.US_ASCII); |
| ByteBuffer byteBuffer = ByteBuffer.wrap(contents); |
| |
| byteBuffer.position(0); |
| int written = Os.write(fd, byteBuffer); |
| assertTrue(written > 0); |
| assertEquals(written, byteBuffer.position()); |
| |
| byteBuffer.position(4); |
| written = Os.write(fd, byteBuffer); |
| assertTrue(written > 0); |
| assertEquals(written + 4, byteBuffer.position()); |
| |
| byteBuffer.position(0); |
| written = Os.pwrite(fd, byteBuffer, 64 /* offset */); |
| assertTrue(written > 0); |
| assertEquals(written, byteBuffer.position()); |
| |
| byteBuffer.position(4); |
| written = Os.pwrite(fd, byteBuffer, 64 /* offset */); |
| assertTrue(written > 0); |
| assertEquals(written + 4, byteBuffer.position()); |
| |
| fos.close(); |
| } |
| |
| @Test |
| public void test_byteBufferPositions_read_pread() throws Exception { |
| FileInputStream fis = new FileInputStream(new File("/dev/zero")); |
| FileDescriptor fd = fis.getFD(); |
| ByteBuffer byteBuffer = ByteBuffer.allocate(64); |
| |
| byteBuffer.position(0); |
| int read = Os.read(fd, byteBuffer); |
| assertTrue(read > 0); |
| assertEquals(read, byteBuffer.position()); |
| |
| byteBuffer.position(4); |
| read = Os.read(fd, byteBuffer); |
| assertTrue(read > 0); |
| assertEquals(read + 4, byteBuffer.position()); |
| |
| byteBuffer.position(0); |
| read = Os.pread(fd, byteBuffer, 64 /* offset */); |
| assertTrue(read > 0); |
| assertEquals(read, byteBuffer.position()); |
| |
| byteBuffer.position(4); |
| read = Os.pread(fd, byteBuffer, 64 /* offset */); |
| assertTrue(read > 0); |
| assertEquals(read + 4, byteBuffer.position()); |
| |
| fis.close(); |
| } |
| |
| private static void checkByteBufferPositions_sendto_recvfrom( |
| int family, InetAddress loopback) throws Exception { |
| final FileDescriptor serverFd = Os.socket(family, SOCK_STREAM, 0); |
| Os.bind(serverFd, loopback, 0); |
| Os.listen(serverFd, 5); |
| |
| InetSocketAddress address = (InetSocketAddress) Os.getsockname(serverFd); |
| |
| final Thread server = new Thread(() -> { |
| try { |
| InetSocketAddress peerAddress = new InetSocketAddress(); |
| FileDescriptor clientFd = Os.accept(serverFd, peerAddress); |
| |
| // Attempt to receive a maximum of 24 bytes from the client, and then |
| // close the connection. |
| ByteBuffer buffer = ByteBuffer.allocate(16); |
| int received = Os.recvfrom(clientFd, buffer, 0, null); |
| assertTrue(received > 0); |
| assertEquals(received, buffer.position()); |
| |
| ByteBuffer buffer2 = ByteBuffer.allocate(16); |
| buffer2.position(8); |
| received = Os.recvfrom(clientFd, buffer2, 0, null); |
| assertTrue(received > 0); |
| assertEquals(received + 8, buffer.position()); |
| |
| Os.close(clientFd); |
| } catch (Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| }); |
| |
| server.start(); |
| |
| FileDescriptor clientFd = Os.socket(family, SOCK_STREAM, 0); |
| Os.connect(clientFd, address.getAddress(), address.getPort()); |
| |
| final byte[] bytes = "good bye, cruel black hole with fancy distortion" |
| .getBytes(StandardCharsets.US_ASCII); |
| assertTrue(bytes.length > 24); |
| |
| ByteBuffer input = ByteBuffer.wrap(bytes); |
| input.position(0); |
| input.limit(16); |
| |
| int sent = Os.sendto(clientFd, input, 0, address.getAddress(), address.getPort()); |
| assertTrue(sent > 0); |
| assertEquals(sent, input.position()); |
| |
| input.position(16); |
| input.limit(24); |
| sent = Os.sendto(clientFd, input, 0, address.getAddress(), address.getPort()); |
| assertTrue(sent > 0); |
| assertEquals(sent + 16, input.position()); |
| |
| Os.close(clientFd); |
| } |
| |
| private interface ExceptionalRunnable { |
| void run() throws Exception; |
| } |
| |
| /** |
| * Expects that the given Runnable will throw an exception of the specified class. If the class |
| * is ErrnoException, and expectedErrno is non-null, also checks that the errno is equal to |
| * expectedErrno. |
| */ |
| private static void expectException(ExceptionalRunnable r, Class<? extends Exception> exClass, |
| Integer expectedErrno, String msg) { |
| try { |
| r.run(); |
| fail(msg + " did not throw exception"); |
| } catch (Exception e) { |
| assertEquals(msg + " threw unexpected exception", exClass, e.getClass()); |
| |
| if (expectedErrno != null) { |
| if (e instanceof ErrnoException) { |
| assertEquals(msg + "threw ErrnoException with unexpected error number", |
| (int) expectedErrno, ((ErrnoException) e).errno); |
| } else { |
| fail("Can only pass expectedErrno when expecting ErrnoException"); |
| } |
| } |
| |
| } |
| } |
| |
| private static void expectBindException(FileDescriptor socket, SocketAddress addr, |
| Class<? extends Exception> exClass, Integer expectedErrno) { |
| String msg = String.format("bind(%s, %s)", socket, addr); |
| expectException(() -> { |
| Os.bind(socket, addr); |
| }, exClass, expectedErrno, msg); |
| } |
| |
| private static void expectConnectException(FileDescriptor socket, SocketAddress addr, |
| Class<? extends Exception> exClass, Integer expectedErrno) { |
| String msg = String.format("connect(%s, %s)", socket, addr); |
| expectException(() -> { |
| Os.connect(socket, addr); |
| }, exClass, expectedErrno, msg); |
| } |
| |
| private static void expectSendtoException(FileDescriptor socket, SocketAddress addr, |
| Integer expectedErrno) { |
| String msg = String.format("sendto(%s, %s)", socket, addr); |
| byte[] packet = new byte[42]; |
| expectException(() -> { |
| Os.sendto(socket, packet, 0, packet.length, 0, addr); |
| }, |
| ErrnoException.class, expectedErrno, msg); |
| } |
| |
| private static void expectBindConnectSendtoSuccess(FileDescriptor socket, String socketDesc, |
| SocketAddress addr) { |
| String msg = socketDesc + " socket to " + addr.toString(); |
| |
| try { |
| try { |
| // Expect that bind throws when any of its arguments are null. |
| expectBindException(null, addr, ErrnoException.class, EBADF); |
| expectBindException(socket, null, NullPointerException.class, null); |
| expectBindException(null, null, NullPointerException.class, null); |
| |
| // Expect bind to succeed. |
| Os.bind(socket, addr); |
| |
| // Find out which port we're actually bound to, and use that in subsequent connect() |
| // and send() calls. We can't send to addr because that has a port of 0. |
| if (addr instanceof InetSocketAddress) { |
| InetSocketAddress addrISA = (InetSocketAddress) addr; |
| InetSocketAddress socknameISA = (InetSocketAddress) Os.getsockname(socket); |
| |
| assertEquals(addrISA.getAddress(), socknameISA.getAddress()); |
| assertEquals(0, addrISA.getPort()); |
| assertNotEquals(0, socknameISA.getPort()); |
| addr = socknameISA; |
| } |
| |
| // Expect sendto with a null address to throw because the socket is not connected, |
| // but to succeed with a non-null address. |
| byte[] packet = new byte[42]; |
| Os.sendto(socket, packet, 0, packet.length, 0, addr); |
| // UNIX and IP sockets return different errors for this operation, so we can't check |
| // errno. |
| expectSendtoException(socket, null, null); |
| expectSendtoException(null, null, EBADF); |
| expectSendtoException(null, addr, EBADF); |
| |
| // Expect that connect throws when any of its arguments are null. |
| expectConnectException(null, addr, ErrnoException.class, EBADF); |
| expectConnectException(socket, null, NullPointerException.class, null); |
| expectConnectException(null, null, NullPointerException.class, null); |
| |
| // Expect connect to succeed. |
| Os.connect(socket, addr); |
| assertEquals(Os.getsockname(socket), Os.getpeername(socket)); |
| |
| // Expect sendto to succeed both when given an explicit address and a null address. |
| Os.sendto(socket, packet, 0, packet.length, 0, addr); |
| Os.sendto(socket, packet, 0, packet.length, 0, null); |
| } catch (SocketException | ErrnoException e) { |
| fail("Expected success for " + msg + ", but got: " + e); |
| } |
| |
| } finally { |
| IoUtils.closeQuietly(socket); |
| } |
| } |
| |
| private static void expectBindConnectSendtoErrno(int bindErrno, int connectErrno, |
| int sendtoErrno, FileDescriptor socket, String socketDesc, SocketAddress addr) { |
| try { |
| |
| // Expect bind to fail with bindErrno. |
| String msg = "bind " + socketDesc + " socket to " + addr.toString(); |
| try { |
| Os.bind(socket, addr); |
| fail("Expected to fail " + msg); |
| } catch (ErrnoException e) { |
| assertEquals("Expected errno " + bindErrno + " " + msg, bindErrno, e.errno); |
| } catch (SocketException e) { |
| fail("Unexpected SocketException " + msg); |
| } |
| |
| // Expect connect to fail with connectErrno. |
| msg = "connect " + socketDesc + " socket to " + addr.toString(); |
| try { |
| Os.connect(socket, addr); |
| fail("Expected to fail " + msg); |
| } catch (ErrnoException e) { |
| assertEquals("Expected errno " + connectErrno + " " + msg, connectErrno, e.errno); |
| } catch (SocketException e) { |
| fail("Unexpected SocketException " + msg); |
| } |
| |
| // Expect sendto to fail with sendtoErrno. |
| byte[] packet = new byte[42]; |
| msg = "sendto " + socketDesc + " socket to " + addr.toString(); |
| try { |
| Os.sendto(socket, packet, 0, packet.length, 0, addr); |
| fail("Expected to fail " + msg); |
| } catch (ErrnoException e) { |
| assertEquals("Expected errno " + sendtoErrno + " " + msg, sendtoErrno, e.errno); |
| } catch (SocketException e) { |
| fail("Unexpected SocketException " + msg); |
| } |
| |
| } finally { |
| // No matter what happened, close the socket. |
| IoUtils.closeQuietly(socket); |
| } |
| } |
| |
| private FileDescriptor makeIpv4Socket() throws Exception { |
| return Os.socket(AF_INET, SOCK_DGRAM, 0); |
| } |
| |
| private FileDescriptor makeIpv6Socket() throws Exception { |
| return Os.socket(AF_INET6, SOCK_DGRAM, 0); |
| } |
| |
| private FileDescriptor makeUnixSocket() throws Exception { |
| return Os.socket(AF_UNIX, SOCK_DGRAM, 0); |
| } |
| |
| @Test |
| public void testCrossFamilyBindConnectSendto() throws Exception { |
| SocketAddress addrIpv4 = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); |
| SocketAddress addrIpv6 = new InetSocketAddress(InetAddress.getByName("::1"), 0); |
| SocketAddress addrUnix = UnixSocketAddress.createAbstract("/abstract_name_unix_socket"); |
| |
| expectBindConnectSendtoSuccess(makeIpv4Socket(), "ipv4", addrIpv4); |
| expectBindConnectSendtoErrno(EAFNOSUPPORT, EAFNOSUPPORT, EAFNOSUPPORT, |
| makeIpv4Socket(), "ipv4", addrIpv6); |
| expectBindConnectSendtoErrno(EAFNOSUPPORT, EAFNOSUPPORT, EAFNOSUPPORT, |
| makeIpv4Socket(), "ipv4", addrUnix); |
| |
| // This succeeds because Java always uses dual-stack sockets and all InetAddress and |
| // InetSocketAddress objects represent IPv4 addresses using IPv4-mapped IPv6 addresses. |
| expectBindConnectSendtoSuccess(makeIpv6Socket(), "ipv6", addrIpv4); |
| expectBindConnectSendtoSuccess(makeIpv6Socket(), "ipv6", addrIpv6); |
| expectBindConnectSendtoErrno(EAFNOSUPPORT, EAFNOSUPPORT, EINVAL, |
| makeIpv6Socket(), "ipv6", addrUnix); |
| |
| expectBindConnectSendtoErrno(EINVAL, EINVAL, EINVAL, |
| makeUnixSocket(), "unix", addrIpv4); |
| expectBindConnectSendtoErrno(EINVAL, EINVAL, EINVAL, |
| makeUnixSocket(), "unix", addrIpv6); |
| expectBindConnectSendtoSuccess(makeUnixSocket(), "unix", addrUnix); |
| } |
| |
| @Test |
| public void testUnknownSocketAddressSubclass() throws Exception { |
| class MySocketAddress extends SocketAddress { |
| |
| } |
| MySocketAddress myaddr = new MySocketAddress(); |
| |
| for (int family : new int[] { AF_INET, AF_INET6, AF_NETLINK }) { |
| FileDescriptor s = Os.socket(family, SOCK_DGRAM, 0); |
| try { |
| |
| try { |
| Os.bind(s, myaddr); |
| fail("bind socket family " + family |
| + " to unknown SocketAddress subclass succeeded"); |
| } catch (UnsupportedOperationException expected) { |
| } |
| |
| try { |
| Os.connect(s, myaddr); |
| fail("connect socket family " + family |
| + " to unknown SocketAddress subclass succeeded"); |
| } catch (UnsupportedOperationException expected) { |
| } |
| |
| byte[] msg = new byte[42]; |
| try { |
| Os.sendto(s, msg, 0, msg.length, 0, myaddr); |
| fail("sendto socket family " + family |
| + " to unknown SocketAddress subclass succeeded"); |
| } catch (UnsupportedOperationException expected) { |
| } |
| |
| } finally { |
| Os.close(s); |
| } |
| } |
| } |
| |
| @Test |
| public void test_NetlinkSocket() throws Exception { |
| FileDescriptor nlSocket = Os.socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); |
| try { |
| Os.bind(nlSocket, new NetlinkSocketAddress()); |
| // Non-system processes should not be allowed to bind() to NETLINK_ROUTE sockets. |
| // http://b/141455849 |
| fail("bind() on NETLINK_ROUTE socket succeeded"); |
| } catch (ErrnoException expectedException) { |
| assertEquals(expectedException.errno, EACCES); |
| } |
| |
| NetlinkSocketAddress nlKernel = new NetlinkSocketAddress(); |
| Os.connect(nlSocket, nlKernel); |
| NetlinkSocketAddress nlPeer = (NetlinkSocketAddress) Os.getpeername(nlSocket); |
| assertEquals(0, nlPeer.getPortId()); |
| assertEquals(0, nlPeer.getGroupsMask()); |
| Os.close(nlSocket); |
| } |
| |
| // This test is excluded from CTS via the knownfailures.txt because it requires extra |
| // permissions not available in CTS. To run it you have to use an -eng build and use a tool like |
| // vogar that runs the Android runtime as a privileged user. |
| @Test |
| public void test_PacketSocketAddress() throws Exception { |
| NetworkInterface lo = NetworkInterface.getByName("lo"); |
| assertNotNull(lo); |
| FileDescriptor fd = Os.socket(AF_PACKET, SOCK_DGRAM, ETH_P_IPV6); |
| PacketSocketAddress addr = |
| new PacketSocketAddress(ETH_P_IPV6, lo.getIndex(), null /* sll_addr */); |
| Os.bind(fd, addr); |
| |
| PacketSocketAddress bound = (PacketSocketAddress) Os.getsockname(fd); |
| assertEquals(ETH_P_IPV6, bound.sll_protocol); |
| assertEquals(lo.getIndex(), bound.sll_ifindex); |
| assertEquals(ARPHRD_LOOPBACK, bound.sll_hatype); |
| assertEquals(0, bound.sll_pkttype); |
| |
| // The loopback address is ETH_ALEN bytes long and is all zeros. |
| // http://lxr.free-electrons.com/source/drivers/net/loopback.c?v=3.10#L167 |
| assertNotNull(bound.sll_addr); |
| assertEquals(6, bound.sll_addr.length); |
| for (int i = 0; i < 6; i++) { |
| assertEquals(0, bound.sll_addr[i]); |
| } |
| |
| // The following checks that the packet socket address was constructed correctly in a form |
| // that the kernel understands. If the address is correct, the bind should result in a |
| // socket that is listening only for IPv6 packets, and only on loopback. |
| |
| // Send an IPv4 packet on loopback. |
| // We send ourselves an IPv4 packet first. If we don't receive it, that (with high |
| // probability) ensures that the packet socket does not see IPv4 packets. |
| try (DatagramSocket s = new DatagramSocket()) { |
| byte[] packet = new byte[64]; |
| s.send(new DatagramPacket(packet, 0, packet.length, Inet4Address.LOOPBACK, |
| 53 /* arbitrary port */)); |
| } |
| |
| // Send an IPv6 packet on loopback. |
| // Sending ourselves an IPv6 packet should cause the socket to receive a packet. |
| // The idea is that if the code gets sll_protocol wrong, then the packet socket will receive |
| // no packets and the test will fail. |
| try (DatagramSocket s = new DatagramSocket()) { |
| byte[] packet = new byte[64]; |
| s.send(new DatagramPacket(packet, 0, packet.length, Inet6Address.LOOPBACK, |
| 53 /* arbitrary port */)); |
| } |
| |
| // Check that the socket associated with fd has received an IPv6 packet, not necessarily the |
| // UDP one we sent above. IPv6 packets always begin with the nibble 6. If we get anything |
| // else it means we're catching non-IPv6 or non-loopback packets unexpectedly. Since the |
| // socket is not discriminating it may catch packets unrelated to this test from things |
| // happening on the device at the same time, so we can't assert too much about the received |
| // packet, i.e. no length / content check. |
| { |
| byte[] receivedPacket = new byte[4096]; |
| Os.read(fd, receivedPacket, 0, receivedPacket.length); |
| assertEquals(6, (receivedPacket[0] & 0xf0) >> 4); |
| |
| byte[] sourceAddress = getIPv6AddressBytesAtOffset(receivedPacket, 8); |
| assertArrayEquals(Inet6Address.LOOPBACK.getAddress(), sourceAddress); |
| |
| byte[] destAddress = getIPv6AddressBytesAtOffset(receivedPacket, 24); |
| assertArrayEquals(Inet6Address.LOOPBACK.getAddress(), destAddress); |
| } |
| |
| Os.close(fd); |
| } |
| |
| @Test |
| public void test_VmSocketAddress() { |
| try { |
| final VmSocketAddress addr = new VmSocketAddress(111, 222); |
| assertEquals(111, addr.getSvmPort()); |
| assertEquals(222, addr.getSvmCid()); |
| } catch (UnsupportedOperationException ignore) { |
| assumeNoException(ignore); // the platform does not support virtio-vsock |
| } |
| } |
| |
| private static Thread createVmSocketEchoServer(final FileDescriptor serverFd) { |
| return new Thread(new Runnable() { |
| public void run() { |
| final VmSocketAddress peer = |
| new VmSocketAddress(VMADDR_PORT_ANY, VMADDR_CID_ANY); |
| |
| try { |
| final FileDescriptor clientFd = Os.accept(serverFd, peer); |
| try { |
| final byte[] requestBuf = new byte[256]; |
| final int len = Os.read(clientFd, requestBuf, 0, requestBuf.length); |
| final String request = |
| new String(requestBuf, 0, len, StandardCharsets.UTF_8); |
| final byte[] responseBuf = |
| request.toUpperCase(Locale.ROOT).getBytes(StandardCharsets.UTF_8); |
| Os.write(clientFd, responseBuf, 0, responseBuf.length); |
| } finally { |
| Os.close(clientFd); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| }); |
| } |
| |
| @Test |
| public void test_VmSocket() throws Exception { |
| try { |
| final VmSocketAddress serverAddr = new VmSocketAddress(12345, VMADDR_CID_LOCAL); |
| |
| final FileDescriptor serverFd = Os.socket(AF_VSOCK, SOCK_STREAM, 0); |
| |
| try { |
| Os.bind(serverFd, serverAddr); |
| Os.listen(serverFd, 3); |
| |
| final Thread server = createVmSocketEchoServer(serverFd); |
| server.start(); |
| |
| final FileDescriptor clientFd = Os.socket(AF_VSOCK, SOCK_STREAM, 0); |
| try { |
| Os.connect(clientFd, serverAddr); |
| |
| final String request = "hello, world!"; |
| final byte[] requestBuf = request.getBytes(StandardCharsets.UTF_8); |
| |
| assertEquals(requestBuf.length, |
| Os.write(clientFd, requestBuf, 0, requestBuf.length)); |
| |
| final byte[] responseBuf = new byte[requestBuf.length]; |
| assertEquals(responseBuf.length, |
| Os.read(clientFd, responseBuf, 0, responseBuf.length)); |
| |
| final String response = new String(responseBuf, StandardCharsets.UTF_8); |
| |
| assertEquals(request.toUpperCase(Locale.ROOT), response); |
| } finally { |
| Os.close(clientFd); |
| } |
| } finally { |
| Os.close(serverFd); |
| } |
| } catch (UnsupportedOperationException ignore) { |
| assumeNoException(ignore); // the platform does not support virtio-vsock |
| } catch (ErrnoException e) { |
| // the platform does not support vsock |
| assumeTrue(e.errno != EAFNOSUPPORT && e.errno != EACCES); |
| throw e; |
| } |
| } |
| |
| private static byte[] getIPv6AddressBytesAtOffset(byte[] packet, int offsetIndex) { |
| byte[] address = new byte[16]; |
| System.arraycopy(packet, offsetIndex, address, 0, 16); |
| return address; |
| } |
| |
| @Test |
| public void test_byteBufferPositions_sendto_recvfrom_af_inet() throws Exception { |
| checkByteBufferPositions_sendto_recvfrom(AF_INET, InetAddress.getByName("127.0.0.1")); |
| } |
| |
| @Test |
| public void test_byteBufferPositions_sendto_recvfrom_af_inet6() throws Exception { |
| checkByteBufferPositions_sendto_recvfrom(AF_INET6, InetAddress.getByName("::1")); |
| } |
| |
| private void checkSendToSocketAddress(int family, InetAddress loopback) throws Exception { |
| FileDescriptor recvFd = Os.socket(family, SOCK_DGRAM, 0); |
| Os.bind(recvFd, loopback, 0); |
| StructTimeval tv = StructTimeval.fromMillis(20); |
| Os.setsockoptTimeval(recvFd, SOL_SOCKET, SO_RCVTIMEO, tv); |
| |
| InetSocketAddress to = ((InetSocketAddress) Os.getsockname(recvFd)); |
| FileDescriptor sendFd = Os.socket(family, SOCK_DGRAM, 0); |
| byte[] msg = ("Hello, I'm going to a socket address: " + to.toString()).getBytes( |
| StandardCharsets.UTF_8); |
| int len = msg.length; |
| |
| assertEquals(len, Os.sendto(sendFd, msg, 0, len, 0, to)); |
| byte[] received = new byte[msg.length + 42]; |
| InetSocketAddress from = new InetSocketAddress(); |
| assertEquals(len, Os.recvfrom(recvFd, received, 0, received.length, 0, from)); |
| assertEquals(loopback, from.getAddress()); |
| } |
| |
| @Test |
| public void test_sendtoSocketAddress_af_inet() throws Exception { |
| checkSendToSocketAddress(AF_INET, InetAddress.getByName("127.0.0.1")); |
| } |
| |
| @Test |
| public void test_sendtoSocketAddress_af_inet6() throws Exception { |
| checkSendToSocketAddress(AF_INET6, InetAddress.getByName("::1")); |
| } |
| |
| private static short asShort(StructCmsghdr cmsg) { |
| ByteBuffer buf = ByteBuffer.wrap(cmsg.cmsg_data).order(ByteOrder.nativeOrder()); |
| assertEquals(Short.BYTES, buf.capacity()); |
| return buf.getShort(); |
| } |
| |
| private static int asInt(StructCmsghdr cmsg) { |
| ByteBuffer buf = ByteBuffer.wrap(cmsg.cmsg_data).order(ByteOrder.nativeOrder()); |
| assertEquals(Integer.BYTES, buf.capacity()); |
| return buf.getInt(); |
| } |
| |
| @Test |
| public void test_StructCmsgHdrConstructors() throws Exception { |
| final StructCmsghdr cmsg1 = new StructCmsghdr(1, 2, (short) 32005); |
| assertEquals(1, cmsg1.cmsg_level); |
| assertEquals(2, cmsg1.cmsg_type); |
| assertEquals(32005, asShort(cmsg1)); |
| |
| ByteBuffer buf = ByteBuffer.allocate(Short.BYTES); |
| buf.order(ByteOrder.nativeOrder()); |
| buf.putShort((short) 32005); |
| assertArrayEquals(cmsg1.cmsg_data, buf.array()); |
| |
| buf = ByteBuffer.allocate(Integer.BYTES); |
| buf.order(ByteOrder.nativeOrder()); |
| buf.putInt(1000042); |
| |
| final StructCmsghdr cmsg2 = new StructCmsghdr(456789, 123456, buf.array()); |
| assertEquals(456789, cmsg2.cmsg_level); |
| assertEquals(123456, cmsg2.cmsg_type); |
| assertEquals(1000042, asInt(cmsg2)); |
| assertArrayEquals(buf.array(), cmsg2.cmsg_data); |
| } |
| |
| /* |
| * Test case for sendmsg with/without GSO in loopback iface, |
| * recvmsg/gro would not happen since in loopback |
| */ |
| private void checkSendmsgSocketAddress(int family, InetSocketAddress loopbackAddr, |
| StructMsghdr sendmsgHdr, StructMsghdr recvmsgHdr, int sendSize) throws Exception { |
| |
| FileDescriptor sendFd = Os.socket(family, SOCK_DGRAM, 0); |
| FileDescriptor recvFd = Os.socket(family, SOCK_DGRAM, 0); |
| int rc = 0; |
| |
| //recvmsg cleanup data |
| if (loopbackAddr.getAddress() instanceof Inet6Address) { |
| Os.bind(recvFd, Inet6Address.ANY, loopbackAddr.getPort()); |
| } else { |
| Os.bind(recvFd, Inet4Address.ANY, loopbackAddr.getPort()); |
| } |
| |
| StructTimeval tv = StructTimeval.fromMillis(20); |
| Os.setsockoptTimeval(recvFd, SOL_SOCKET, SO_RCVTIMEO, tv); |
| Os.setsockoptInt(recvFd, IPPROTO_UDP, UDP_GRO, 1); //enable GRO |
| Os.setsockoptInt(recvFd, SOL_SOCKET, SO_RCVBUF, 1024 * 1024); |
| |
| try { |
| assertEquals(sendSize, Os.sendmsg(sendFd, sendmsgHdr, 0)); |
| rc = 0; |
| do { |
| int temp_rc = Os.recvmsg(recvFd, recvmsgHdr, OsConstants.MSG_TRUNC); |
| rc += temp_rc; |
| if (recvmsgHdr.msg_control != null && recvmsgHdr.msg_control.length > 0) { |
| byte[] sendCmsgByte = sendmsgHdr.msg_control[0].cmsg_data; |
| byte[] recvCmsgByte = recvmsgHdr.msg_control[0].cmsg_data; |
| /* Note: |
| * GSO: is set with Short(2Byte) values; |
| * GRO: IP stack return with Int(4Bytes) value; |
| */ |
| assertEquals( |
| ByteBuffer.wrap(sendCmsgByte).order( |
| ByteOrder.nativeOrder()).getShort(0), |
| ByteBuffer.wrap(recvCmsgByte).order( |
| ByteOrder.nativeOrder()).getInt(0)); |
| } |
| |
| recvmsgHdr = new StructMsghdr(recvmsgHdr.msg_name, recvmsgHdr.msg_iov, |
| null, |
| recvmsgHdr.msg_flags); |
| }while(rc < sendSize); |
| } finally { |
| Os.close(sendFd); |
| Os.close(recvFd); |
| } |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet_4K() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| StructCmsghdr[] cmsg = new StructCmsghdr[1]; |
| cmsg[0] = new StructCmsghdr(SOL_UDP, UDP_SEGMENT, (short) 1400); |
| |
| //sendmsg/recvmsg with 1*4K ByteBuffer |
| ByteBuffer[] bufferArray = new ByteBuffer[1]; |
| ByteBuffer[] bufferArrayRecv = new ByteBuffer[1]; |
| bufferArray[0] = ByteBuffer.allocate(4096); |
| bufferArrayRecv[0] = ByteBuffer.allocate(4096); |
| |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, |
| bufferArray, |
| cmsg, 0); |
| StructMsghdr recvmsgHdr = new StructMsghdr(new InetSocketAddress(), |
| bufferArrayRecv, |
| null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet6_4K() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("::1"), 10234); |
| StructCmsghdr[] cmsg = new StructCmsghdr[1]; |
| cmsg[0] = new StructCmsghdr(SOL_UDP, UDP_SEGMENT, (short) 1400); |
| |
| //sendmsg/recvmsg with 1*4K ByteBuffer |
| ByteBuffer[] bufferArray = new ByteBuffer[1]; |
| ByteBuffer[] bufferArrayRecv = new ByteBuffer[1]; |
| bufferArray[0] = ByteBuffer.allocate(4096); |
| bufferArrayRecv[0] = ByteBuffer.allocate(4096); |
| |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, |
| bufferArray, |
| cmsg, 0); |
| StructMsghdr recvmsgHdr = new StructMsghdr(new InetSocketAddress(), |
| bufferArrayRecv, |
| null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET6, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet6_4K_directBuffer() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| StructCmsghdr[] cmsg = new StructCmsghdr[1]; |
| cmsg[0] = new StructCmsghdr(SOL_UDP, UDP_SEGMENT, (short) 1400); |
| |
| //sendmsg/recvmsg with 1*4K ByteBuffer |
| ByteBuffer[] bufferArray = new ByteBuffer[1]; |
| ByteBuffer[] bufferArrayRecv = new ByteBuffer[1]; |
| bufferArray[0] = ByteBuffer.allocateDirect(4096); // DirectBuffer |
| bufferArrayRecv[0] = ByteBuffer.allocateDirect(4096); // DirectBuffer |
| |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, |
| bufferArray, |
| cmsg, 0); |
| StructMsghdr recvmsgHdr = new StructMsghdr(new InetSocketAddress(), |
| bufferArrayRecv, |
| null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET6, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet_16K_recvparts() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| StructCmsghdr[] cmsg = new StructCmsghdr[1]; |
| cmsg[0] = new StructCmsghdr(SOL_UDP, UDP_SEGMENT, (short) 1400); |
| |
| //sendmsg with 4*4K ByteBuffer, recv with 1*4K ByteBuffer(already with MSG_TRUNC option) |
| ByteBuffer[] bufferArray = new ByteBuffer[4]; |
| ByteBuffer[] bufferArrayRecv = new ByteBuffer[1]; |
| bufferArray[0] = ByteBuffer.allocate(4096); |
| bufferArray[1] = ByteBuffer.allocate(4096); |
| bufferArray[2] = ByteBuffer.allocate(4096); |
| bufferArray[3] = ByteBuffer.allocate(4096); |
| bufferArrayRecv[0] = ByteBuffer.allocate(4096); //receive only part of data |
| |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, |
| bufferArray, |
| cmsg, 0); |
| StructMsghdr recvmsgHdr = new StructMsghdr(new InetSocketAddress(), |
| bufferArrayRecv, |
| null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096 * 4); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet_16K_reciveall() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| StructCmsghdr[] cmsg = new StructCmsghdr[1]; |
| cmsg[0] = new StructCmsghdr(SOL_UDP, UDP_SEGMENT, (short) 1400); |
| |
| // Create sendmsg/recvmsg with 4*4K ByteBuffer |
| ByteBuffer[] bufferArray = new ByteBuffer[4]; |
| bufferArray[0] = ByteBuffer.allocate(4096); |
| bufferArray[1] = ByteBuffer.allocate(4096); |
| bufferArray[2] = ByteBuffer.allocate(4096); |
| bufferArray[3] = ByteBuffer.allocate(4096); |
| |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, bufferArray, cmsg, 0); |
| StructMsghdr recvmsgHdr = new StructMsghdr(new InetSocketAddress(), bufferArray, null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096 * 4); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet_16K_receiveall_without_recv_msgname() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| StructCmsghdr[] cmsg = new StructCmsghdr[1]; |
| cmsg[0] = new StructCmsghdr(SOL_UDP, UDP_SEGMENT, (short) 1400); |
| |
| // Create sendmsg/recvmsg with 4*4K ByteBuffer |
| ByteBuffer[] bufferArray = new ByteBuffer[4]; |
| bufferArray[0] = ByteBuffer.allocate(4096); |
| bufferArray[1] = ByteBuffer.allocate(4096); |
| bufferArray[2] = ByteBuffer.allocate(4096); |
| bufferArray[3] = ByteBuffer.allocate(4096); |
| |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, bufferArray, cmsg, 0); |
| // msg_name is unnecessary. |
| StructMsghdr recvmsgHdr = new StructMsghdr(null, bufferArray, null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096 * 4); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet_16K_without_send_msgcontrl() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| InetSocketAddress loopbackAddr = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| |
| // Create sendmsg/recvmsg with 4*4K ByteBuffer |
| ByteBuffer[] bufferArray = new ByteBuffer[4]; |
| bufferArray[0] = ByteBuffer.allocate(4096); |
| bufferArray[1] = ByteBuffer.allocate(4096); |
| bufferArray[2] = ByteBuffer.allocate(4096); |
| bufferArray[3] = ByteBuffer.allocate(4096); |
| |
| // GSO will not happen without msgcontrol. |
| StructMsghdr sendmsgHdr = new StructMsghdr(loopbackAddr, bufferArray, null, 0); |
| StructMsghdr recvmsgHdr = new StructMsghdr(null, bufferArray, null, 0); |
| |
| checkSendmsgSocketAddress(AF_INET, loopbackAddr, sendmsgHdr, recvmsgHdr, 4096 * 4); |
| } |
| |
| @Test |
| public void test_sendmsg_af_inet_abnormal() throws Exception { |
| //sendmsg socket set |
| InetSocketAddress address = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), |
| 10234); |
| FileDescriptor sendFd = Os.socket(AF_INET, SOCK_DGRAM, 0); |
| |
| ByteBuffer[] bufferArray = new ByteBuffer[1]; |
| bufferArray[0] = ByteBuffer.allocate(8192); |
| |
| try { |
| StructMsghdr msgHdr = new StructMsghdr(address, null, null, 0); |
| Os.sendmsg(sendFd, msgHdr, 0); |
| fail("Expected NullPointerException due to invalid StructMsghdr.msg_iov(NULL)"); |
| } catch (NullPointerException expected) { |
| } |
| |
| try { |
| StructMsghdr msgHdr = new StructMsghdr(null, bufferArray, null, 0); |
| Os.sendmsg(sendFd, msgHdr, 0); |
| fail("Expected ErrnoException due to invalid StructMsghdr.msg_name(NULL)"); |
| } catch (ErrnoException expected) { |
| assertEquals("Expected EDESTADDRREQ binding IPv4 socket to ::", EDESTADDRREQ, |
| expected.errno); |
| } |
| |
| } |
| |
| @Test |
| public void test_socketFamilies() throws Exception { |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| Os.bind(fd, InetAddress.getByName("::"), 0); |
| InetSocketAddress localSocketAddress = (InetSocketAddress) Os.getsockname(fd); |
| assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); |
| |
| fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| Os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); |
| localSocketAddress = (InetSocketAddress) Os.getsockname(fd); |
| assertEquals(Inet6Address.ANY, localSocketAddress.getAddress()); |
| |
| fd = Os.socket(AF_INET, SOCK_STREAM, 0); |
| Os.bind(fd, InetAddress.getByName("0.0.0.0"), 0); |
| localSocketAddress = (InetSocketAddress) Os.getsockname(fd); |
| assertEquals(Inet4Address.ANY, localSocketAddress.getAddress()); |
| try { |
| Os.bind(fd, InetAddress.getByName("::"), 0); |
| fail("Expected ErrnoException binding IPv4 socket to ::"); |
| } catch (ErrnoException expected) { |
| assertEquals("Expected EAFNOSUPPORT binding IPv4 socket to ::", EAFNOSUPPORT, |
| expected.errno); |
| } |
| } |
| |
| private static void checkSocketPing(FileDescriptor fd, InetAddress to, byte[] packet, |
| byte type, byte responseType, boolean useSendto) throws Exception { |
| int len = packet.length; |
| packet[0] = type; |
| if (useSendto) { |
| assertEquals(len, Os.sendto(fd, packet, 0, len, 0, to, 0)); |
| } else { |
| Os.connect(fd, to, 0); |
| assertEquals(len, Os.sendto(fd, packet, 0, len, 0, null, 0)); |
| } |
| |
| int icmpId = ((InetSocketAddress) Os.getsockname(fd)).getPort(); |
| byte[] received = new byte[4096]; |
| InetSocketAddress srcAddress = new InetSocketAddress(); |
| assertEquals(len, Os.recvfrom(fd, received, 0, received.length, 0, srcAddress)); |
| assertEquals(to, srcAddress.getAddress()); |
| assertEquals(responseType, received[0]); |
| assertEquals(received[4], (byte) (icmpId >> 8)); |
| assertEquals(received[5], (byte) (icmpId & 0xff)); |
| |
| received = Arrays.copyOf(received, len); |
| received[0] = type; |
| received[2] = received[3] = 0; // Checksum. |
| received[4] = received[5] = 0; // ICMP ID. |
| assertArrayEquals(packet, received); |
| } |
| |
| @Test |
| public void test_socketPing() throws Exception { |
| final byte ICMP_ECHO = 8, ICMP_ECHOREPLY = 0; |
| final byte ICMPV6_ECHO_REQUEST = (byte) 128, ICMPV6_ECHO_REPLY = (byte) 129; |
| final byte[] packet = ("\000\000\000\000" + // ICMP type, code. |
| "\000\000\000\003" + // ICMP ID (== port), sequence number. |
| "Hello myself").getBytes(StandardCharsets.US_ASCII); |
| |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); |
| InetAddress ipv6Loopback = InetAddress.getByName("::1"); |
| checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, true); |
| checkSocketPing(fd, ipv6Loopback, packet, ICMPV6_ECHO_REQUEST, ICMPV6_ECHO_REPLY, false); |
| |
| fd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); |
| InetAddress ipv4Loopback = InetAddress.getByName("127.0.0.1"); |
| checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, true); |
| checkSocketPing(fd, ipv4Loopback, packet, ICMP_ECHO, ICMP_ECHOREPLY, false); |
| } |
| |
| @Test |
| public void test_Ipv4Fallback() throws Exception { |
| // This number of iterations gives a ~60% chance of creating the conditions that caused |
| // http://b/23088314 without making test times too long. On a hammerhead running MRZ37C |
| // using vogar, this test takes about 4s. |
| final int ITERATIONS = 10000; |
| for (int i = 0; i < ITERATIONS; i++) { |
| FileDescriptor mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
| try { |
| Os.bind(mUdpSock, Inet4Address.ANY, 0); |
| } catch (ErrnoException e) { |
| fail("ErrnoException after " + i + " iterations: " + e); |
| } finally { |
| Os.close(mUdpSock); |
| } |
| } |
| } |
| |
| @Test |
| public void test_unlink() throws Exception { |
| File f = File.createTempFile("OsTest", "tst"); |
| assertTrue(f.exists()); |
| Os.unlink(f.getAbsolutePath()); |
| assertFalse(f.exists()); |
| |
| try { |
| Os.unlink(f.getAbsolutePath()); |
| fail(); |
| } catch (ErrnoException e) { |
| assertEquals(OsConstants.ENOENT, e.errno); |
| } |
| } |
| |
| // b/27294715 |
| @Test |
| public void test_recvfrom_concurrentShutdown() throws Exception { |
| final FileDescriptor serverFd = Os.socket(AF_INET, SOCK_DGRAM, 0); |
| Os.bind(serverFd, InetAddress.getByName("127.0.0.1"), 0); |
| // Set 4s timeout |
| StructTimeval tv = StructTimeval.fromMillis(4000); |
| Os.setsockoptTimeval(serverFd, SOL_SOCKET, SO_RCVTIMEO, tv); |
| |
| final AtomicReference<Exception> killerThreadException = new AtomicReference<>( |
| null); |
| final Thread killer = new Thread(() -> { |
| try { |
| Thread.sleep(2000); |
| try { |
| Os.shutdown(serverFd, SHUT_RDWR); |
| } catch (ErrnoException expected) { |
| if (OsConstants.ENOTCONN != expected.errno) { |
| killerThreadException.set(expected); |
| } |
| } |
| } catch (Exception ex) { |
| killerThreadException.set(ex); |
| } |
| }); |
| killer.start(); |
| |
| ByteBuffer buffer = ByteBuffer.allocate(16); |
| InetSocketAddress srcAddress = new InetSocketAddress(); |
| int received = Os.recvfrom(serverFd, buffer, 0, srcAddress); |
| assertEquals(0, received); |
| Os.close(serverFd); |
| |
| killer.join(); |
| assertNull(killerThreadException.get()); |
| } |
| |
| @Test |
| public void test_xattr() throws Exception { |
| final String NAME_TEST = "user.meow"; |
| |
| final byte[] VALUE_CAKE = "cake cake cake".getBytes(StandardCharsets.UTF_8); |
| final byte[] VALUE_PIE = "pie".getBytes(StandardCharsets.UTF_8); |
| |
| File file = File.createTempFile("xattr", "test"); |
| String path = file.getAbsolutePath(); |
| |
| try { |
| try { |
| Os.getxattr(path, NAME_TEST); |
| fail("Expected ENODATA"); |
| } catch (ErrnoException e) { |
| assertEquals(OsConstants.ENODATA, e.errno); |
| } |
| assertFalse(Arrays.asList(Os.listxattr(path)).contains(NAME_TEST)); |
| |
| Os.setxattr(path, NAME_TEST, VALUE_CAKE, OsConstants.XATTR_CREATE); |
| byte[] xattr_create = Os.getxattr(path, NAME_TEST); |
| assertTrue(Arrays.asList(Os.listxattr(path)).contains(NAME_TEST)); |
| assertEquals(VALUE_CAKE.length, xattr_create.length); |
| assertStartsWith(VALUE_CAKE, xattr_create); |
| |
| try { |
| Os.setxattr(path, NAME_TEST, VALUE_PIE, OsConstants.XATTR_CREATE); |
| fail("Expected EEXIST"); |
| } catch (ErrnoException e) { |
| assertEquals(OsConstants.EEXIST, e.errno); |
| } |
| |
| Os.setxattr(path, NAME_TEST, VALUE_PIE, OsConstants.XATTR_REPLACE); |
| byte[] xattr_replace = Os.getxattr(path, NAME_TEST); |
| assertTrue(Arrays.asList(Os.listxattr(path)).contains(NAME_TEST)); |
| assertEquals(VALUE_PIE.length, xattr_replace.length); |
| assertStartsWith(VALUE_PIE, xattr_replace); |
| |
| Os.removexattr(path, NAME_TEST); |
| try { |
| Os.getxattr(path, NAME_TEST); |
| fail("Expected ENODATA"); |
| } catch (ErrnoException e) { |
| assertEquals(OsConstants.ENODATA, e.errno); |
| } |
| assertFalse(Arrays.asList(Os.listxattr(path)).contains(NAME_TEST)); |
| |
| } finally { |
| file.delete(); |
| } |
| } |
| |
| @Test |
| public void test_xattr_NPE() throws Exception { |
| File file = File.createTempFile("xattr", "test"); |
| final String path = file.getAbsolutePath(); |
| final String NAME_TEST = "user.meow"; |
| final byte[] VALUE_CAKE = "cake cake cake".getBytes(StandardCharsets.UTF_8); |
| |
| // getxattr |
| try { |
| Os.getxattr(null, NAME_TEST); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| try { |
| Os.getxattr(path, null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| |
| // listxattr |
| try { |
| Os.listxattr(null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| |
| // removexattr |
| try { |
| Os.removexattr(null, NAME_TEST); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| try { |
| Os.removexattr(path, null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| |
| // setxattr |
| try { |
| Os.setxattr(null, NAME_TEST, VALUE_CAKE, OsConstants.XATTR_CREATE); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| try { |
| Os.setxattr(path, null, VALUE_CAKE, OsConstants.XATTR_CREATE); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| try { |
| Os.setxattr(path, NAME_TEST, null, OsConstants.XATTR_CREATE); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| @Test |
| public void test_xattr_Errno() { |
| final String NAME_TEST = "user.meow"; |
| final byte[] VALUE_CAKE = "cake cake cake".getBytes(StandardCharsets.UTF_8); |
| |
| // ENOENT, No such file or directory. |
| try { |
| Os.getxattr("", NAME_TEST); |
| fail(); |
| } catch (ErrnoException e) { |
| assertEquals(ENOENT, e.errno); |
| } |
| try { |
| Os.listxattr(""); |
| fail(); |
| } catch (ErrnoException e) { |
| assertEquals(ENOENT, e.errno); |
| } |
| try { |
| Os.removexattr("", NAME_TEST); |
| fail(); |
| } catch (ErrnoException e) { |
| assertEquals(ENOENT, e.errno); |
| } |
| try { |
| Os.setxattr("", NAME_TEST, VALUE_CAKE, OsConstants.XATTR_CREATE); |
| fail(); |
| } catch (ErrnoException e) { |
| assertEquals(ENOENT, e.errno); |
| } |
| |
| // ENOTSUP, Extended attributes are not supported by the filesystem, or are disabled. |
| // Since kernel version 4.9 (or some other version after 4.4), *xattr() methods |
| // may set errno to EACCES instead. This behavior change is likely related to |
| // https://patchwork.kernel.org/patch/9294421/ which reimplemented getxattr, setxattr, |
| // and removexattr on top of generic handlers. |
| final String path = "/proc/self/stat"; |
| try { |
| Os.setxattr(path, NAME_TEST, VALUE_CAKE, OsConstants.XATTR_CREATE); |
| fail(); |
| } catch (ErrnoException e) { |
| assertTrue("Unexpected errno: " + e.errno, e.errno == ENOTSUP || e.errno == EACCES); |
| } |
| try { |
| Os.getxattr(path, NAME_TEST); |
| fail(); |
| } catch (ErrnoException e) { |
| assertEquals(ENOTSUP, e.errno); |
| } |
| try { |
| // Linux listxattr does not set errno. |
| Os.listxattr(path); |
| } catch (ErrnoException e) { |
| fail(); |
| } |
| try { |
| Os.removexattr(path, NAME_TEST); |
| fail(); |
| } catch (ErrnoException e) { |
| assertTrue("Unexpected errno: " + e.errno, e.errno == ENOTSUP || e.errno == EACCES); |
| } |
| } |
| |
| @Test |
| public void test_realpath() throws Exception { |
| File tmpDir = new File(System.getProperty("java.io.tmpdir")); |
| // This is a chicken and egg problem. We have no way of knowing whether |
| // the temporary directory or one of its path elements were symlinked, so |
| // we'll need this call to realpath. |
| String canonicalTmpDir = Os.realpath(tmpDir.getAbsolutePath()); |
| |
| // Test that "." and ".." are resolved correctly. |
| assertEquals(canonicalTmpDir, |
| Os.realpath(canonicalTmpDir + "/./../" + tmpDir.getName())); |
| |
| // Test that symlinks are resolved correctly. |
| File target = new File(tmpDir, "target"); |
| File link = new File(tmpDir, "link"); |
| try { |
| assertTrue(target.createNewFile()); |
| Os.symlink(target.getAbsolutePath(), link.getAbsolutePath()); |
| |
| assertEquals(canonicalTmpDir + "/target", |
| Os.realpath(canonicalTmpDir + "/link")); |
| } finally { |
| boolean deletedTarget = target.delete(); |
| boolean deletedLink = link.delete(); |
| // Asserting this here to provide a definitive reason for |
| // a subsequent failure on the same run. |
| assertTrue("deletedTarget = " + deletedTarget + ", deletedLink =" + deletedLink, |
| deletedTarget && deletedLink); |
| } |
| } |
| |
| private int[] getKernelVersion() { |
| // Example: |
| // 4.9.29-g958411d --> 4.9 |
| String release = Os.uname().release; |
| Matcher m = Pattern.compile("^(\\d+)\\.(\\d+)").matcher(release); |
| assertTrue("No pattern in release string: " + release, m.find()); |
| return new int[]{ Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)) }; |
| } |
| |
| private boolean kernelIsAtLeast(int major, int minor) { |
| int[] version = getKernelVersion(); |
| return version[0] > major || (version[0] == major && version[1] >= minor); |
| } |
| |
| @Test |
| public void test_socket_udpGro_setAndGet() throws Exception { |
| // UDP GRO not required to be enabled on kernels prior to 5.4 |
| assumeTrue(kernelIsAtLeast(5, 4)); |
| |
| final FileDescriptor fd = Os.socket(AF_INET6, SOCK_DGRAM, 0); |
| try { |
| final int setValue = 1; |
| Os.setsockoptInt(fd, IPPROTO_UDP, UDP_GRO, setValue); |
| // getsockopt(IPPROTO_UDP, UDP_GRO) is not implemented. |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_udpGso_set() throws Exception { |
| // UDP GSO not required to be enabled on kernels prior to 4.19. |
| assumeTrue(kernelIsAtLeast(4, 19)); |
| |
| final FileDescriptor fd = Os.socket(AF_INET, SOCK_DGRAM, 0); |
| try { |
| assertEquals(0, Os.getsockoptInt(fd, IPPROTO_UDP, UDP_SEGMENT)); |
| |
| final int setValue = 1452; |
| Os.setsockoptInt(fd, IPPROTO_UDP, UDP_SEGMENT, setValue); |
| assertEquals(setValue, Os.getsockoptInt(fd, IPPROTO_UDP, UDP_SEGMENT)); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| /** |
| * Tests that TCP_USER_TIMEOUT can be set on a TCP socket, but doesn't test |
| * that it behaves as expected. |
| */ |
| @Test |
| public void test_socket_tcpUserTimeout_setAndGet() throws Exception { |
| final FileDescriptor fd = Os.socket(AF_INET, SOCK_STREAM, 0); |
| try { |
| int v = Os.getsockoptInt(fd, OsConstants.IPPROTO_TCP, OsConstants.TCP_USER_TIMEOUT); |
| assertEquals(0, v); // system default value |
| int newValue = 3000; |
| Os.setsockoptInt(fd, OsConstants.IPPROTO_TCP, OsConstants.TCP_USER_TIMEOUT, |
| newValue); |
| int actualValue = Os.getsockoptInt(fd, OsConstants.IPPROTO_TCP, |
| OsConstants.TCP_USER_TIMEOUT); |
| // The kernel can round the requested value based on the HZ setting. We allow up to 10ms |
| // difference. |
| assertTrue("Returned incorrect timeout:" + actualValue, |
| Math.abs(newValue - actualValue) <= 10); |
| // No need to reset the value to 0, since we're throwing the socket away |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_tcpUserTimeout_doesNotWorkOnDatagramSocket() throws Exception { |
| final FileDescriptor fd = Os.socket(AF_INET, SOCK_DGRAM, 0); |
| try { |
| Os.setsockoptInt(fd, OsConstants.IPPROTO_TCP, OsConstants.TCP_USER_TIMEOUT, |
| 3000); |
| fail("datagram (connectionless) sockets shouldn't support TCP_USER_TIMEOUT"); |
| } catch (ErrnoException expected) { |
| // expected |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_sockoptTimeval_readWrite() throws Exception { |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| try { |
| StructTimeval v = Os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO); |
| assertEquals(0, v.toMillis()); // system default value |
| |
| StructTimeval newValue = StructTimeval.fromMillis(3000); |
| Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, newValue); |
| |
| StructTimeval actualValue = Os.getsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO); |
| |
| // The kernel can round the requested value based on the HZ setting. We allow up to 10ms |
| // difference. |
| assertTrue("Returned incorrect timeout:" + actualValue, |
| Math.abs(newValue.toMillis() - actualValue.toMillis()) <= 10); |
| // No need to reset the value to 0, since we're throwing the socket away |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_effective() throws Exception { |
| final int TIMEOUT_VALUE_MILLIS = 250; |
| final int ALLOWED_TIMEOUT_MILLIS = 3000; |
| final int ROUNDING_ERROR_MILLIS = 10; |
| |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); |
| try { |
| // Configure the receive timeout. |
| StructTimeval tv = StructTimeval.fromMillis(TIMEOUT_VALUE_MILLIS); |
| Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv); |
| |
| // Bind socket and wait for data (and timeout). |
| Os.bind(fd, InetAddress.getByName("::1"), 0); |
| byte[] request = new byte[1]; |
| long startTime = System.nanoTime(); |
| expectException(() -> Os.read(fd, request, 0, request.length), |
| ErrnoException.class, EAGAIN, "Expected timeout"); |
| long durationMillis = Duration.ofNanos(System.nanoTime() - startTime).toMillis(); |
| |
| // Our requested timeout may be rounded by the kernel (b/176104885, b/216667550). |
| // We allow up to 1 scheduling quantum difference (assuming HZ = 100). |
| assertTrue("Timeout of " + tv.toMillis() + "ms returned after " + durationMillis + "ms", |
| durationMillis >= tv.toMillis() - ROUNDING_ERROR_MILLIS); |
| assertTrue("Timeout of " + TIMEOUT_VALUE_MILLIS + "ms failed to return within " |
| + ALLOWED_TIMEOUT_MILLIS + "ms", |
| durationMillis < ALLOWED_TIMEOUT_MILLIS); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_nullFd() { |
| StructTimeval tv = StructTimeval.fromMillis(500); |
| expectException( |
| () -> Os.setsockoptTimeval(null, SOL_SOCKET, SO_RCVTIMEO, tv), |
| ErrnoException.class, EBADF, "setsockoptTimeval(null, ...)"); |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_fileFd() throws Exception { |
| File testFile = createTempFile("test_socket_setSockoptTimeval_invalidFd", ""); |
| try (FileInputStream fis = new FileInputStream(testFile)) { |
| final FileDescriptor fd = fis.getFD(); |
| |
| StructTimeval tv = StructTimeval.fromMillis(500); |
| expectException( |
| () -> Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, tv), |
| ErrnoException.class, ENOTSOCK, "setsockoptTimeval(<file fd>, ...)"); |
| } |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_badFd() throws Exception { |
| StructTimeval tv = StructTimeval.fromMillis(500); |
| FileDescriptor invalidFd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| Os.close(invalidFd); |
| |
| expectException( |
| () -> Os.setsockoptTimeval(invalidFd, SOL_SOCKET, SO_RCVTIMEO, tv), |
| ErrnoException.class, EBADF, "setsockoptTimeval(<closed fd>, ...)"); |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_invalidLevel() throws Exception { |
| StructTimeval tv = StructTimeval.fromMillis(500); |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| try { |
| expectException( |
| () -> Os.setsockoptTimeval(fd, -1, SO_RCVTIMEO, tv), |
| ErrnoException.class, ENOPROTOOPT, |
| "setsockoptTimeval(fd, <invalid level>, ...)"); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_invalidOpt() throws Exception { |
| StructTimeval tv = StructTimeval.fromMillis(500); |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| try { |
| expectException( |
| () -> Os.setsockoptTimeval(fd, SOL_SOCKET, -1, tv), |
| ErrnoException.class, ENOPROTOOPT, |
| "setsockoptTimeval(fd, <invalid level>, ...)"); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_setSockoptTimeval_nullTimeVal() throws Exception { |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| try { |
| expectException( |
| () -> Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, null), |
| NullPointerException.class, null, "setsockoptTimeval(..., null)"); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_socket_getSockoptTimeval_invalidOption() throws Exception { |
| FileDescriptor fd = Os.socket(AF_INET6, SOCK_STREAM, 0); |
| try { |
| expectException( |
| () -> Os.getsockoptTimeval(fd, SOL_SOCKET, SO_DEBUG), |
| IllegalArgumentException.class, null, |
| "getsockoptTimeval(..., <non-timeval option>)"); |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_if_nametoindex_if_indextoname() throws Exception { |
| Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); |
| assertNotNull(networkInterfaces); |
| List<NetworkInterface> nis = Collections.list(networkInterfaces); |
| |
| assertTrue(nis.size() > 0); |
| for (NetworkInterface ni : nis) { |
| int index = ni.getIndex(); |
| String name = ni.getName(); |
| assertEquals(index, Os.if_nametoindex(name)); |
| assertEquals(Os.if_indextoname(index), name); |
| } |
| |
| assertEquals(0, Os.if_nametoindex("this-interface-does-not-exist")); |
| assertNull(Os.if_indextoname(-1000)); |
| |
| try { |
| Os.if_nametoindex(null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| private static void assertStartsWith(byte[] expectedContents, byte[] container) { |
| for (int i = 0; i < expectedContents.length; i++) { |
| if (expectedContents[i] != container[i]) { |
| fail("Expected " + Arrays.toString(expectedContents) + " but found " |
| + Arrays.toString(expectedContents)); |
| } |
| } |
| } |
| |
| @Test |
| public void test_readlink() throws Exception { |
| File path = new File(TestIoUtils.createTemporaryDirectory("test_readlink"), "symlink"); |
| |
| // ext2 and ext4 have PAGE_SIZE limits on symlink targets. |
| // If file encryption is enabled, there's extra overhead to store the |
| // size of the encrypted symlink target. There's also an off-by-one |
| // in current kernels (and marlin/sailfish where we're seeing this |
| // failure are still on 3.18, far from current). Given that we don't |
| // really care here, just use 2048 instead. http://b/33306057. |
| int size = 2048; |
| StringBuilder xs = new StringBuilder(); |
| for (int i = 0; i < size - 1; ++i) { |
| xs.append("x"); |
| } |
| |
| Os.symlink(xs.toString(), path.getPath()); |
| |
| assertEquals(xs.toString(), Os.readlink(path.getPath())); |
| } |
| |
| // Address should be correctly set for empty packets. http://b/33481605 |
| @Test |
| public void test_recvfrom_EmptyPacket() throws Exception { |
| try (DatagramSocket ds = new DatagramSocket(); |
| DatagramSocket srcSock = new DatagramSocket()) { |
| srcSock.send(new DatagramPacket(new byte[0], 0, ds.getLocalSocketAddress())); |
| |
| byte[] recvBuf = new byte[16]; |
| InetSocketAddress address = new InetSocketAddress(); |
| int recvCount = |
| android.system.Os.recvfrom(ds.getFileDescriptor$(), recvBuf, 0, 16, 0, address); |
| assertEquals(0, recvCount); |
| assertTrue(address.getAddress().isLoopbackAddress()); |
| assertEquals(srcSock.getLocalPort(), address.getPort()); |
| } |
| } |
| |
| @Test |
| public void test_fstat_times() throws Exception { |
| File file = File.createTempFile("OsTest", "fstattest"); |
| FileOutputStream fos = new FileOutputStream(file); |
| StructStat structStat1 = Os.fstat(fos.getFD()); |
| assertEquals(structStat1.st_mtim.tv_sec, structStat1.st_mtime); |
| assertEquals(structStat1.st_ctim.tv_sec, structStat1.st_ctime); |
| assertEquals(structStat1.st_atim.tv_sec, structStat1.st_atime); |
| Thread.sleep(100); |
| fos.write(new byte[] { 1, 2, 3 }); |
| fos.flush(); |
| StructStat structStat2 = Os.fstat(fos.getFD()); |
| fos.close(); |
| |
| assertEquals(-1, structStat1.st_mtim.compareTo(structStat2.st_mtim)); |
| assertEquals(-1, structStat1.st_ctim.compareTo(structStat2.st_ctim)); |
| assertEquals(0, structStat1.st_atim.compareTo(structStat2.st_atim)); |
| } |
| |
| @Test |
| public void test_build_sruct_stat() { |
| StructStat structStat1 = new StructStat(/*st_dev*/ 0L, /*st_ino*/ 0L, /*st_mode*/ 0, |
| /*st_nlink*/ 1L, /*st_uid*/ 1000, /*st_gid*/ 1000, /*st_rdev*/ 0L, /*st_size*/ 0L, |
| /*st_atime*/ 0L, /*st_mtime*/ 0L, /*st_ctime*/ 0L, /*st_blksize*/ 4096L, |
| /*st_blocks*/ 1L); |
| StructStat structStat2 = new StructStat(/*st_dev*/ 0L, /*st_ino*/ 0L, /*st_mode*/ 0, |
| /*st_nlink*/ 1L, /*st_uid*/ 1000, /*st_gid*/ 1000, /*st_rdev*/ 0L, /*st_size*/ 0L, |
| /*st_atim*/ new StructTimespec(0L, 0L), /*st_mtim*/ new StructTimespec(0L, 0L), |
| /*st_ctim*/ new StructTimespec(0L, 0L), /*st_blksize*/ 4096L, /*st_blocks*/ 1L); |
| assertStructStatsEqual(structStat1, structStat2); |
| } |
| |
| @Test |
| public void test_getrlimit() throws Exception { |
| StructRlimit rlimit = Os.getrlimit(OsConstants.RLIMIT_NOFILE); |
| // We can't really make any assertions about these values since they might vary from |
| // device to device and even process to process. We do know that they will be greater |
| // than zero, though. |
| assertTrue(rlimit.rlim_cur > 0); |
| assertTrue(rlimit.rlim_max > 0); |
| } |
| |
| // http://b/65051835 |
| @Test |
| public void test_pipe2_errno() { |
| try { |
| // flag=-1 is not a valid value for pip2, will EINVAL |
| Os.pipe2(-1); |
| fail(); |
| } catch (ErrnoException expected) { |
| } |
| } |
| |
| // http://b/65051835 |
| @Test |
| public void test_sendfile_errno() throws Exception { |
| File testFile = File.createTempFile("test_sendfile_errno", ""); |
| FileDescriptor fd = Os.open(testFile.toString(), O_WRONLY, S_IRUSR | S_IWUSR); |
| assertNotNull(fd); |
| |
| try { |
| // fd is not open for input, will cause EBADF |
| Int64Ref offset = new Int64Ref(10); |
| Os.sendfile(fd, fd, offset, 10); |
| fail(); |
| } catch (ErrnoException expected) { |
| } finally { |
| Os.close(fd); |
| } |
| } |
| |
| @Test |
| public void test_sendfile_null() throws Exception { |
| File in = createTempFile("test_sendfile_null", "Hello, world!"); |
| try { |
| int len = "Hello".length(); |
| assertEquals("Hello", checkSendfile(in, null, len, null)); |
| } finally { |
| in.delete(); |
| } |
| } |
| |
| @Test |
| public void test_sendfile_offset() throws Exception { |
| File in = createTempFile("test_sendfile_offset", "Hello, world!"); |
| try { |
| // checkSendfile(sendFileImplToUse, in, startOffset, maxBytes, expectedEndOffset) |
| assertEquals("Hello", checkSendfile(in, 0L, 5, 5L)); |
| assertEquals("ello,", checkSendfile(in, 1L, 5, 6L)); |
| // At offset 9, only 4 bytes/chars available, even though we're asking for 5. |
| assertEquals("rld!", checkSendfile(in, 9L, 5, 13L)); |
| assertEquals("", checkSendfile(in, 1L, 0, 1L)); |
| } finally { |
| in.delete(); |
| } |
| } |
| |
| private static String checkSendfile(File in, Long startOffset, |
| int maxBytes, Long expectedEndOffset) throws IOException, ErrnoException { |
| File out = File.createTempFile(OsTest.class.getSimpleName() + "_checkSendFile", ".out"); |
| try (FileInputStream inStream = new FileInputStream(in)) { |
| FileDescriptor inFd = inStream.getFD(); |
| try (FileOutputStream outStream = new FileOutputStream(out)) { |
| FileDescriptor outFd = outStream.getFD(); |
| Int64Ref offset = (startOffset == null) ? null : new Int64Ref(startOffset); |
| android.system.Os.sendfile(outFd, inFd, offset, maxBytes); |
| assertEquals(expectedEndOffset, offset == null ? null : offset.value); |
| } |
| return TestIoUtils.readFileAsString(out.getPath()); |
| } finally { |
| out.delete(); |
| } |
| } |
| |
| private static File createTempFile(String namePart, String contents) throws IOException { |
| File f = File.createTempFile(OsTest.class.getSimpleName() + namePart, ".in"); |
| try (FileWriter writer = new FileWriter(f)) { |
| writer.write(contents); |
| } |
| return f; |
| } |
| |
| @Test |
| public void test_odirect() throws Exception { |
| File testFile = createTempFile("test_odirect", ""); |
| try { |
| FileDescriptor fd = |
| Os.open(testFile.toString(), O_WRONLY | O_DIRECT, S_IRUSR | S_IWUSR); |
| assertNotNull(fd); |
| assertTrue(fd.valid()); |
| int flags = Os.fcntlVoid(fd, F_GETFL); |
| assertTrue("Expected file flags to include " + O_DIRECT + ", actual value: " + flags, |
| 0 != (flags & O_DIRECT)); |
| Os.close(fd); |
| } finally { |
| testFile.delete(); |
| } |
| } |
| |
| @Test |
| public void test_oappend() throws Exception { |
| File testFile = createTempFile("test_oappend", ""); |
| try { |
| FileDescriptor fd = |
| Os.open(testFile.toString(), O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); |
| assertNotNull(fd); |
| assertTrue(fd.valid()); |
| int flags = Os.fcntlVoid(fd, F_GETFL); |
| assertTrue("Expected file flags to include " + O_APPEND + ", actual value: " + flags, |
| 0 != (flags & O_APPEND)); |
| Os.close(fd); |
| } finally { |
| testFile.delete(); |
| } |
| } |
| |
| @Test |
| public void test_splice() throws Exception { |
| FileDescriptor[] pipe = Os.pipe2(0); |
| File in = createTempFile("splice1", "foobar"); |
| File out = createTempFile("splice2", ""); |
| |
| Int64Ref offIn = new Int64Ref(1); |
| Int64Ref offOut = new Int64Ref(0); |
| |
| // Splice into pipe |
| try (FileInputStream streamIn = new FileInputStream(in)) { |
| FileDescriptor fdIn = streamIn.getFD(); |
| long result = Os |
| .splice(fdIn, offIn, pipe[1], null /* offOut */, 10 /* len */, 0 /* flags */); |
| assertEquals(5, result); |
| assertEquals(6, offIn.value); |
| } |
| |
| // Splice from pipe |
| try (FileOutputStream streamOut = new FileOutputStream(out)) { |
| FileDescriptor fdOut = streamOut.getFD(); |
| long result = Os |
| .splice(pipe[0], null /* offIn */, fdOut, offOut, 10 /* len */, 0 /* flags */); |
| assertEquals(5, result); |
| assertEquals(5, offOut.value); |
| } |
| |
| assertEquals("oobar", TestIoUtils.readFileAsString(out.getPath())); |
| |
| Os.close(pipe[0]); |
| Os.close(pipe[1]); |
| } |
| |
| @Test |
| public void test_splice_errors() throws Exception { |
| File in = createTempFile("splice3", ""); |
| File out = createTempFile("splice4", ""); |
| FileDescriptor[] pipe = Os.pipe2(0); |
| |
| //.fdIn == null |
| try { |
| Os.splice(null /* fdIn */, null /* offIn */, pipe[1], |
| null /*offOut*/, 10 /* len */, 0 /* flags */); |
| fail(); |
| } catch (ErrnoException expected) { |
| assertEquals(EBADF, expected.errno); |
| } |
| |
| //.fdOut == null |
| try { |
| Os.splice(pipe[0] /* fdIn */, null /* offIn */, null /* fdOut */, |
| null /*offOut*/, 10 /* len */, 0 /* flags */); |
| fail(); |
| } catch (ErrnoException expected) { |
| assertEquals(EBADF, expected.errno); |
| } |
| |
| // No pipe fd |
| try (FileOutputStream streamOut = new FileOutputStream(out)) { |
| try (FileInputStream streamIn = new FileInputStream(in)) { |
| FileDescriptor fdIn = streamIn.getFD(); |
| FileDescriptor fdOut = streamOut.getFD(); |
| Os.splice(fdIn, null /* offIn */, fdOut, null /* offOut */, 10 /* len */, |
| 0 /* flags */); |
| fail(); |
| } catch (ErrnoException expected) { |
| assertEquals(EINVAL, expected.errno); |
| } |
| } |
| |
| Os.close(pipe[0]); |
| Os.close(pipe[1]); |
| } |
| |
| @Test |
| public void testCloseNullFileDescriptor() throws Exception { |
| try { |
| Os.close(null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| @Test |
| public void testSocketpairNullFileDescriptor1() throws Exception { |
| try { |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, null, new FileDescriptor()); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| @Test |
| public void testSocketpairNullFileDescriptor2() throws Exception { |
| try { |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, new FileDescriptor(), null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| @Test |
| public void testSocketpairNullFileDescriptorBoth() throws Exception { |
| try { |
| Os.socketpair(AF_UNIX, SOCK_STREAM, 0, null, null); |
| fail(); |
| } catch (NullPointerException expected) { |
| } |
| } |
| |
| @Test |
| public void testInetPtonIpv4() { |
| String srcAddress = "127.0.0.1"; |
| InetAddress inetAddress = Os.inet_pton(AF_INET, srcAddress); |
| assertEquals(srcAddress, inetAddress.getHostAddress()); |
| } |
| |
| @Test |
| public void testInetPtonIpv6() { |
| String srcAddress = "1123:4567:89ab:cdef:fedc:ba98:7654:3210"; |
| InetAddress inetAddress = Os.inet_pton(AF_INET6, srcAddress); |
| assertEquals(srcAddress, inetAddress.getHostAddress()); |
| } |
| |
| @Test |
| public void testInetPtonInvalidFamily() { |
| String srcAddress = "127.0.0.1"; |
| InetAddress inetAddress = Os.inet_pton(AF_UNIX, srcAddress); |
| assertNull(inetAddress); |
| } |
| |
| @Test |
| public void testInetPtonWrongFamily() { |
| String srcAddress = "127.0.0.1"; |
| InetAddress inetAddress = Os.inet_pton(AF_INET6, srcAddress); |
| assertNull(inetAddress); |
| } |
| |
| @Test |
| public void testInetPtonInvalidData() { |
| String srcAddress = "10.1"; |
| InetAddress inetAddress = Os.inet_pton(AF_INET, srcAddress); |
| assertNull(inetAddress); |
| } |
| |
| /** |
| * Verifies the {@link OsConstants#MAP_ANONYMOUS}. |
| */ |
| @Test |
| public void testMapAnonymous() throws Exception { |
| final long size = 4096; |
| final long address = Os.mmap(0, size, PROT_READ, |
| MAP_PRIVATE | MAP_ANONYMOUS, new FileDescriptor(), 0); |
| assertTrue(address > 0); |
| Os.munmap(address, size); |
| } |
| |
| @Test |
| public void testMemfdCreate() throws Exception { |
| FileDescriptor fd = null; |
| try { |
| fd = Os.memfd_create("test_memfd", 0); |
| assertNotNull(fd); |
| assertTrue(fd.valid()); |
| |
| StructStat stat = Os.fstat(fd); |
| assertEquals(0, stat.st_size); |
| |
| final byte[] expected = new byte[] {1, 2, 3, 4}; |
| Os.write(fd, expected, 0, expected.length); |
| stat = Os.fstat(fd); |
| assertEquals(expected.length, stat.st_size); |
| |
| byte[] actual = new byte[expected.length]; |
| // should be seekable |
| Os.lseek(fd, 0, SEEK_SET); |
| Os.read(fd, actual, 0, actual.length); |
| assertArrayEquals(expected, actual); |
| } finally { |
| if (fd != null) { |
| Os.close(fd); |
| fd = null; |
| } |
| } |
| } |
| |
| @Test |
| public void testMemfdCreateFlags() throws Exception { |
| FileDescriptor fd = null; |
| |
| // test that MFD_CLOEXEC is obeyed |
| try { |
| fd = Os.memfd_create("test_memfd", 0); |
| assertNotNull(fd); |
| assertTrue(fd.valid()); |
| int flags = Os.fcntlVoid(fd, F_GETFD); |
| assertEquals("Expected flags to not include " + FD_CLOEXEC + ", actual value: " + flags, |
| 0, (flags & FD_CLOEXEC)); |
| } finally { |
| if (fd != null) { |
| Os.close(fd); |
| fd = null; |
| } |
| } |
| try { |
| fd = Os.memfd_create("test_memfd", MFD_CLOEXEC); |
| assertNotNull(fd); |
| assertTrue(fd.valid()); |
| int flags = Os.fcntlVoid(fd, F_GETFD); |
| assertTrue("Expected flags to include " + FD_CLOEXEC + ", actual value: " + flags, |
| 0 != (flags & FD_CLOEXEC)); |
| } finally { |
| if (fd != null) { |
| Os.close(fd); |
| fd = null; |
| } |
| } |
| } |
| |
| @Test |
| public void testMemfdCreateErrno() { |
| expectException(() -> Os.memfd_create(null, 0), NullPointerException.class, null, |
| "memfd_create(null, 0)"); |
| |
| expectException(() -> Os.memfd_create("test_memfd", 0xffff), ErrnoException.class, EINVAL, |
| "memfd_create(\"test_memfd\", 0xffff)"); |
| } |
| |
| @Test |
| public void environmentsInitiallyEqual() throws Exception { |
| assertEnvironmentsEqual(); |
| } |
| |
| @Test |
| public void getSetUnsetenvEnviron_Success() throws Exception { |
| String variable1 = "OSTEST_VARIABLE1"; |
| String variable2 = "OSTEST_VARIABLE2"; |
| String value1 = "value1"; |
| String value2 = "value2"; |
| |
| // Initial state, the test variables should not be set anywhere |
| assertNull(System.getenv(variable1)); |
| assertNull(System.getenv(variable2)); |
| assertNull(Os.getenv(variable1)); |
| assertNull(Os.getenv(variable2)); |
| |
| // Set and get |
| Os.setenv(variable1, value1, false); |
| Os.setenv(variable2, value2, false); |
| assertEquals(value1, Os.getenv(variable1)); |
| assertEquals(value2, Os.getenv(variable2)); |
| assertEquals(value1, System.getenv(variable1)); |
| assertEquals(value2, System.getenv(variable2)); |
| // Comparing environments indirectly tests Os.environ() |
| assertEnvironmentsEqual(); |
| |
| // Update values with overwrite flag set to false - should be a no-op |
| Os.setenv(variable1, value2, false); |
| Os.setenv(variable2, value1, false); |
| assertEquals(value1, Os.getenv(variable1)); |
| assertEquals(value2, Os.getenv(variable2)); |
| assertEquals(value1, System.getenv(variable1)); |
| assertEquals(value2, System.getenv(variable2)); |
| assertEnvironmentsEqual(); |
| |
| // Update values (swap value1 and value2) |
| Os.setenv(variable1, value2, true); |
| Os.setenv(variable2, value1, true); |
| assertEquals(value2, Os.getenv(variable1)); |
| assertEquals(value1, Os.getenv(variable2)); |
| assertEquals(value2, System.getenv(variable1)); |
| assertEquals(value1, System.getenv(variable2)); |
| assertEnvironmentsEqual(); |
| |
| // Unset |
| Os.unsetenv(variable1); |
| Os.unsetenv(variable2); |
| assertNull(System.getenv(variable1)); |
| assertNull(System.getenv(variable2)); |
| assertNull(Os.getenv(variable1)); |
| assertNull(Os.getenv(variable2)); |
| assertEnvironmentsEqual(); |
| } |
| |
| @Test |
| public void setenv() { |
| expectException(() -> Os.setenv(null, null, true), NullPointerException.class, null, |
| "setenv(null, null, true)"); |
| |
| expectException(() -> Os.setenv(null, "value", true), NullPointerException.class, null, |
| "setenv(null, value, true)"); |
| |
| expectException(() -> Os.setenv("a", null, true), NullPointerException.class, null, |
| "setenv(\"a\", null, true)"); |
| |
| expectException(() -> Os.setenv("", "value", true), ErrnoException.class, EINVAL, |
| "setenv(\"\", value, true)"); |
| |
| expectException(() -> Os.setenv("a=b", "value", true), ErrnoException.class, EINVAL, |
| "setenv(\"a=b\", value, true)"); |
| |
| expectException(() -> Os.setenv(null, null, false), NullPointerException.class, null, |
| "setenv(null, null, false)"); |
| |
| expectException(() -> Os.setenv(null, "value", false), NullPointerException.class, null, |
| "setenv(null, value, false)"); |
| |
| expectException(() -> Os.setenv("a", null, false), NullPointerException.class, null, |
| "setenv(\"a\", null, false)"); |
| |
| expectException(() -> Os.setenv("", "value", false), ErrnoException.class, EINVAL, |
| "setenv(\"\", value, false)"); |
| |
| expectException(() -> Os.setenv("a=b", "value", false), ErrnoException.class, EINVAL, |
| "setenv(\"a=b\", value, false)"); |
| } |
| |
| @Test |
| public void getenv() { |
| assertNotNull(Os.getenv("PATH")); |
| assertNull(Os.getenv("This can't possibly exist but is valid")); |
| assertNull(Os.getenv("so=is=this")); |
| |
| expectException(() ->Os.getenv(null), NullPointerException.class, null, |
| "getenv(null)"); |
| } |
| |
| @Test |
| public void unsetenv() { |
| expectException(() -> Os.unsetenv(null), NullPointerException.class, null, |
| "unsetenv(null)"); |
| |
| expectException(() -> Os.unsetenv(""), ErrnoException.class, EINVAL, |
| "unsetenv(\"\")"); |
| |
| expectException(() -> Os.unsetenv("a=b"), ErrnoException.class, EINVAL, |
| "unsetenv(\"a=b\")"); |
| } |
| |
| @Test |
| public void tcdrain_throwsWhenFdIsNotTty() throws Exception { |
| File temp = Files.createTempFile("regular-file", "txt").toFile(); |
| FileDescriptor fd = Os.open(temp.getAbsolutePath(), O_RDWR, 0); |
| |
| try { |
| assertThrows(ErrnoException.class, () -> Os.tcdrain(fd)); |
| } finally { |
| if (fd != null) { |
| Os.close(fd); |
| } |
| } |
| } |
| |
| @Test |
| public void testMsync() throws Exception { |
| File temp = Files.createTempFile("regular-file", "txt").toFile(); |
| |
| try (FileOutputStream fos = new FileOutputStream(temp)) { |
| fos.write("hello".getBytes(StandardCharsets.UTF_8)); |
| fos.flush(); |
| } |
| |
| final long size = 5; |
| FileDescriptor fd = Os.open(temp.getAbsolutePath(), O_RDWR, 0); |
| final long address = Os.mmap(0, size, PROT_WRITE, |
| MAP_SHARED, fd, 0); |
| assertTrue(address > 0); |
| |
| // The easiest way to write at address from Java I came up with. |
| var field = Unsafe.class.getDeclaredField("THE_ONE"); |
| field.setAccessible(true); |
| Unsafe unsafe = (Unsafe) field.get(null); |
| unsafe.setMemory(address, size, (byte) 'a'); |
| |
| Os.msync(address, size, MS_SYNC); |
| |
| try (Stream<String> stream = Files.lines(temp.toPath())) { |
| List<String> lines = stream.toList(); |
| assertEquals(1, lines.size()); |
| assertEquals("a".repeat((int) size), lines.get(0)); |
| } |
| |
| Os.munmap(address, size); |
| } |
| |
| /* |
| * Checks that all ways of accessing the environment are consistent by collecting: |
| * osEnvironment - The environment returned by Os.environ() |
| * systemEnvironment - The environment returned by System.getenv() |
| * processEnvironment - The environment that will be passed to sub-processes via |
| * ProcessBuilder |
| * execedEnvironment - The actual environment passed to an execed instance of env |
| * |
| * All are converted to a sorted list of strings of the form "NAME=VALUE" for comparison. |
| */ |
| private void assertEnvironmentsEqual() throws IOException { |
| List<String> osEnvironment = stringArrayToList(Os.environ()); |
| List<String> systemEnvironment = stringMapToList(System.getenv()); |
| |
| ProcessBuilder pb = new ProcessBuilder("env"); |
| List<String> processEnvironment = stringMapToList(pb.environment()); |
| |
| BufferedReader reader = new BufferedReader( |
| new InputStreamReader(pb.start().getInputStream())); |
| |
| List<String> execedEnvironment = reader |
| .lines() |
| .sorted() |
| .collect(Collectors.toList()); |
| |
| assertEquals(osEnvironment, systemEnvironment); |
| assertEquals(osEnvironment, processEnvironment); |
| assertEquals(osEnvironment, execedEnvironment); |
| } |
| |
| private static void assertStructStatsEqual(StructStat expected, StructStat actual) { |
| assertEquals(expected.st_dev, actual.st_dev); |
| assertEquals(expected.st_ino, actual.st_ino); |
| assertEquals(expected.st_mode, actual.st_mode); |
| assertEquals(expected.st_nlink, actual.st_nlink); |
| assertEquals(expected.st_uid, actual.st_uid); |
| assertEquals(expected.st_gid, actual.st_gid); |
| assertEquals(expected.st_rdev, actual.st_rdev); |
| assertEquals(expected.st_size, actual.st_size); |
| assertEquals(expected.st_atime, actual.st_atime); |
| assertEquals(expected.st_atim, actual.st_atim); |
| assertEquals(expected.st_mtime, actual.st_mtime); |
| assertEquals(expected.st_mtim, actual.st_mtim); |
| assertEquals(expected.st_ctime, actual.st_ctime); |
| assertEquals(expected.st_ctim, actual.st_ctim); |
| assertEquals(expected.st_blksize, actual.st_blksize); |
| assertEquals(expected.st_blocks, actual.st_blocks); |
| } |
| |
| private List<String> stringMapToList(Map<String, String> stringMap) { |
| return stringMap |
| .entrySet() |
| .stream() |
| .map(e -> e.getKey() + "=" + e.getValue()) |
| .sorted() |
| .collect(Collectors.toList()); |
| } |
| |
| private List<String> stringArrayToList(String[] stringArray) { |
| List<String> result = Arrays.asList(stringArray); |
| Collections.sort(result); |
| return result; |
| } |
| } |