diff --git a/CHANGES.md b/CHANGES.md index 7a1a2b18b..7cf8fb997 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Features * [#1718](https://github.com/java-native-access/jna/pull/1718): Add `Cups` to `c.s.j.p.unix` providing CUPS printing system bindings for destinations, jobs, options, and server configuration - [@dbwiddis](https://github.com/dbwiddis). * [#1720](https://github.com/java-native-access/jna/pull/1720): Add `groupCount` and `groupMasks` fields to `CACHE_RELATIONSHIP` in `c.s.j.p.win32.WinNT`, matching the updated Windows struct layout - [@dbwiddis](https://github.com/dbwiddis). * [#1719](https://github.com/java-native-access/jna/pull/1719): Add `CoreGraphics` to `c.s.j.p.mac` with Quartz Window Services and Display Services bindings; implement `getAllWindows()` in `MacWindowUtils` - [@dbwiddis](https://github.com/dbwiddis). +* [#1723](https://github.com/java-native-access/jna/pull/1723): Add `ProcFdInfo`, `InSockInfo`, `TcpSockInfo`, `proc_pidfdinfo`, `statfs64`, and `vm_deallocate` to `c.s.j.p.mac.SystemB` - [@dbwiddis](https://github.com/dbwiddis). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/mac/SystemB.java b/contrib/platform/src/com/sun/jna/platform/mac/SystemB.java index 04e5459dd..3ac513d0e 100644 --- a/contrib/platform/src/com/sun/jna/platform/mac/SystemB.java +++ b/contrib/platform/src/com/sun/jna/platform/mac/SystemB.java @@ -30,6 +30,7 @@ import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.Structure; +import com.sun.jna.Union; import com.sun.jna.platform.unix.LibCAPI; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.LongByReference; @@ -298,6 +299,245 @@ class RUsageInfoV2 extends Structure { public long ri_diskio_byteswritten; } + // proc_info.h: Flavors for proc_pidinfo and proc_pidfdinfo + int PROC_PIDLISTFDS = 1; + int PROC_PIDFDSOCKETINFO = 3; + + // proc_info.h: File descriptor types + int PROX_FDTYPE_SOCKET = 2; + + // proc_info.h: Socket info kinds + int SOCKINFO_IN = 1; + int SOCKINFO_TCP = 2; + + // proc_info.h: TCP timer count + int TSI_T_NTIMERS = 4; + + // socket.h: Address families + int AF_INET = 2; + int AF_INET6 = 30; + + /** + * File descriptor information as returned by {@code proc_pidinfo} with + * {@link #PROC_PIDLISTFDS}. + *

+ * Corresponds to {@code struct proc_fdinfo} in {@code }. + */ + @Structure.FieldOrder({ "proc_fd", "proc_fdtype" }) + class ProcFdInfo extends Structure { + public int proc_fd; + public int proc_fdtype; + } + + /** + * IPv4 address mapped into IPv6 address space. + *

+ * Corresponds to {@code struct in4in6_addr} in {@code }. + */ + @Structure.FieldOrder({ "i46a_pad32", "i46a_addr4" }) + class In4In6Addr extends Structure { + /** Padding to align IPv4 address at offset 12. */ + public int[] i46a_pad32 = new int[3]; + /** IPv4 address (network byte order). */ + public int i46a_addr4; + } + + /** + * IPv6 address (128 bits). + *

+ * Corresponds to {@code struct in6_addr} in {@code }. + */ + @Structure.FieldOrder({ "__u6_addr" }) + class In6Addr extends Structure { + /** 16-byte IPv6 address. */ + public byte[] __u6_addr = new byte[16]; + } + + /** + * Union of IPv4-mapped-in-IPv6 and IPv6 addresses, used in + * {@link InSockInfo}. + *

+ * Corresponds to the anonymous union containing {@code ina_46} and + * {@code ina_6} in {@code struct in_sockinfo}. + */ + class InsiAddr extends Union { + public In4In6Addr ina_46; + public In6Addr ina_6; + } + + /** + * Internet socket information. + *

+ * Corresponds to {@code struct in_sockinfo} in {@code }. + */ + @Structure.FieldOrder({ "insi_fport", "insi_lport", "insi_gencnt", "insi_flags", "insi_flow", "insi_vflag", + "insi_ip_ttl", "rfu_1", "insi_faddr", "insi_laddr", "insi_v4", "insi_v6" }) + class InSockInfo extends Structure { + /** Foreign port. */ + public int insi_fport; + /** Local port. */ + public int insi_lport; + /** Generation count of this instance. */ + public long insi_gencnt; + /** Generic IP/datagram flags. */ + public int insi_flags; + public int insi_flow; + /** INI_IPV4 or INI_IPV6. */ + public byte insi_vflag; + /** Time to live proto. */ + public byte insi_ip_ttl; + /** Reserved. */ + public int rfu_1; + /** Foreign host table entry. */ + public InsiAddr insi_faddr; + /** Local host table entry. */ + public InsiAddr insi_laddr; + /** IPv4 type of service. */ + public byte insi_v4; + /** IPv6 info (in6_hlim, in6_cksum, in6_ifindex, in6_hops). */ + public byte[] insi_v6 = new byte[9]; + + @Override + public void read() { + super.read(); + if (insi_vflag == 2) { + insi_faddr.setType("ina_6"); + insi_laddr.setType("ina_6"); + } else { + insi_faddr.setType("ina_46"); + insi_laddr.setType("ina_46"); + } + insi_faddr.read(); + insi_laddr.read(); + } + } + + /** + * TCP socket information. + *

+ * Corresponds to {@code struct tcp_sockinfo} in {@code }. + */ + @Structure.FieldOrder({ "tcpsi_ini", "tcpsi_state", "tcpsi_timer", "tcpsi_mss", "tcpsi_flags", "rfu_1", + "tcpsi_tp" }) + class TcpSockInfo extends Structure { + public InSockInfo tcpsi_ini; + public int tcpsi_state; + public int[] tcpsi_timer = new int[TSI_T_NTIMERS]; + public int tcpsi_mss; + public int tcpsi_flags; + /** Reserved. */ + public int rfu_1; + /** Opaque handle of TCP protocol control block. */ + public long tcpsi_tp; + } + + /** + * Per-file descriptor information. + *

+ * Corresponds to {@code struct proc_fileinfo} in {@code }. + */ + @Structure.FieldOrder({ "fi_openflags", "fi_status", "fi_offset", "fi_type", "fi_guardflags" }) + class ProcFileInfo extends Structure { + public int fi_openflags; + public int fi_status; + public long fi_offset; + public int fi_type; + public int fi_guardflags; + } + + /** + * Socket buffer information. + *

+ * Corresponds to {@code struct sockbuf_info} in {@code }. + */ + @Structure.FieldOrder({ "sbi_cc", "sbi_hiwat", "sbi_mbcnt", "sbi_mbmax", "sbi_lowat", "sbi_flags", + "sbi_timeo" }) + class SockbufInfo extends Structure { + public int sbi_cc; + public int sbi_hiwat; + public int sbi_mbcnt; + public int sbi_mbmax; + public int sbi_lowat; + public short sbi_flags; + public short sbi_timeo; + } + + /** + * Union of protocol-specific socket information in {@link SocketInfo}. + *

+ * Corresponds to the {@code soi_proto} union in + * {@code struct socket_info}. + */ + class Pri extends Union { + public InSockInfo pri_in; + public TcpSockInfo pri_tcp; + /** Ensures the union is large enough for all protocol variants. */ + public byte[] max_size = new byte[524]; + } + + /** + * Socket information. + *

+ * Corresponds to {@code struct socket_info} in {@code }. + * The {@code soi_stat} field ({@code struct vinfo_stat}) is mapped as a + * 136-byte opaque region. + */ + @Structure.FieldOrder({ "soi_stat", "soi_so", "soi_pcb", "soi_type", "soi_protocol", "soi_family", + "soi_options", "soi_linger", "soi_state", "soi_qlen", "soi_incqlen", "soi_qlimit", "soi_timeo", + "soi_error", "soi_oobmark", "soi_rcv", "soi_snd", "soi_kind", "rfu_1", "soi_proto" }) + class SocketInfo extends Structure { + /** Opaque {@code vinfo_stat} (136 bytes). */ + public byte[] soi_stat = new byte[136]; + /** Opaque handle of socket. */ + public long soi_so; + /** Opaque handle of protocol control block. */ + public long soi_pcb; + public int soi_type; + public int soi_protocol; + public int soi_family; + public short soi_options; + public short soi_linger; + public short soi_state; + public short soi_qlen; + public short soi_incqlen; + public short soi_qlimit; + public short soi_timeo; + public short soi_error; + public int soi_oobmark; + public SockbufInfo soi_rcv; + public SockbufInfo soi_snd; + public int soi_kind; + /** Reserved. */ + public int rfu_1; + public Pri soi_proto; + + @Override + public void read() { + super.read(); + if (soi_kind == SOCKINFO_TCP) { + soi_proto.setType("pri_tcp"); + } else if (soi_kind == SOCKINFO_IN) { + soi_proto.setType("pri_in"); + } else { + soi_proto.setType("max_size"); + } + soi_proto.read(); + } + } + + /** + * Socket file descriptor information as returned by + * {@code proc_pidfdinfo} with {@link #PROC_PIDFDSOCKETINFO}. + *

+ * Corresponds to {@code struct socket_fdinfo} in + * {@code }. + */ + @Structure.FieldOrder({ "pfi", "psi" }) + class SocketFdInfo extends Structure { + public ProcFileInfo pfi; + public SocketInfo psi; + } + @Structure.FieldOrder({ "vip_vi", "vip_path" }) class VnodeInfoPath extends Structure { public byte[] vip_vi = new byte[152]; // vnode_info but we don't @@ -877,4 +1117,51 @@ int host_processor_info(int hostPort, int flavor, IntByReference procCount, Poin * @return the process ID of the calling process. */ int getpid(); + + /** + * Returns information about a file descriptor of a process. + * + * @param pid + * the process identifier + * @param fd + * the file descriptor + * @param flavor + * the type of information requested (e.g., + * {@link #PROC_PIDFDSOCKETINFO}) + * @param buffer + * holds results + * @param buffersize + * size of results + * @return the number of bytes of data returned in the provided buffer; -1 if an + * error was encountered + */ + int proc_pidfdinfo(int pid, int fd, int flavor, Structure buffer, int buffersize); + + /** + * The statfs64() routine returns information about a mounted file system. + * The {@code path} argument is the path name of any file or directory within + * the mounted file system. The {@code buf} argument is a pointer to a + * {@code statfs} structure. + * + * @param path + * the path to any file within the mounted filesystem + * @param buf + * a {@link Statfs} structure + * @return 0 on success; -1 on failure (sets errno) + */ + int statfs64(String path, Statfs buf); + + /** + * Deallocates a region of virtual memory in the specified task. + * + * @param targetTask + * the target task (typically from {@link #mach_task_self()}) + * @param address + * the starting address of the region to deallocate + * @param size + * the number of bytes to deallocate + * @return 0 ({@code KERN_SUCCESS}) on success; a {@code kern_return_t} error + * code otherwise + */ + int vm_deallocate(int targetTask, long address, long size); } diff --git a/contrib/platform/test/com/sun/jna/platform/mac/SystemBTest.java b/contrib/platform/test/com/sun/jna/platform/mac/SystemBTest.java index b658eefb5..9f41efb81 100644 --- a/contrib/platform/test/com/sun/jna/platform/mac/SystemBTest.java +++ b/contrib/platform/test/com/sun/jna/platform/mac/SystemBTest.java @@ -38,8 +38,10 @@ import com.sun.jna.platform.mac.SystemB.IFmsgHdr; import com.sun.jna.platform.mac.SystemB.IFmsgHdr2; import com.sun.jna.platform.mac.SystemB.Passwd; +import com.sun.jna.platform.mac.SystemB.ProcFdInfo; import com.sun.jna.platform.mac.SystemB.ProcTaskAllInfo; import com.sun.jna.platform.mac.SystemB.RUsageInfoV2; +import com.sun.jna.platform.mac.SystemB.SocketFdInfo; import com.sun.jna.platform.mac.SystemB.Statfs; import com.sun.jna.platform.mac.SystemB.Timeval; import com.sun.jna.platform.mac.SystemB.Timezone; @@ -171,6 +173,12 @@ public void testHostProcessorInfo() { assertTrue(procCount.getValue() > 0); assertEquals(procCpuLoadInfo.getValue().getIntArray(0, procInfoCount.getValue()).length, procInfoCount.getValue()); + + // Deallocate the memory allocated by host_processor_info + int taskSelf = SystemB.INSTANCE.mach_task_self(); + ret = SystemB.INSTANCE.vm_deallocate(taskSelf, Pointer.nativeValue(procCpuLoadInfo.getValue()), + (long) procInfoCount.getValue() * SystemB.INT_SIZE); + assertEquals(0, ret); } // From Unix LibCAPI @@ -366,6 +374,82 @@ public void testIFs() { } } + public void testProcPidFdInfo() { + int pid = SystemB.INSTANCE.getpid(); + + // Get the list of open file descriptors for this process + int bufferSize = SystemB.INSTANCE.proc_pidinfo(pid, SystemB.PROC_PIDLISTFDS, 0, null, 0); + assertTrue(bufferSize > 0); + + int numFds = bufferSize / new ProcFdInfo().size(); + assertTrue(numFds > 0); + + ProcFdInfo[] fdInfoArray = (ProcFdInfo[]) new ProcFdInfo().toArray(numFds); + int ret = SystemB.INSTANCE.proc_pidinfo(pid, SystemB.PROC_PIDLISTFDS, 0, fdInfoArray[0], + bufferSize); + assertTrue(ret > 0); + + // Verify we got valid fd entries + assertTrue(fdInfoArray[0].proc_fd >= 0); + assertTrue(fdInfoArray[0].proc_fdtype >= 0); + } + + public void testProcPidFdSocketInfo() throws Exception { + int pid = SystemB.INSTANCE.getpid(); + + // Open a socket so we are guaranteed to have one + java.net.ServerSocket serverSocket = new java.net.ServerSocket(0); + + try { + // Get the list of open file descriptors + int bufferSize = SystemB.INSTANCE.proc_pidinfo(pid, SystemB.PROC_PIDLISTFDS, 0, null, 0); + assertTrue(bufferSize > 0); + + int numFds = bufferSize / new ProcFdInfo().size(); + ProcFdInfo[] fdInfoArray = (ProcFdInfo[]) new ProcFdInfo().toArray(numFds); + int ret = SystemB.INSTANCE.proc_pidinfo(pid, SystemB.PROC_PIDLISTFDS, 0, fdInfoArray[0], + bufferSize); + assertTrue(ret > 0); + + // Find a socket fd and query its info with proc_pidfdinfo + boolean foundSocket = false; + SystemB.SocketFdInfo socketFdInfo = new SystemB.SocketFdInfo(); + for (int i = 0; i < numFds; i++) { + if (fdInfoArray[i].proc_fdtype == SystemB.PROX_FDTYPE_SOCKET) { + ret = SystemB.INSTANCE.proc_pidfdinfo(pid, fdInfoArray[i].proc_fd, + SystemB.PROC_PIDFDSOCKETINFO, socketFdInfo, socketFdInfo.size()); + if (ret == socketFdInfo.size()) { + foundSocket = true; + assertTrue(socketFdInfo.psi.soi_family == SystemB.AF_INET + || socketFdInfo.psi.soi_family == SystemB.AF_INET6 + || socketFdInfo.psi.soi_family > 0); + assertTrue(socketFdInfo.psi.soi_kind >= 0); + + // Unions are read automatically by SocketInfo.read() and InSockInfo.read() + if (socketFdInfo.psi.soi_kind == SystemB.SOCKINFO_TCP) { + SystemB.InSockInfo ini = socketFdInfo.psi.soi_proto.pri_tcp.tcpsi_ini; + assertTrue(ini.insi_lport >= 0); + } else if (socketFdInfo.psi.soi_kind == SystemB.SOCKINFO_IN) { + SystemB.InSockInfo ini = socketFdInfo.psi.soi_proto.pri_in; + assertTrue(ini.insi_lport >= 0); + } + break; + } + } + } + assertTrue("Expected to find at least one socket fd", foundSocket); + } finally { + serverSocket.close(); + } + } + + public void testStatfs64() { + Statfs buf = new Statfs(); + assertEquals(0, SystemB.INSTANCE.statfs64("/", buf)); + assertTrue(buf.f_blocks > 0); + assertTrue(buf.f_bsize > 0); + } + public static void main(java.lang.String[] argList) { junit.textui.TestRunner.run(SystemBTest.class); }