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);
}