diff --git a/build.gradle b/build.gradle index 6721a5d9f..5369183ec 100644 --- a/build.gradle +++ b/build.gradle @@ -73,8 +73,7 @@ dependencies { implementation libs.snakeyaml.engine // YAML processing implementation libs.tomlj // TOML processing implementation libs.commons.csv // CSV processing - implementation libs.jna // Native access - implementation libs.jna.platform // Native access + implementation libs.jnr.posix // Native access // Testing dependencies testImplementation libs.junit.jupiter.api diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d69bef39a..49fcaae49 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ asm = "9.9.1" commons-csv = "1.14.1" fastjson2 = "2.0.61" icu4j = "78.2" -jna = "5.18.1" +jnr-posix = "3.1.19" junit-jupiter = "6.1.0-M1" snakeyaml-engine = "3.0.1" tomlj = "1.1.1" @@ -14,8 +14,7 @@ asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } commons-csv = { module = "org.apache.commons:commons-csv", version.ref = "commons-csv" } fastjson2 = { module = "com.alibaba.fastjson2:fastjson2", version.ref = "fastjson2" } icu4j = { module = "com.ibm.icu:icu4j", version.ref = "icu4j" } -jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } -jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" } +jnr-posix = { module = "com.github.jnr:jnr-posix", version.ref = "jnr-posix" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" } diff --git a/jperl.bat b/jperl.bat index d005e60da..9a04e1b1e 100755 --- a/jperl.bat +++ b/jperl.bat @@ -14,4 +14,4 @@ rem Set environment variable for PerlOnJava to use as $^X set PERLONJAVA_EXECUTABLE=%JPERL_PATH% rem Launch Java -java %JPERL_OPTS% -cp "%CLASSPATH%;%SCRIPT_DIR%target\perlonjava-3.0.0.jar" org.perlonjava.app.cli.Main %* +java --enable-native-access=ALL-UNNAMED %JPERL_OPTS% -cp "%CLASSPATH%;%SCRIPT_DIR%target\perlonjava-3.0.0.jar" org.perlonjava.app.cli.Main %* diff --git a/pom.xml b/pom.xml index 11c7761ff..2fb63237e 100644 --- a/pom.xml +++ b/pom.xml @@ -71,14 +71,9 @@ 1.14.1 - net.java.dev.jna - jna - 5.18.1 - - - net.java.dev.jna - jna-platform - 5.18.1 + com.github.jnr + jnr-posix + 3.1.19 diff --git a/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java b/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java index f0b9a2ceb..83e7a6137 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ExtendedNativeUtils.java @@ -3,8 +3,7 @@ import org.perlonjava.frontend.parser.StringParser; import org.perlonjava.runtime.runtimetypes.*; -import com.sun.jna.Platform; -import com.sun.jna.Pointer; +import jnr.posix.Passwd; import java.net.InetAddress; import java.nio.charset.StandardCharsets; @@ -13,17 +12,12 @@ import static org.perlonjava.runtime.runtimetypes.RuntimeContextType.SCALAR; -/** - * Extended native operations for missing Perl operators - */ public class ExtendedNativeUtils extends NativeUtils { - // Cache for network and user info lookups private static final Map userInfoCache = new ConcurrentHashMap<>(); private static final Map groupInfoCache = new ConcurrentHashMap<>(); private static final Map hostInfoCache = new ConcurrentHashMap<>(); - // Thread-local iterator state for *ent functions private static final ThreadLocal> userIterator = new ThreadLocal<>(); private static final ThreadLocal> groupIterator = new ThreadLocal<>(); private static final ThreadLocal> hostIterator = new ThreadLocal<>(); @@ -31,7 +25,6 @@ public class ExtendedNativeUtils extends NativeUtils { private static final ThreadLocal> protoIterator = new ThreadLocal<>(); private static final ThreadLocal> servIterator = new ThreadLocal<>(); - // System V IPC structures simulation private static final Map messageQueues = new ConcurrentHashMap<>(); private static final Map semaphores = new ConcurrentHashMap<>(); private static final Map sharedMemory = new ConcurrentHashMap<>(); @@ -39,9 +32,6 @@ public class ExtendedNativeUtils extends NativeUtils { // ================== User/Group Information Functions ================== - /** - * Get login name of current user - */ public static RuntimeScalar getlogin(int ctx, RuntimeBase... args) { try { String username = System.getProperty("user.name"); @@ -51,25 +41,19 @@ public static RuntimeScalar getlogin(int ctx, RuntimeBase... args) { } } - private static String ptrString(Pointer p, long offset) { - Pointer s = p.getPointer(offset); - return s != null ? s.getString(0) : ""; - } - - private static RuntimeList passwdPointerToList(Pointer pw) { + private static RuntimeList passwdToList(Passwd pw) { if (pw == null) return new RuntimeList(); RuntimeArray result = new RuntimeArray(); - String name = ptrString(pw, 0); - String passwd = ptrString(pw, 8); - int uid = pw.getInt(16); - int gid = pw.getInt(20); - if (Platform.isMac()) { - // macOS struct passwd: name(0) passwd(8) uid(16) gid(20) change(24) class(32) gecos(40) dir(48) shell(56) expire(64) - long change = pw.getLong(24); - String gecos = ptrString(pw, 40); - String dir = ptrString(pw, 48); - String shell = ptrString(pw, 56); - long expire = pw.getLong(64); + String name = pw.getLoginName(); + String passwd = pw.getPassword(); + int uid = (int) pw.getUID(); + int gid = (int) pw.getGID(); + if (IS_MAC) { + long change = pw.getPasswdChangeTime(); + String gecos = pw.getGECOS(); + String dir = pw.getHome(); + String shell = pw.getShell(); + long expire = pw.getExpire(); RuntimeArray.push(result, new RuntimeScalar(name)); RuntimeArray.push(result, new RuntimeScalar(passwd)); RuntimeArray.push(result, new RuntimeScalar(uid)); @@ -81,10 +65,9 @@ private static RuntimeList passwdPointerToList(Pointer pw) { RuntimeArray.push(result, new RuntimeScalar(shell)); RuntimeArray.push(result, new RuntimeScalar(expire)); } else { - // Linux struct passwd: name(0) passwd(8) uid(16) gid(20) gecos(24) dir(32) shell(40) - String gecos = ptrString(pw, 24); - String dir = ptrString(pw, 32); - String shell = ptrString(pw, 40); + String gecos = pw.getGECOS(); + String dir = pw.getHome(); + String shell = pw.getShell(); RuntimeArray.push(result, new RuntimeScalar(name)); RuntimeArray.push(result, new RuntimeScalar(passwd)); RuntimeArray.push(result, new RuntimeScalar(uid)); @@ -100,26 +83,26 @@ private static RuntimeList passwdPointerToList(Pointer pw) { } private static RuntimeList nativeGetpwnam(String username) { - Pointer pw = PosixLibrary.INSTANCE.getpwnam(username); - return passwdPointerToList(pw); + Passwd pw = PosixLibrary.INSTANCE.getpwnam(username); + return passwdToList(pw); } private static RuntimeList nativeGetpwuid(int uid) { - Pointer pw = PosixLibrary.INSTANCE.getpwuid(uid); - return passwdPointerToList(pw); + Passwd pw = PosixLibrary.INSTANCE.getpwuid(uid); + return passwdToList(pw); } public static RuntimeList getpwnam(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeList(); String username = args[0].toString(); - if (Platform.isWindows()) { + if (IS_WINDOWS) { if (ctx == RuntimeContextType.SCALAR) return new RuntimeList(); return windowsGetpwnam(username); } try { if (ctx == RuntimeContextType.SCALAR) { - Pointer pw = PosixLibrary.INSTANCE.getpwnam(username); - if (pw != null) return new RuntimeScalar(pw.getInt(16)).getList(); + Passwd pw = PosixLibrary.INSTANCE.getpwnam(username); + if (pw != null) return new RuntimeScalar((int) pw.getUID()).getList(); return new RuntimeList(); } return nativeGetpwnam(username); @@ -131,14 +114,14 @@ public static RuntimeList getpwnam(int ctx, RuntimeBase... args) { public static RuntimeList getpwuid(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeList(); int uid = args[0].scalar().getInt(); - if (Platform.isWindows()) { + if (IS_WINDOWS) { if (ctx == RuntimeContextType.SCALAR) return new RuntimeList(); return windowsGetpwuid(uid); } try { if (ctx == RuntimeContextType.SCALAR) { - Pointer pw = PosixLibrary.INSTANCE.getpwuid(uid); - if (pw != null) return new RuntimeScalar(ptrString(pw, 0)).getList(); + Passwd pw = PosixLibrary.INSTANCE.getpwuid(uid); + if (pw != null) return new RuntimeScalar(pw.getLoginName()).getList(); return new RuntimeList(); } return nativeGetpwuid(uid); @@ -175,9 +158,6 @@ private static RuntimeList windowsGetpwuid(int uid) { return new RuntimeList(); } - /** - * Get group entry by name - */ public static RuntimeArray getgrnam(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeArray(); String groupname = args[0].toString(); @@ -189,20 +169,17 @@ public static RuntimeArray getgrnam(int ctx, RuntimeBase... args) { RuntimeArray result = new RuntimeArray(); try { - // Simplified group info - in real implementation would query system if (IS_WINDOWS) { - // Use computer workgroup or domain String computerName = System.getenv("COMPUTERNAME"); if (groupname.equals("Users") || groupname.equals(computerName)) { - RuntimeArray.push(result, new RuntimeScalar(groupname)); // name - RuntimeArray.push(result, new RuntimeScalar("x")); // passwd - RuntimeArray.push(result, getgid(SCALAR)); // gid + RuntimeArray.push(result, new RuntimeScalar(groupname)); + RuntimeArray.push(result, new RuntimeScalar("x")); + RuntimeArray.push(result, getgid(SCALAR)); RuntimeArray members = new RuntimeArray(); RuntimeArray.push(members, new RuntimeScalar(System.getProperty("user.name"))); - RuntimeArray.push(result, members); // members + RuntimeArray.push(result, members); } } else { - // POSIX - simplified if (groupname.equals("users") || groupname.equals(System.getProperty("user.name"))) { RuntimeArray.push(result, new RuntimeScalar(groupname)); RuntimeArray.push(result, new RuntimeScalar("x")); @@ -215,15 +192,11 @@ public static RuntimeArray getgrnam(int ctx, RuntimeBase... args) { groupInfoCache.put(cacheKey, result); } catch (Exception e) { - // Return empty on error } return result; } - /** - * Get group entry by GID - */ public static RuntimeArray getgrgid(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeArray(); @@ -239,14 +212,14 @@ public static RuntimeArray getgrgid(int ctx, RuntimeBase... args) { } public static RuntimeList getpwent(int ctx, RuntimeBase... args) { - if (Platform.isWindows()) return new RuntimeList(); + if (IS_WINDOWS) return new RuntimeList(); try { - Pointer pw = PosixLibrary.INSTANCE.getpwent(); + Passwd pw = PosixLibrary.INSTANCE.getpwent(); if (pw == null) return new RuntimeList(); if (ctx == RuntimeContextType.SCALAR) { - return new RuntimeScalar(ptrString(pw, 0)).getList(); + return new RuntimeScalar(pw.getLoginName()).getList(); } - return passwdPointerToList(pw); + return passwdToList(pw); } catch (Exception e) { return new RuntimeList(); } @@ -268,7 +241,7 @@ public static RuntimeArray getgrent(int ctx, RuntimeBase... args) { } public static RuntimeScalar setpwent(int ctx, RuntimeBase... args) { - if (!Platform.isWindows()) { + if (!IS_WINDOWS) { try { PosixLibrary.INSTANCE.setpwent(); } catch (Exception e) { } } userIterator.remove(); @@ -277,13 +250,13 @@ public static RuntimeScalar setpwent(int ctx, RuntimeBase... args) { } public static RuntimeScalar setgrent(int ctx, RuntimeBase... args) { - groupIterator.remove(); // Clear this thread's iterator + groupIterator.remove(); groupInfoCache.clear(); return new RuntimeScalar(1); } public static RuntimeScalar endpwent(int ctx, RuntimeBase... args) { - if (!Platform.isWindows()) { + if (!IS_WINDOWS) { try { PosixLibrary.INSTANCE.endpwent(); } catch (Exception e) { } } userIterator.remove(); @@ -291,15 +264,12 @@ public static RuntimeScalar endpwent(int ctx, RuntimeBase... args) { } public static RuntimeScalar endgrent(int ctx, RuntimeBase... args) { - groupIterator.remove(); // Clear this thread's iterator + groupIterator.remove(); return new RuntimeScalar(1); } // ================== Network Information Functions ================== - /** - * Get host information by name - */ public static RuntimeArray gethostbyname(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeArray(); String hostname = args[0].toString(); @@ -307,9 +277,7 @@ public static RuntimeArray gethostbyname(int ctx, RuntimeBase... args) { String cacheKey = "host:" + hostname; if (hostInfoCache.containsKey(cacheKey)) { RuntimeArray cached = hostInfoCache.get(cacheKey); - // In scalar context, return the packed IP address (4th element) if (ctx == SCALAR && cached.size() >= 5) { - // The packed IP address is now directly at index 4 RuntimeBase packedAddress = cached.get(4); RuntimeArray scalarResult = new RuntimeArray(); RuntimeArray.push(scalarResult, packedAddress); @@ -322,34 +290,28 @@ public static RuntimeArray gethostbyname(int ctx, RuntimeBase... args) { try { InetAddress addr = InetAddress.getByName(hostname); - RuntimeArray.push(result, new RuntimeScalar(addr.getHostName())); // name - RuntimeArray aliases = new RuntimeArray(); // aliases (empty for now) + RuntimeArray.push(result, new RuntimeScalar(addr.getHostName())); + RuntimeArray aliases = new RuntimeArray(); RuntimeArray.push(result, aliases); - RuntimeArray.push(result, new RuntimeScalar(2)); // addrtype (AF_INET) - RuntimeArray.push(result, new RuntimeScalar(4)); // length (IPv4 = 4 bytes) + RuntimeArray.push(result, new RuntimeScalar(2)); + RuntimeArray.push(result, new RuntimeScalar(4)); - // Add the packed IP addresses as individual elements (not as an array) RuntimeScalar packedAddress = new RuntimeScalar(addr.getAddress()); - RuntimeArray.push(result, packedAddress); // packed address + RuntimeArray.push(result, packedAddress); hostInfoCache.put(cacheKey, result); - // In scalar context, return the packed IP address if (ctx == SCALAR) { RuntimeArray scalarResult = new RuntimeArray(); RuntimeArray.push(scalarResult, packedAddress); return scalarResult; } } catch (Exception e) { - // Return empty array on error } return result; } - /** - * Get host information by address - */ public static RuntimeArray gethostbyaddr(int ctx, RuntimeBase... args) { if (args.length < 2) return new RuntimeArray(); @@ -359,10 +321,8 @@ public static RuntimeArray gethostbyaddr(int ctx, RuntimeBase... args) { String addrStr = args[0].toString(); StringParser.assertNoWideCharacters(addrStr, "gethostbyaddr"); if (addrStr.length() == 4) { - // Packed address addr = addrStr.getBytes(StandardCharsets.ISO_8859_1); } else { - // String IP address String[] parts = addrStr.split("\\."); addr = new byte[4]; for (int i = 0; i < 4 && i < parts.length; i++) { @@ -377,9 +337,9 @@ public static RuntimeArray gethostbyaddr(int ctx, RuntimeBase... args) { RuntimeArray result = new RuntimeArray(); RuntimeArray.push(result, new RuntimeScalar(inetAddr.getHostName())); - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(2)); // addrtype - RuntimeArray.push(result, new RuntimeScalar(4)); // length + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(2)); + RuntimeArray.push(result, new RuntimeScalar(4)); RuntimeArray addresses = new RuntimeArray(); RuntimeArray.push(addresses, new RuntimeScalar(new String(addr, StandardCharsets.ISO_8859_1))); @@ -393,16 +353,12 @@ public static RuntimeArray gethostbyaddr(int ctx, RuntimeBase... args) { } } - /** - * Get service information by name and protocol - */ public static RuntimeArray getservbyname(int ctx, RuntimeBase... args) { if (args.length < 2) return new RuntimeArray(); String service = args[0].toString(); String protocol = args[1].toString(); - // Common services mapping Map commonPorts = Map.of( "http", 80, "https", 443, "ftp", 21, "ssh", 22, "telnet", 23, "smtp", 25, "dns", 53, "pop3", 110, @@ -412,25 +368,21 @@ public static RuntimeArray getservbyname(int ctx, RuntimeBase... args) { RuntimeArray result = new RuntimeArray(); Integer port = commonPorts.get(service.toLowerCase()); if (port != null) { - RuntimeArray.push(result, new RuntimeScalar(service)); // name - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(port)); // port - RuntimeArray.push(result, new RuntimeScalar(protocol)); // proto + RuntimeArray.push(result, new RuntimeScalar(service)); + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(port)); + RuntimeArray.push(result, new RuntimeScalar(protocol)); } return result; } - /** - * Get service information by port and protocol - */ public static RuntimeArray getservbyport(int ctx, RuntimeBase... args) { if (args.length < 2) return new RuntimeArray(); int port = args[0].scalar().getInt(); String protocol = args[1].toString(); - // Reverse lookup of common ports Map commonServices = Map.of( 80, "http", 443, "https", 21, "ftp", 22, "ssh", 23, "telnet", 25, "smtp", 53, "dns", 110, "pop3", @@ -440,18 +392,15 @@ public static RuntimeArray getservbyport(int ctx, RuntimeBase... args) { RuntimeArray result = new RuntimeArray(); String service = commonServices.get(port); if (service != null) { - RuntimeArray.push(result, new RuntimeScalar(service)); // name - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(port)); // port - RuntimeArray.push(result, new RuntimeScalar(protocol)); // proto + RuntimeArray.push(result, new RuntimeScalar(service)); + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(port)); + RuntimeArray.push(result, new RuntimeScalar(protocol)); } return result; } - /** - * Get protocol information by name - */ public static RuntimeArray getprotobyname(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeArray(); String protocol = args[0].toString().toLowerCase(); @@ -463,17 +412,14 @@ public static RuntimeArray getprotobyname(int ctx, RuntimeBase... args) { RuntimeArray result = new RuntimeArray(); Integer protoNum = protocols.get(protocol); if (protoNum != null) { - RuntimeArray.push(result, new RuntimeScalar(protocol)); // name - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(protoNum)); // proto number + RuntimeArray.push(result, new RuntimeScalar(protocol)); + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(protoNum)); } return result; } - /** - * Get protocol information by number - */ public static RuntimeArray getprotobynumber(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeArray(); int protoNum = args[0].scalar().getInt(); @@ -485,30 +431,25 @@ public static RuntimeArray getprotobynumber(int ctx, RuntimeBase... args) { RuntimeArray result = new RuntimeArray(); String protocol = protocols.get(protoNum); if (protocol != null) { - RuntimeArray.push(result, new RuntimeScalar(protocol)); // name - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(protoNum)); // proto number + RuntimeArray.push(result, new RuntimeScalar(protocol)); + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(protoNum)); } return result; } - /** - * Get list of system users (cross-platform) - */ private static List getSystemUsers() { List users = new ArrayList<>(); try { if (IS_WINDOWS) { - // Windows: Use 'net user' command Process proc = Runtime.getRuntime().exec(new String[]{"net", "user"}); try (Scanner scanner = new Scanner(proc.getInputStream())) { boolean inUserList = false; while (scanner.hasNextLine()) { String line = scanner.nextLine().trim(); - // Skip header lines if (line.contains("User accounts for")) { inUserList = true; continue; @@ -520,7 +461,6 @@ private static List getSystemUsers() { break; } - // Parse user names (they're space-separated) String[] usernames = line.split("\\s+"); for (String username : usernames) { if (!username.isEmpty() && !username.equals("Administrator") && @@ -531,7 +471,6 @@ private static List getSystemUsers() { } } } else { - // POSIX: Read /etc/passwd try (Scanner scanner = new Scanner(new java.io.File("/etc/passwd"))) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); @@ -544,24 +483,20 @@ private static List getSystemUsers() { String username = parts[0]; int uid = Integer.parseInt(parts[2]); - // Include system users like root (uid 0) and regular users - if (uid <= 65534) { // Exclude nobody/nogroup + if (uid <= 65534) { users.add(username); } } } } catch (Exception e) { - // Fallback if can't read /etc/passwd users.add("root"); users.add(System.getProperty("user.name")); } } } catch (Exception e) { - // Fallback to current user only users.add(System.getProperty("user.name")); } - // Ensure we always have at least the current user String currentUser = System.getProperty("user.name"); if (!users.contains(currentUser)) { users.add(currentUser); @@ -570,18 +505,13 @@ private static List getSystemUsers() { return users; } - /** - * Get list of system groups (cross-platform) - */ private static List getSystemGroups() { List groups = new ArrayList<>(); try { if (IS_WINDOWS) { - // Windows: Common built-in groups groups.addAll(Arrays.asList("Users", "Administrators", "Guests", "Power Users")); } else { - // POSIX: Read /etc/group try (Scanner scanner = new Scanner(new java.io.File("/etc/group"))) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); @@ -594,19 +524,16 @@ private static List getSystemGroups() { String groupname = parts[0]; int gid = Integer.parseInt(parts[2]); - // Include system and user groups if (gid <= 65534) { groups.add(groupname); } } } } catch (Exception e) { - // Fallback groups.addAll(Arrays.asList("root", "users", "wheel")); } } } catch (Exception e) { - // Fallback groups.add(IS_WINDOWS ? "Users" : "users"); } @@ -615,9 +542,6 @@ private static List getSystemGroups() { // ================== System V IPC Functions ================== - /** - * Message queue control operations - */ public static RuntimeScalar msgctl(int ctx, RuntimeBase... args) { if (args.length < 3) return new RuntimeScalar(-1); @@ -625,37 +549,32 @@ public static RuntimeScalar msgctl(int ctx, RuntimeBase... args) { int cmd = args[1].scalar().getInt(); RuntimeBase buf = args[2]; - // Simulate basic msgctl operations switch (cmd) { case 0: // IPC_STAT if (messageQueues.containsKey(msqid)) { - return new RuntimeScalar(0); // Success + return new RuntimeScalar(0); } break; case 1: // IPC_SET if (messageQueues.containsKey(msqid)) { - return new RuntimeScalar(0); // Success + return new RuntimeScalar(0); } break; case 2: // IPC_RMID if (messageQueues.remove(msqid) != null) { - return new RuntimeScalar(0); // Success + return new RuntimeScalar(0); } break; } - return new RuntimeScalar(-1); // Error + return new RuntimeScalar(-1); } - /** - * Get message queue identifier - */ public static RuntimeScalar msgget(int ctx, RuntimeBase... args) { if (args.length < 2) return new RuntimeScalar(-1); int key = args[0].scalar().getInt(); int msgflg = args[1].scalar().getInt(); - // Create or get existing message queue int msqid = nextIpcId++; RuntimeArray msgQueue = new RuntimeArray(); messageQueues.put(msqid, msgQueue); @@ -663,9 +582,6 @@ public static RuntimeScalar msgget(int ctx, RuntimeBase... args) { return new RuntimeScalar(msqid); } - /** - * Receive message from queue - */ public static RuntimeScalar msgrcv(int ctx, RuntimeBase... args) { if (args.length < 5) return new RuntimeScalar(-1); @@ -677,19 +593,15 @@ public static RuntimeScalar msgrcv(int ctx, RuntimeBase... args) { RuntimeArray msgQueue = messageQueues.get(msqid); if (msgQueue == null || msgQueue.size() == 0) { - return new RuntimeScalar(-1); // No messages + return new RuntimeScalar(-1); } - // Simulate receiving first message RuntimeBase message = msgQueue.get(0); msgQueue.elements.remove(0); return new RuntimeScalar(message.toString().length()); } - /** - * Send message to queue - */ public static RuntimeScalar msgsnd(int ctx, RuntimeBase... args) { if (args.length < 3) return new RuntimeScalar(-1); @@ -699,17 +611,13 @@ public static RuntimeScalar msgsnd(int ctx, RuntimeBase... args) { RuntimeArray msgQueue = messageQueues.get(msqid); if (msgQueue == null) { - return new RuntimeScalar(-1); // Queue doesn't exist + return new RuntimeScalar(-1); } - // Add message to queue RuntimeArray.push(msgQueue, msgp); - return new RuntimeScalar(0); // Success + return new RuntimeScalar(0); } - /** - * Semaphore control operations - */ public static RuntimeScalar semctl(int ctx, RuntimeBase... args) { if (args.length < 3) return new RuntimeScalar(-1); @@ -719,10 +627,9 @@ public static RuntimeScalar semctl(int ctx, RuntimeBase... args) { RuntimeArray semArray = semaphores.get(semid); if (semArray == null) { - return new RuntimeScalar(-1); // Semaphore doesn't exist + return new RuntimeScalar(-1); } - // Simulate basic semctl operations switch (cmd) { case 0: // GETVAL if (semnum < semArray.size()) { @@ -744,9 +651,6 @@ public static RuntimeScalar semctl(int ctx, RuntimeBase... args) { return new RuntimeScalar(-1); } - /** - * Get semaphore identifier - */ public static RuntimeScalar semget(int ctx, RuntimeBase... args) { if (args.length < 3) return new RuntimeScalar(-1); @@ -754,7 +658,6 @@ public static RuntimeScalar semget(int ctx, RuntimeBase... args) { int nsems = args[1].scalar().getInt(); int semflg = args[2].scalar().getInt(); - // Create semaphore array int semid = nextIpcId++; RuntimeArray semArray = new RuntimeArray(); for (int i = 0; i < nsems; i++) { @@ -765,9 +668,6 @@ public static RuntimeScalar semget(int ctx, RuntimeBase... args) { return new RuntimeScalar(semid); } - /** - * Semaphore operations - */ public static RuntimeScalar semop(int ctx, RuntimeBase... args) { if (args.length < 2) return new RuntimeScalar(-1); @@ -776,16 +676,12 @@ public static RuntimeScalar semop(int ctx, RuntimeBase... args) { RuntimeArray semArray = semaphores.get(semid); if (semArray == null) { - return new RuntimeScalar(-1); // Semaphore doesn't exist + return new RuntimeScalar(-1); } - // Simulate semaphore operation (simplified) - return new RuntimeScalar(0); // Success + return new RuntimeScalar(0); } - /** - * Shared memory control operations - */ public static RuntimeScalar shmctl(int ctx, RuntimeBase... args) { if (args.length < 3) return new RuntimeScalar(-1); @@ -815,9 +711,6 @@ public static RuntimeScalar shmctl(int ctx, RuntimeBase... args) { return new RuntimeScalar(-1); } - /** - * Get shared memory identifier - */ public static RuntimeScalar shmget(int ctx, RuntimeBase... args) { if (args.length < 3) return new RuntimeScalar(-1); @@ -825,10 +718,8 @@ public static RuntimeScalar shmget(int ctx, RuntimeBase... args) { int size = args[1].scalar().getInt(); int shmflg = args[2].scalar().getInt(); - // Create shared memory segment int shmid = nextIpcId++; RuntimeArray shmSeg = new RuntimeArray(); - // Initialize with zeros for (int i = 0; i < size; i++) { RuntimeArray.push(shmSeg, new RuntimeScalar(0)); } @@ -837,9 +728,6 @@ public static RuntimeScalar shmget(int ctx, RuntimeBase... args) { return new RuntimeScalar(shmid); } - /** - * Read from shared memory - */ public static RuntimeScalar shmread(int ctx, RuntimeBase... args) { if (args.length < 4) return new RuntimeScalar(-1); @@ -853,13 +741,11 @@ public static RuntimeScalar shmread(int ctx, RuntimeBase... args) { return new RuntimeScalar(-1); } - // Read data from shared memory segment StringBuilder data = new StringBuilder(); for (int i = pos; i < Math.min(pos + size, shmSeg.size()); i++) { data.append((char) shmSeg.get(i).scalar().getInt()); } - // Set the variable to the read data if (var instanceof RuntimeScalar) { ((RuntimeScalar) var).set(data.toString()); } @@ -867,9 +753,6 @@ public static RuntimeScalar shmread(int ctx, RuntimeBase... args) { return new RuntimeScalar(data.length()); } - /** - * Write to shared memory - */ public static RuntimeScalar shmwrite(int ctx, RuntimeBase... args) { if (args.length < 4) return new RuntimeScalar(-1); @@ -883,11 +766,9 @@ public static RuntimeScalar shmwrite(int ctx, RuntimeBase... args) { return new RuntimeScalar(-1); } - // Write data to shared memory segment byte[] bytes = string.getBytes(); int writeSize = Math.min(size, bytes.length); - // Extend segment if necessary while (shmSeg.size() <= pos + writeSize) { RuntimeArray.push(shmSeg, new RuntimeScalar(0)); } @@ -901,41 +782,26 @@ public static RuntimeScalar shmwrite(int ctx, RuntimeBase... args) { // ================== Network Enumeration Functions ================== - /** - * End host entries enumeration - */ public static RuntimeScalar endhostent(int ctx, RuntimeBase... args) { hostIterator.remove(); return new RuntimeScalar(1); } - /** - * End network entries enumeration - */ public static RuntimeScalar endnetent(int ctx, RuntimeBase... args) { netIterator.remove(); return new RuntimeScalar(1); } - /** - * End protocol entries enumeration - */ public static RuntimeScalar endprotoent(int ctx, RuntimeBase... args) { protoIterator.remove(); return new RuntimeScalar(1); } - /** - * End service entries enumeration - */ public static RuntimeScalar endservent(int ctx, RuntimeBase... args) { servIterator.remove(); return new RuntimeScalar(1); } - /** - * Get next host entry - */ public static RuntimeArray gethostent(int ctx, RuntimeBase... args) { Iterator iterator = hostIterator.get(); if (iterator == null) { @@ -949,51 +815,40 @@ public static RuntimeArray gethostent(int ctx, RuntimeBase... args) { return gethostbyname(ctx, new RuntimeScalar(hostname)); } - return new RuntimeArray(); // End of entries + return new RuntimeArray(); } - /** - * Get network by address - */ public static RuntimeArray getnetbyaddr(int ctx, RuntimeBase... args) { if (args.length < 2) return new RuntimeArray(); String addr = args[0].toString(); int addrtype = args[1].scalar().getInt(); - // Simplified network lookup RuntimeArray result = new RuntimeArray(); - RuntimeArray.push(result, new RuntimeScalar("loopback")); // name - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(addrtype)); // addrtype - RuntimeArray.push(result, new RuntimeScalar("127.0.0.1")); // net + RuntimeArray.push(result, new RuntimeScalar("loopback")); + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(addrtype)); + RuntimeArray.push(result, new RuntimeScalar("127.0.0.1")); return result; } - /** - * Get network by name - */ public static RuntimeArray getnetbyname(int ctx, RuntimeBase... args) { if (args.length < 1) return new RuntimeArray(); String name = args[0].toString(); - // Simplified network lookup RuntimeArray result = new RuntimeArray(); if (name.equals("loopback") || name.equals("localhost")) { - RuntimeArray.push(result, new RuntimeScalar(name)); // name - RuntimeArray.push(result, new RuntimeArray()); // aliases - RuntimeArray.push(result, new RuntimeScalar(2)); // addrtype (AF_INET) - RuntimeArray.push(result, new RuntimeScalar("127.0.0.1")); // net + RuntimeArray.push(result, new RuntimeScalar(name)); + RuntimeArray.push(result, new RuntimeArray()); + RuntimeArray.push(result, new RuntimeScalar(2)); + RuntimeArray.push(result, new RuntimeScalar("127.0.0.1")); } return result; } - /** - * Get next network entry - */ public static RuntimeArray getnetent(int ctx, RuntimeBase... args) { Iterator iterator = netIterator.get(); if (iterator == null) { @@ -1007,12 +862,9 @@ public static RuntimeArray getnetent(int ctx, RuntimeBase... args) { return getnetbyname(ctx, new RuntimeScalar(netname)); } - return new RuntimeArray(); // End of entries + return new RuntimeArray(); } - /** - * Get next protocol entry - */ public static RuntimeArray getprotoent(int ctx, RuntimeBase... args) { Iterator iterator = protoIterator.get(); if (iterator == null) { @@ -1026,12 +878,9 @@ public static RuntimeArray getprotoent(int ctx, RuntimeBase... args) { return getprotobyname(ctx, new RuntimeScalar(protoname)); } - return new RuntimeArray(); // End of entries + return new RuntimeArray(); } - /** - * Get next service entry - */ public static RuntimeArray getservent(int ctx, RuntimeBase... args) { Iterator iterator = servIterator.get(); if (iterator == null) { @@ -1045,57 +894,40 @@ public static RuntimeArray getservent(int ctx, RuntimeBase... args) { return getservbyname(ctx, new RuntimeScalar(servicename), new RuntimeScalar("tcp")); } - return new RuntimeArray(); // End of entries + return new RuntimeArray(); } - /** - * Set host entries enumeration - */ public static RuntimeScalar sethostent(int ctx, RuntimeBase... args) { - hostIterator.remove(); // Reset iterator + hostIterator.remove(); return new RuntimeScalar(1); } - /** - * Set network entries enumeration - */ public static RuntimeScalar setnetent(int ctx, RuntimeBase... args) { - netIterator.remove(); // Reset iterator + netIterator.remove(); return new RuntimeScalar(1); } - /** - * Set protocol entries enumeration - */ public static RuntimeScalar setprotoent(int ctx, RuntimeBase... args) { - protoIterator.remove(); // Reset iterator + protoIterator.remove(); return new RuntimeScalar(1); } - /** - * Set service entries enumeration - */ public static RuntimeScalar setservent(int ctx, RuntimeBase... args) { - servIterator.remove(); // Reset iterator + servIterator.remove(); return new RuntimeScalar(1); } - /** - * Get list of system hosts (simplified) - */ private static List getSystemHosts() { List hosts = new ArrayList<>(); hosts.add("localhost"); hosts.add("127.0.0.1"); try { - // Add local hostname String hostname = java.net.InetAddress.getLocalHost().getHostName(); if (!hosts.contains(hostname)) { hosts.add(hostname); } } catch (Exception e) { - // Ignore } return hosts; diff --git a/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java b/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java index a8b29b321..314f7cdc3 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java +++ b/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java @@ -1,12 +1,5 @@ package org.perlonjava.runtime.nativ; -import com.sun.jna.Function; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.Tlhelp32; -import com.sun.jna.platform.win32.WinDef; -import com.sun.jna.platform.win32.WinNT; import org.perlonjava.runtime.runtimetypes.GlobalVariable; import org.perlonjava.runtime.runtimetypes.RuntimeBase; import org.perlonjava.runtime.runtimetypes.RuntimeIO; @@ -17,27 +10,14 @@ import java.nio.file.Path; import java.nio.file.Paths; -/** - * Platform-agnostic native operations utility following PerlOnJava operator API - */ public class NativeUtils { - public static final boolean IS_WINDOWS = Platform.isWindows(); + public static final boolean IS_WINDOWS = System.getProperty("os.name", "").toLowerCase().contains("win"); + public static final boolean IS_MAC = System.getProperty("os.name", "").toLowerCase().contains("mac"); - // Constants for default IDs private static final int DEFAULT_UID = 1000; private static final int DEFAULT_GID = 1000; private static final int ID_RANGE = 65536; - // Direct function mapping for CreateHardLink on Windows - private static final Function CREATE_HARD_LINK = IS_WINDOWS ? - Function.getFunction("kernel32", "CreateHardLinkA") : null; - - /** - * Create a symbolic link - * - * @param args RuntimeScalar array containing [oldfile, newfile] - * @return RuntimeScalar with 1 for success, 0 for failure - */ public static RuntimeScalar symlink(int ctx, RuntimeBase... args) { if (args.length < 2) { return new RuntimeScalar(0); @@ -54,41 +34,31 @@ public static RuntimeScalar symlink(int ctx, RuntimeBase... args) { Path target = Paths.get(oldFile); Path link = Paths.get(newFile); - // Check if the link already exists if (Files.exists(link)) { - // Set $! to "File exists" GlobalVariable.getGlobalVariable("main::!").set("File exists"); - Native.setLastError(17); // EEXIST return new RuntimeScalar(0); } - // Create the symbolic link Files.createSymbolicLink(link, target); return new RuntimeScalar(1); } catch (UnsupportedOperationException e) { - // Symbolic links not supported on this platform throw new RuntimeException("Symbolic links are not implemented on this platform"); } catch (IOException e) { - // Set $! based on the specific IOException String errorMessage = e.getMessage(); if (errorMessage != null && errorMessage.contains("privilege")) { GlobalVariable.getGlobalVariable("main::!").set("Permission denied"); - Native.setLastError(13); // EACCES } else if (errorMessage != null && errorMessage.contains("Access is denied")) { GlobalVariable.getGlobalVariable("main::!").set("Permission denied"); - Native.setLastError(13); // EACCES } else if (errorMessage != null && errorMessage.contains("No such file")) { GlobalVariable.getGlobalVariable("main::!").set("No such file or directory"); - Native.setLastError(2); // ENOENT } else { GlobalVariable.getGlobalVariable("main::!").set(errorMessage != null ? errorMessage : "I/O error"); } return new RuntimeScalar(0); } catch (SecurityException e) { GlobalVariable.getGlobalVariable("main::!").set("Permission denied"); - Native.setLastError(13); // EACCES return new RuntimeScalar(0); } catch (Exception e) { GlobalVariable.getGlobalVariable("main::!").set(e.getMessage() != null ? e.getMessage() : "Unknown error"); @@ -96,12 +66,6 @@ public static RuntimeScalar symlink(int ctx, RuntimeBase... args) { } } - /** - * Create a hard link between two files - * - * @param args RuntimeScalar array containing [oldfile, newfile] - * @return RuntimeScalar with 1 for success, 0 for failure - */ public static RuntimeScalar link(int ctx, RuntimeBase... args) { if (args.length < 2) { return new RuntimeScalar(0); @@ -116,13 +80,9 @@ public static RuntimeScalar link(int ctx, RuntimeBase... args) { try { if (IS_WINDOWS) { - // Windows implementation using CreateHardLinkA via direct function call - Object[] args_array = {newFile, oldFile, null}; - Object result = CREATE_HARD_LINK.invoke(Boolean.class, args_array); - boolean success = (Boolean) result; - return new RuntimeScalar(success ? 1 : 0); + Files.createLink(Paths.get(newFile), Paths.get(oldFile)); + return new RuntimeScalar(1); } else { - // POSIX implementation using link() system call int result = PosixLibrary.INSTANCE.link(oldFile, newFile); return new RuntimeScalar(result == 0 ? 1 : 0); } @@ -131,53 +91,24 @@ public static RuntimeScalar link(int ctx, RuntimeBase... args) { } } - /** - * Get parent process ID - * - * @param args Unused (for API consistency) - * @return RuntimeScalar with parent process ID - */ public static RuntimeScalar getppid(int ctx, RuntimeBase... args) { if (IS_WINDOWS) { - // Windows implementation - int currentPid = Kernel32.INSTANCE.GetCurrentProcessId(); - WinNT.HANDLE snapshot = Kernel32.INSTANCE.CreateToolhelp32Snapshot( - Tlhelp32.TH32CS_SNAPPROCESS, new WinDef.DWORD(0)); - - try { - Tlhelp32.PROCESSENTRY32 entry = new Tlhelp32.PROCESSENTRY32(); - if (Kernel32.INSTANCE.Process32First(snapshot, entry)) { - do { - if (entry.th32ProcessID.intValue() == currentPid) { - return new RuntimeScalar(entry.th32ParentProcessID.intValue()); - } - } while (Kernel32.INSTANCE.Process32Next(snapshot, entry)); - } - } finally { - Kernel32.INSTANCE.CloseHandle(snapshot); - } - return new RuntimeScalar(0); + return ProcessHandle.current().parent() + .map(ph -> new RuntimeScalar(ph.pid())) + .orElse(new RuntimeScalar(0)); } else { return new RuntimeScalar(PosixLibrary.INSTANCE.getppid()); } } - /** - * Get user ID (returns username-based hash on Windows) - * - * @param args Unused (for API consistency) - * @return RuntimeScalar with user ID - */ public static RuntimeScalar getuid(int ctx, RuntimeBase... args) { if (IS_WINDOWS) { - // Simplified approach: use username hash try { String userName = System.getProperty("user.name"); if (userName != null && !userName.isEmpty()) { return new RuntimeScalar(Math.abs(userName.hashCode()) % ID_RANGE); } } catch (Exception e) { - // Fall through to default } return new RuntimeScalar(DEFAULT_UID); } else { @@ -185,37 +116,22 @@ public static RuntimeScalar getuid(int ctx, RuntimeBase... args) { } } - /** - * Get effective user ID - * - * @param args Unused (for API consistency) - * @return RuntimeScalar with effective user ID - */ public static RuntimeScalar geteuid(int ctx, RuntimeBase... args) { if (IS_WINDOWS) { - // On Windows, effective UID is same as UID return getuid(ctx, args); } else { return new RuntimeScalar(PosixLibrary.INSTANCE.geteuid()); } } - /** - * Get group ID (returns computer name hash on Windows) - * - * @param args Unused (for API consistency) - * @return RuntimeScalar with group ID - */ public static RuntimeScalar getgid(int ctx, RuntimeBase... args) { if (IS_WINDOWS) { - // Simplified: use computer name for consistent group ID try { String computerName = System.getenv("COMPUTERNAME"); if (computerName != null && !computerName.isEmpty()) { return new RuntimeScalar(Math.abs(computerName.hashCode()) % ID_RANGE); } } catch (Exception e) { - // Fall through to default } return new RuntimeScalar(DEFAULT_GID); } else { @@ -223,15 +139,8 @@ public static RuntimeScalar getgid(int ctx, RuntimeBase... args) { } } - /** - * Get effective group ID - * - * @param args Unused (for API consistency) - * @return RuntimeScalar with effective group ID - */ public static RuntimeScalar getegid(int ctx, RuntimeBase... args) { if (IS_WINDOWS) { - // On Windows, effective GID is same as GID return getgid(ctx, args); } else { return new RuntimeScalar(PosixLibrary.INSTANCE.getegid()); diff --git a/src/main/java/org/perlonjava/runtime/nativ/PosixLibrary.java b/src/main/java/org/perlonjava/runtime/nativ/PosixLibrary.java index be4fcea6e..eeed920d1 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/PosixLibrary.java +++ b/src/main/java/org/perlonjava/runtime/nativ/PosixLibrary.java @@ -1,98 +1,8 @@ package org.perlonjava.runtime.nativ; -import com.sun.jna.LastErrorException; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; +import jnr.posix.POSIX; +import jnr.posix.POSIXFactory; -/** - * JNA interface to POSIX C library functions - */ -public interface PosixLibrary extends Library { - PosixLibrary INSTANCE = Native.load("c", PosixLibrary.class); - - // Process management - int kill(int pid, int sig) throws LastErrorException; - - int getpid(); - - int getppid(); - - int getuid(); - - int geteuid(); - - int getgid(); - - int getegid(); - - int setuid(int uid) throws LastErrorException; - - int setgid(int gid) throws LastErrorException; - - // Process control - int fork() throws LastErrorException; - - int execve(String path, String[] argv, String[] envp) throws LastErrorException; - - int waitpid(int pid, IntByReference status, int options) throws LastErrorException; - - // File operations - int chmod(String path, int mode) throws LastErrorException; - - int chown(String path, int uid, int gid) throws LastErrorException; - - int umask(int mask); - - int stat(String path, Pointer buf); - - int lstat(String path, Pointer buf); - - int unlink(String path) throws LastErrorException; - - int rename(String oldpath, String newpath) throws LastErrorException; - - // Symbolic link operations - int readlink(String path, byte[] buf, int bufsiz) throws LastErrorException; - - int link(String oldPath, String newPath); - - // Signal handling - Pointer signal(int sig, Pointer handler) throws LastErrorException; - - int raise(int sig) throws LastErrorException; - - int sigaction(int sig, Pointer act, Pointer oldact) throws LastErrorException; - - // Time functions - long time(LongByReference tloc); - - int sleep(int seconds); - - int usleep(int microseconds) throws LastErrorException; - - // Environment - String getenv(String name); - - int setenv(String name, String value, int overwrite) throws LastErrorException; - - int unsetenv(String name) throws LastErrorException; - - // Error handling - int errno(); - - String strerror(int errnum); - - // Password database - Pointer getpwnam(String name); - - Pointer getpwuid(int uid); - - void setpwent(); - - void endpwent(); - - Pointer getpwent(); -} \ No newline at end of file +public class PosixLibrary { + public static final POSIX INSTANCE = POSIXFactory.getNativePOSIX(); +} diff --git a/src/main/java/org/perlonjava/runtime/nativ/WindowsLibrary.java b/src/main/java/org/perlonjava/runtime/nativ/WindowsLibrary.java index 73315617f..a41cb9a4b 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/WindowsLibrary.java +++ b/src/main/java/org/perlonjava/runtime/nativ/WindowsLibrary.java @@ -1,34 +1 @@ package org.perlonjava.runtime.nativ; - -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import com.sun.jna.WString; -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.win32.StdCallLibrary; - -/** - * Extended Windows API interface for operations not in jna-platform - */ -public interface WindowsLibrary extends StdCallLibrary { - WindowsLibrary INSTANCE = Native.load("kernel32", WindowsLibrary.class); - // File attributes and reparse point constants - int FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; - int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; - int FSCTL_GET_REPARSE_POINT = 0x000900A8; - // Maximum reparse data buffer size - int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384; - - // Additional Windows-specific functions not in Kernel32 - boolean CreateProcessW( - WString applicationName, - WString commandLine, - WinBase.SECURITY_ATTRIBUTES processAttributes, - WinBase.SECURITY_ATTRIBUTES threadAttributes, - boolean inheritHandles, - int creationFlags, - Pointer environment, - WString currentDirectory, - WinBase.STARTUPINFO startupInfo, - WinBase.PROCESS_INFORMATION processInformation - ); -} \ No newline at end of file diff --git a/src/main/java/org/perlonjava/runtime/operators/KillOperator.java b/src/main/java/org/perlonjava/runtime/operators/KillOperator.java index c9c674b97..eeb700c0b 100644 --- a/src/main/java/org/perlonjava/runtime/operators/KillOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/KillOperator.java @@ -1,9 +1,5 @@ package org.perlonjava.runtime.operators; -import com.sun.jna.LastErrorException; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.platform.win32.Wincon; import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.nativ.PosixLibrary; import org.perlonjava.runtime.runtimetypes.PerlSignalQueue; @@ -96,48 +92,30 @@ private static boolean sendSignalToPid(int pid, int signal) { } if (NativeUtils.IS_WINDOWS) { switch (signal) { - case 0: // Check if process exists - WinNT.HANDLE handle = Kernel32.INSTANCE.OpenProcess( - WinNT.PROCESS_QUERY_INFORMATION, false, pid); - if (handle != null) { - Kernel32.INSTANCE.CloseHandle(handle); - return true; - } - setErrno(3); // ESRCH - No such process + case 0: + var ph = ProcessHandle.of(pid); + if (ph.isPresent() && ph.get().isAlive()) return true; + setErrno(3); return false; - case 2: // SIGINT - try Ctrl+C event - boolean result = Kernel32.INSTANCE.GenerateConsoleCtrlEvent( - Wincon.CTRL_C_EVENT, pid); - if (!result) { - setErrno(Kernel32.INSTANCE.GetLastError()); + case 2: + case 3: + var proc = ProcessHandle.of(pid); + if (proc.isPresent()) { + proc.get().destroy(); + return true; } - return result; + setErrno(3); + return false; - case 3: // SIGQUIT - try Ctrl+Break event - result = Kernel32.INSTANCE.GenerateConsoleCtrlEvent( - Wincon.CTRL_BREAK_EVENT, pid); - if (!result) { - setErrno(Kernel32.INSTANCE.GetLastError()); - } - return result; - - case 9: // SIGKILL - case 15: // SIGTERM - WinNT.HANDLE processHandle = Kernel32.INSTANCE.OpenProcess( - WinNT.PROCESS_TERMINATE, false, pid); - if (processHandle != null) { - try { - result = Kernel32.INSTANCE.TerminateProcess(processHandle, 1); - if (!result) { - setErrno(Kernel32.INSTANCE.GetLastError()); - } - return result; - } finally { - Kernel32.INSTANCE.CloseHandle(processHandle); - } + case 9: + case 15: + var p = ProcessHandle.of(pid); + if (p.isPresent()) { + if (signal == 9) p.get().destroyForcibly(); else p.get().destroy(); + return true; } - setErrno(Kernel32.INSTANCE.GetLastError()); + setErrno(3); return false; default: @@ -146,26 +124,23 @@ private static boolean sendSignalToPid(int pid, int signal) { return false; } } else { - try { - int result = PosixLibrary.INSTANCE.kill(pid, signal); - return result == 0; - } catch (LastErrorException e) { - setErrno(e.getErrorCode()); + int result = PosixLibrary.INSTANCE.kill(pid, signal); + if (result != 0) { + setErrno(PosixLibrary.INSTANCE.errno()); return false; } + return true; } } // Helper method for sending signals to process groups (Unix only) private static boolean sendSignalToProcessGroup(int pgid, int signal) { - try { - // kill with negative PID targets process group - int result = PosixLibrary.INSTANCE.kill(-pgid, signal); - return result == 0; - } catch (LastErrorException e) { - setErrno(e.getErrorCode()); + int result = PosixLibrary.INSTANCE.kill(-pgid, signal); + if (result != 0) { + setErrno(PosixLibrary.INSTANCE.errno()); return false; } + return true; } // Convert signal names to numbers diff --git a/src/main/java/org/perlonjava/runtime/operators/Operator.java b/src/main/java/org/perlonjava/runtime/operators/Operator.java index d698f24ce..8a152a29e 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Operator.java +++ b/src/main/java/org/perlonjava/runtime/operators/Operator.java @@ -1,10 +1,5 @@ package org.perlonjava.runtime.operators; -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinDef; -import com.sun.jna.platform.win32.WinNT; import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.nativ.PosixLibrary; import org.perlonjava.runtime.regex.RegexTimeoutCharSequence; @@ -47,7 +42,7 @@ public static RuntimeScalar chmod(RuntimeList runtimeList) { int successCount = 0; // Detect platform - boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); + boolean isWindows = NativeUtils.IS_WINDOWS; // Process each file in the list for (int i = 1; i < runtimeList.size(); i++) { @@ -61,21 +56,16 @@ public static RuntimeScalar chmod(RuntimeList runtimeList) { boolean success; if (isWindows) { - // Windows: use File attributes - int attributes = 0; - if ((mode & 0200) == 0) { // Write bit not set - attributes |= WinNT.FILE_ATTRIBUTE_READONLY; - } - - success = Kernel32.INSTANCE.SetFileAttributes(path, new WinDef.DWORD(attributes)); - } else { - // POSIX systems try { - int result = PosixLibrary.INSTANCE.chmod(path, mode); - success = (result == 0); - } catch (LastErrorException e) { + boolean readOnly = (mode & 0200) == 0; + java.nio.file.Files.setAttribute(resolved, "dos:readonly", readOnly); + success = true; + } catch (Exception ex) { success = false; } + } else { + int result = PosixLibrary.INSTANCE.chmod(path, mode); + success = (result == 0); } if (success) { @@ -695,7 +685,6 @@ public static RuntimeScalar readlink(int ctx, RuntimeBase... args) { if (!Files.exists(linkPath)) { // Set $! to "No such file or directory" getGlobalVariable("main::!").set("No such file or directory"); - Native.setLastError(2); // ENOENT return RuntimeScalar.undef(); } @@ -703,9 +692,7 @@ public static RuntimeScalar readlink(int ctx, RuntimeBase... args) { Path targetPath = Files.readSymbolicLink(linkPath); return new RuntimeScalar(targetPath.toString()); } else { - // Not a symbolic link - set $! to appropriate error getGlobalVariable("main::!").set("Invalid argument"); - Native.setLastError(22); // EINVAL return RuntimeScalar.undef(); } } catch (UnsupportedOperationException e) { @@ -716,7 +703,6 @@ public static RuntimeScalar readlink(int ctx, RuntimeBase... args) { String errorMessage = e.getMessage(); if (errorMessage != null && errorMessage.contains("Access is denied")) { getGlobalVariable("main::!").set("Permission denied"); - Native.setLastError(13); // EACCES } else { getGlobalVariable("main::!").set(errorMessage != null ? errorMessage : "I/O error"); } @@ -749,7 +735,6 @@ public static RuntimeScalar rename(int ctx, RuntimeBase... args) { // Check if source file exists if (!Files.exists(oldPath)) { getGlobalVariable("main::!").set("No such file or directory"); - Native.setLastError(2); // ENOENT return scalarFalse; } @@ -761,15 +746,12 @@ public static RuntimeScalar rename(int ctx, RuntimeBase... args) { } catch (AccessDeniedException e) { getGlobalVariable("main::!").set("Permission denied"); - Native.setLastError(13); // EACCES return scalarFalse; } catch (FileAlreadyExistsException e) { getGlobalVariable("main::!").set("File exists"); - Native.setLastError(17); // EEXIST return scalarFalse; } catch (NoSuchFileException e) { getGlobalVariable("main::!").set("No such file or directory"); - Native.setLastError(2); // ENOENT return scalarFalse; } catch (IOException e) { // Handle other IO errors @@ -777,10 +759,8 @@ public static RuntimeScalar rename(int ctx, RuntimeBase... args) { if (errorMessage != null) { if (errorMessage.contains("cross-device link")) { getGlobalVariable("main::!").set("Invalid cross-device link"); - Native.setLastError(18); // EXDEV } else if (errorMessage.contains("directory not empty")) { getGlobalVariable("main::!").set("Directory not empty"); - Native.setLastError(39); // ENOTEMPTY } else { getGlobalVariable("main::!").set(errorMessage); } diff --git a/src/main/java/org/perlonjava/runtime/operators/Stat.java b/src/main/java/org/perlonjava/runtime/operators/Stat.java index ecc22f44c..de24a7b02 100644 --- a/src/main/java/org/perlonjava/runtime/operators/Stat.java +++ b/src/main/java/org/perlonjava/runtime/operators/Stat.java @@ -1,14 +1,11 @@ package org.perlonjava.runtime.operators; -import com.sun.jna.Library; -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Pointer; +import jnr.posix.FileStat; import org.perlonjava.runtime.io.ClosedIOHandle; import org.perlonjava.runtime.io.CustomFileChannel; import org.perlonjava.runtime.io.IOHandle; import org.perlonjava.runtime.io.LayeredIOHandle; +import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.nativ.PosixLibrary; import org.perlonjava.runtime.runtimetypes.RuntimeBase; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; @@ -50,94 +47,24 @@ private record NativeStatFields( static NativeStatFields lastNativeStatFields; - private interface MsvcrtLib extends Library { - int _stat64(String path, Pointer buf); - } - - private static final MsvcrtLib MSVCRT; - static { - MsvcrtLib lib = null; - if (Platform.isWindows()) { - try { lib = Native.load("msvcrt", MsvcrtLib.class); } catch (Exception e) { } - } - MSVCRT = lib; - } - static NativeStatFields nativeStat(String path, boolean followLinks) { try { - if (Platform.isWindows()) return nativeStatWindows(path); - return nativeStatUnix(path, followLinks); + if (NativeUtils.IS_WINDOWS) return null; + FileStat fs = followLinks + ? PosixLibrary.INSTANCE.stat(path) + : PosixLibrary.INSTANCE.lstat(path); + if (fs == null) return null; + return new NativeStatFields( + fs.dev(), fs.ino(), fs.mode(), fs.nlink(), + fs.uid(), fs.gid(), fs.rdev(), fs.st_size(), + fs.atime(), fs.mtime(), fs.ctime(), + fs.blockSize(), fs.blocks() + ); } catch (Throwable e) { return null; } } - private static NativeStatFields nativeStatUnix(String path, boolean followLinks) { - Memory buf = new Memory(256); - int rc = followLinks - ? PosixLibrary.INSTANCE.stat(path, buf) - : PosixLibrary.INSTANCE.lstat(path, buf); - if (rc != 0) return null; - return Platform.isMac() ? parseMacOsStat(buf) : parseLinuxStat(buf); - } - - private static NativeStatFields parseMacOsStat(Pointer buf) { - return new NativeStatFields( - buf.getInt(0) & 0xFFFFFFFFL, - buf.getLong(8), - buf.getShort(4) & 0xFFFF, - buf.getShort(6) & 0xFFFF, - buf.getInt(16) & 0xFFFFFFFFL, - buf.getInt(20) & 0xFFFFFFFFL, - buf.getInt(24) & 0xFFFFFFFFL, - buf.getLong(96), - buf.getLong(32), - buf.getLong(48), - buf.getLong(64), - buf.getInt(112) & 0xFFFFFFFFL, - buf.getLong(104) - ); - } - - private static NativeStatFields parseLinuxStat(Pointer buf) { - return new NativeStatFields( - buf.getLong(0), - buf.getLong(8), - buf.getInt(24) & 0xFFFFFFFFL, - buf.getLong(16), - buf.getInt(28) & 0xFFFFFFFFL, - buf.getInt(32) & 0xFFFFFFFFL, - buf.getLong(40), - buf.getLong(48), - buf.getLong(72), - buf.getLong(88), - buf.getLong(104), - buf.getLong(56), - buf.getLong(64) - ); - } - - private static NativeStatFields nativeStatWindows(String path) { - if (MSVCRT == null) return null; - Memory buf = new Memory(64); - int rc = MSVCRT._stat64(path, buf); - if (rc != 0) return null; - return new NativeStatFields( - buf.getInt(0) & 0xFFFFFFFFL, - buf.getShort(4) & 0xFFFF, - buf.getShort(6) & 0xFFFF, - buf.getShort(8) & 0xFFFF, - buf.getShort(10) & 0xFFFF, - buf.getShort(12) & 0xFFFF, - buf.getInt(16) & 0xFFFFFFFFL, - buf.getLong(24), - buf.getLong(32), - buf.getLong(40), - buf.getLong(48), - 0, 0 - ); - } - private static int getPermissionsOctal(BasicFileAttributes basicAttr, PosixFileAttributes attr) { int permissions = 0; if (basicAttr.isDirectory()) permissions |= 0040000; diff --git a/src/main/java/org/perlonjava/runtime/operators/UmaskOperator.java b/src/main/java/org/perlonjava/runtime/operators/UmaskOperator.java index 79d19334b..865fd8e33 100644 --- a/src/main/java/org/perlonjava/runtime/operators/UmaskOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/UmaskOperator.java @@ -1,6 +1,6 @@ package org.perlonjava.runtime.operators; -import com.sun.jna.Platform; +import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.nativ.PosixLibrary; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; import org.perlonjava.runtime.runtimetypes.RuntimeBase; @@ -20,7 +20,7 @@ public class UmaskOperator { // Default umask value (022 = no write permission for group/others) private static final int DEFAULT_UMASK = 022; - private static final boolean IS_WINDOWS = Platform.isWindows(); + private static final boolean IS_WINDOWS = NativeUtils.IS_WINDOWS; // Windows simulation of umask (thread-safe) private static volatile int windowsSimulatedUmask = DEFAULT_UMASK; diff --git a/src/main/java/org/perlonjava/runtime/operators/UnlinkOperator.java b/src/main/java/org/perlonjava/runtime/operators/UnlinkOperator.java index 6c92b5dbd..202bdef5d 100644 --- a/src/main/java/org/perlonjava/runtime/operators/UnlinkOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/UnlinkOperator.java @@ -62,9 +62,6 @@ private static boolean deleteFile(String fileName) { return false; } - // Avoid native/JNA unlink implementations. - // Some environments (including perl5 test runs) may not have JNA available, - // and loading com.sun.jna classes can crash the test harness during cleanup. Files.delete(path); return true; } catch (NoSuchFileException e) { diff --git a/src/main/java/org/perlonjava/runtime/operators/UtimeOperator.java b/src/main/java/org/perlonjava/runtime/operators/UtimeOperator.java index 0ebd19e45..5d1db08ef 100644 --- a/src/main/java/org/perlonjava/runtime/operators/UtimeOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/UtimeOperator.java @@ -1,9 +1,7 @@ package org.perlonjava.runtime.operators; -import com.sun.jna.*; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinNT; +import org.perlonjava.runtime.nativ.NativeUtils; +import org.perlonjava.runtime.nativ.PosixLibrary; import org.perlonjava.runtime.runtimetypes.RuntimeBase; import org.perlonjava.runtime.runtimetypes.RuntimeIO; import org.perlonjava.runtime.runtimetypes.RuntimeScalar; @@ -13,39 +11,18 @@ import java.nio.file.Path; import java.nio.file.Paths; -/** - * Native implementation of Perl's utime operator using JNA - *

- * This implementation provides direct access to system utime functions: - * - On POSIX systems: Uses native utime()/utimes() system calls - * - On Windows: Uses SetFileTime() API - * - Supports both filenames and filehandles - * - Microsecond precision where available - */ public class UtimeOperator { - private static final boolean IS_WINDOWS = Platform.isWindows(); + private static final boolean IS_WINDOWS = NativeUtils.IS_WINDOWS; - // Windows epoch starts 1601, Unix epoch starts 1970 - // Difference in 100-nanosecond intervals - private static final long WINDOWS_EPOCH_DIFF = 116444736000000000L; - - /** - * Implements Perl's utime operator using native system calls - * - * @param args RuntimeBase containing access time, modification time, and file paths - * @return RuntimeScalar with count of successfully changed files - */ public static RuntimeScalar utime(int ctx, RuntimeBase... args) { if (args.length < 2) { return new RuntimeScalar(0); } - // Get access and modification times RuntimeScalar accessTimeArg = args[0].scalar(); RuntimeScalar modTimeArg = args[1].scalar(); - // Check if both are undef (use current time) boolean useCurrentTime = !accessTimeArg.getDefinedBoolean() && !modTimeArg.getDefinedBoolean(); long accessTime; @@ -56,18 +33,15 @@ public static RuntimeScalar utime(int ctx, RuntimeBase... args) { accessTime = currentTime; modTime = currentTime; } else { - // Get times in seconds (Perl uses seconds since epoch) accessTime = accessTimeArg.getDefinedBoolean() ? (long) accessTimeArg.getDouble() : 0; modTime = modTimeArg.getDefinedBoolean() ? (long) modTimeArg.getDouble() : 0; } int successCount = 0; - // Process each file starting from index 2 for (int i = 2; i < args.length; i++) { RuntimeBase arg = args[i]; - // Handle both scalar filenames and lists of filenames for (RuntimeScalar fileArg : arg) { if (changeFileTimes(fileArg, accessTime, modTime)) { successCount++; @@ -78,18 +52,13 @@ public static RuntimeScalar utime(int ctx, RuntimeBase... args) { return new RuntimeScalar(successCount); } - /** - * Changes the access and modification times for a single file - */ private static boolean changeFileTimes(RuntimeScalar fileArg, long accessTime, long modTime) { try { - // Check if this is a filehandle if (fileArg.type == RuntimeScalarType.GLOB || fileArg.type == RuntimeScalarType.GLOBREFERENCE) { return changeFilehandleTimes(fileArg, accessTime, modTime); } - // Regular filename case String filename = RuntimeIO.sanitizePathname("utime", fileArg.toString()); if (filename == null || filename.isEmpty()) { return false; @@ -106,123 +75,35 @@ private static boolean changeFileTimes(RuntimeScalar fileArg, long accessTime, l } } - /** - * POSIX implementation using native utime calls - */ private static boolean changeFileTimesPosix(String filename, long accessTime, long modTime) { try { - // Try utimes() first (microsecond precision) - Timeval[] times = new Timeval[2]; - times[0] = new Timeval(); - times[0].tv_sec = new NativeLong(accessTime); - times[0].tv_usec = new NativeLong(0); - - times[1] = new Timeval(); - times[1].tv_sec = new NativeLong(modTime); - times[1].tv_usec = new NativeLong(0); - - int result = ExtendedPosixLibrary.INSTANCE.utimes(filename, times); - if (result == 0) { - return true; - } - - // Fall back to utime() if utimes() is not available - Utimbuf utimbuf = new Utimbuf(); - utimbuf.actime = new NativeLong(accessTime); - utimbuf.modtime = new NativeLong(modTime); - - result = ExtendedPosixLibrary.INSTANCE.utime(filename, utimbuf); - return result == 0; - - } catch (UnsatisfiedLinkError e) { - // Native method not available, fall back to Java NIO + long[] atimeval = {accessTime, 0}; + long[] mtimeval = {modTime, 0}; + int result = PosixLibrary.INSTANCE.utimes(filename, atimeval, mtimeval); + if (result == 0) return true; + return changeFileTimesJava(filename, accessTime, modTime); + } catch (Exception e) { return changeFileTimesJava(filename, accessTime, modTime); } } - /** - * Windows implementation using SetFileTime - */ private static boolean changeFileTimesWindows(String filename, long accessTime, long modTime) { - WinNT.HANDLE fileHandle = null; - try { - // Open file handle - fileHandle = Kernel32.INSTANCE.CreateFile( - filename, - WinNT.GENERIC_WRITE, - WinNT.FILE_SHARE_READ | WinNT.FILE_SHARE_WRITE, - null, - WinNT.OPEN_EXISTING, - WinNT.FILE_ATTRIBUTE_NORMAL, - null - ); - - if (fileHandle == WinBase.INVALID_HANDLE_VALUE) { - return false; - } - - // Convert Unix timestamps to Windows FILETIME - WinBase.FILETIME accessFileTime = unixTimeToFileTime(accessTime); - WinBase.FILETIME modFileTime = unixTimeToFileTime(modTime); - - // Set file times - return 0 != Kernel32.INSTANCE.SetFileTime( - fileHandle, - null, // Creation time (don't change) - accessFileTime, // Last access time - modFileTime // Last write time - ); - - } finally { - if (fileHandle != null && fileHandle != WinBase.INVALID_HANDLE_VALUE) { - Kernel32.INSTANCE.CloseHandle(fileHandle); - } - } + return changeFileTimesJava(filename, accessTime, modTime); } - /** - * Handle filehandle case by extracting file descriptor - */ private static boolean changeFilehandleTimes(RuntimeScalar filehandle, long accessTime, long modTime) { try { - // Extract file descriptor from the filehandle - // This requires accessing the internal structure of RuntimeGlob - - if (IS_WINDOWS) { - // Windows doesn't have futimes equivalent - // Try to get filename from handle and use regular file method - String filename = getFilenameFromHandle(filehandle); - if (filename != null) { - return changeFileTimesWindows(filename, accessTime, modTime); - } - } else { - // Try to get file descriptor - int fd = getFileDescriptor(filehandle); - if (fd >= 0) { - Timeval[] times = new Timeval[2]; - times[0] = new Timeval(); - times[0].tv_sec = new NativeLong(accessTime); - times[0].tv_usec = new NativeLong(0); - - times[1] = new Timeval(); - times[1].tv_sec = new NativeLong(modTime); - times[1].tv_usec = new NativeLong(0); - - int result = ExtendedPosixLibrary.INSTANCE.futimes(fd, times); - return result == 0; - } + String filename = getFilenameFromHandle(filehandle); + if (filename != null) { + if (IS_WINDOWS) return changeFileTimesWindows(filename, accessTime, modTime); + return changeFileTimesPosix(filename, accessTime, modTime); } } catch (Exception e) { - // Fall through to return false } - return false; } - /** - * Fall back to Java NIO implementation - */ private static boolean changeFileTimesJava(String filename, long accessTime, long modTime) { try { Path path = Paths.get(filename); @@ -230,15 +111,12 @@ private static boolean changeFileTimesJava(String filename, long accessTime, lon return false; } - // Convert to milliseconds for Java long accessMillis = accessTime * 1000L; long modMillis = modTime * 1000L; - // Set times using NIO Files.setLastModifiedTime(path, java.nio.file.attribute.FileTime.fromMillis(modMillis)); - // Also try to set access time java.nio.file.attribute.BasicFileAttributeView view = Files.getFileAttributeView(path, java.nio.file.attribute.BasicFileAttributeView.class); @@ -247,7 +125,7 @@ private static boolean changeFileTimesJava(String filename, long accessTime, lon view.setTimes( java.nio.file.attribute.FileTime.fromMillis(modMillis), java.nio.file.attribute.FileTime.fromMillis(accessMillis), - null // Don't change creation time + null ); } @@ -257,96 +135,15 @@ private static boolean changeFileTimesJava(String filename, long accessTime, lon } } - /** - * Convert Unix timestamp to Windows FILETIME - */ - private static WinBase.FILETIME unixTimeToFileTime(long unixTime) { - // Convert to 100-nanosecond intervals since Windows epoch - long windowsTime = (unixTime * 10000000L) + WINDOWS_EPOCH_DIFF; - - WinBase.FILETIME ft = new WinBase.FILETIME(); - ft.dwLowDateTime = (int) (windowsTime & 0xFFFFFFFFL); - ft.dwHighDateTime = (int) ((windowsTime >> 32) & 0xFFFFFFFFL); - return ft; - } - - /** - * Try to extract file descriptor from filehandle - * This is implementation-specific and may need adjustment - */ private static int getFileDescriptor(RuntimeScalar filehandle) { - // This would need to access the internal structure of RuntimeGlob - // to get the FileDescriptor or file handle - // Placeholder implementation return -1; } - /** - * Try to get filename from filehandle - * This is implementation-specific and may need adjustment - */ private static String getFilenameFromHandle(RuntimeScalar filehandle) { - // This would need to access the internal structure of RuntimeGlob - // to get the associated filename - // Placeholder implementation return null; } - /** - * Check if the system supports high-precision time setting - */ public static boolean supportsHighPrecision() { - if (IS_WINDOWS) { - return true; // Windows FILETIME has 100-nanosecond precision - } else { - try { - // Check if utimes is available - ExtendedPosixLibrary.INSTANCE.utimes("/dev/null", null); - return true; - } catch (Exception e) { - return false; - } - } - } - - /** - * Extended POSIX interface for utime functions - */ - public interface ExtendedPosixLibrary extends Library { - ExtendedPosixLibrary INSTANCE = Native.load("c", ExtendedPosixLibrary.class); - - int utime(String filename, Utimbuf times); - - int utimes(String filename, Timeval[] times); - - int futimes(int fd, Timeval[] times); - - int lutimes(String filename, Timeval[] times); // For symlinks - } - - /** - * Structure for POSIX utimbuf - */ - public static class Utimbuf extends Structure { - public NativeLong actime; // access time - public NativeLong modtime; // modification time - - @Override - protected java.util.List getFieldOrder() { - return java.util.Arrays.asList("actime", "modtime"); - } - } - - /** - * Structure for POSIX timeval (used by utimes) - */ - public static class Timeval extends Structure { - public NativeLong tv_sec; // seconds - public NativeLong tv_usec; // microseconds - - @Override - protected java.util.List getFieldOrder() { - return java.util.Arrays.asList("tv_sec", "tv_usec"); - } + return true; } } diff --git a/src/main/java/org/perlonjava/runtime/operators/WaitpidOperator.java b/src/main/java/org/perlonjava/runtime/operators/WaitpidOperator.java index 792f51e20..14d0c8501 100644 --- a/src/main/java/org/perlonjava/runtime/operators/WaitpidOperator.java +++ b/src/main/java/org/perlonjava/runtime/operators/WaitpidOperator.java @@ -1,12 +1,6 @@ package org.perlonjava.runtime.operators; -import com.sun.jna.LastErrorException; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.platform.win32.Kernel32; -import com.sun.jna.platform.win32.WinBase; -import com.sun.jna.platform.win32.WinNT; -import com.sun.jna.ptr.IntByReference; +import org.perlonjava.runtime.nativ.NativeUtils; import org.perlonjava.runtime.nativ.PosixLibrary; import org.perlonjava.runtime.runtimetypes.RuntimeArray; import org.perlonjava.runtime.runtimetypes.RuntimeBase; @@ -19,68 +13,20 @@ import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable; import static org.perlonjava.runtime.runtimetypes.RuntimeContextType.SCALAR; -/** - * Native implementation of Perl's waitpid operator using JNA - *

- * - On POSIX systems: Uses native waitpid() system call - * - On Windows: Uses Windows process APIs for equivalent functionality - * - Properly retrieves exit codes and signal information - * - Supports WNOHANG for non-blocking waits - * - Better performance (no external process spawning) - */ public class WaitpidOperator { - // POSIX wait flags - public static final int WNOHANG = 1; // Non-blocking wait - public static final int WUNTRACED = 2; // Also return if child stopped - public static final int WCONTINUED = 8; // Also return if stopped child continued + public static final int WNOHANG = 1; + public static final int WUNTRACED = 2; + public static final int WCONTINUED = 8; - // Windows-specific constants - private static final int STILL_ACTIVE = 259; - private static final int INFINITE = -1; - private static final int WAIT_TIMEOUT = 0x00000102; + private static final Map windowsChildProcesses = new ConcurrentHashMap<>(); - // Track child processes on Windows (since Windows doesn't have parent-child waitpid semantics) - private static final Map windowsChildProcesses = new ConcurrentHashMap<>(); + private static final boolean IS_WINDOWS = NativeUtils.IS_WINDOWS; - private static final boolean IS_WINDOWS = Platform.isWindows(); - - /** - * Clean up any remaining Windows handles on shutdown - */ - static { - if (IS_WINDOWS) { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - for (WinNT.HANDLE handle : windowsChildProcesses.values()) { - try { - Kernel32.INSTANCE.CloseHandle(handle); - } catch (Exception ignored) { - } - } - windowsChildProcesses.clear(); - })); - } - } - - /** - * Implements Perl's wait operator (waits for any child process) - * - * @return RuntimeScalar with PID of terminated child, or -1 if no children - */ public static RuntimeScalar waitForChild() { - // wait() is equivalent to waitpid(-1, 0) return waitpid(SCALAR, new RuntimeScalar(-1), new RuntimeScalar(0)); } - /** - * Implements Perl's waitpid operator using native system calls - * - * @param args RuntimeBase containing the PID to wait for; RuntimeBase containing wait flags - * @return RuntimeScalar with: - * - PID: if the specified process has terminated - * - 0: if WNOHANG is set and process is still running - * - -1: for error conditions - */ public static RuntimeScalar waitpid(int ctx, RuntimeBase... args) { var list = new RuntimeArray(args); @@ -102,23 +48,23 @@ private static RuntimeScalar waitpidPosix(int pid, int flags) { } } try { - IntByReference status = new IntByReference(); - int result = PosixLibrary.INSTANCE.waitpid(pid, status, flags); + int[] status = new int[1]; + long result = PosixLibrary.INSTANCE.waitpid(pid, status, flags); if (result > 0) { - setExitStatus(status.getValue()); - return new RuntimeScalar(result); + setExitStatus(status[0]); + return new RuntimeScalar((int) result); } else if (result == 0) { return new RuntimeScalar(0); } else { - int errno = Native.getLastError(); + int errno = PosixLibrary.INSTANCE.errno(); if (errno == 10) { // ECHILD return new RuntimeScalar(-1); } setExitStatus(-1); return new RuntimeScalar(-1); } - } catch (LastErrorException e) { + } catch (Exception e) { return new RuntimeScalar(-1); } } @@ -143,160 +89,86 @@ private static RuntimeScalar waitpidJavaProcess(int pid, Process process, int fl } } - /** - * Windows implementation using Windows process APIs - */ private static RuntimeScalar waitpidWindows(int pid, int flags) { boolean nonBlocking = (flags & WNOHANG) != 0; if (pid <= 0) { - // Windows doesn't support process groups in the same way - // Return -1 to indicate no matching processes return new RuntimeScalar(-1); } - // First check if this is one of our tracked child processes - WinNT.HANDLE childHandle = windowsChildProcesses.get(pid); - - WinNT.HANDLE processHandle; - boolean isOurChild = false; - - if (childHandle != null) { - processHandle = childHandle; - isOurChild = true; - } else { - // Try to open the process (may fail if it doesn't exist or we lack permissions) - processHandle = Kernel32.INSTANCE.OpenProcess( - WinNT.PROCESS_QUERY_INFORMATION | WinNT.SYNCHRONIZE, - false, - pid - ); - - if (processHandle == null) { - // Process doesn't exist or we can't access it - return new RuntimeScalar(-1); + Process childProcess = windowsChildProcesses.get((long) pid); + if (childProcess != null) { + if (nonBlocking) { + if (childProcess.isAlive()) return new RuntimeScalar(0); + } else { + try { + childProcess.waitFor(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return new RuntimeScalar(-1); + } } + int exitCode = childProcess.exitValue(); + windowsChildProcesses.remove((long) pid); + setExitStatus(exitCode << 8); + return new RuntimeScalar(pid); } - try { - // Wait for the process - int waitTime = nonBlocking ? 0 : INFINITE; - int waitResult = Kernel32.INSTANCE.WaitForSingleObject(processHandle, waitTime); - - if (waitResult == WinBase.WAIT_OBJECT_0) { - // Process has terminated - IntByReference exitCode = new IntByReference(); - if (Kernel32.INSTANCE.GetExitCodeProcess(processHandle, exitCode)) { - // Windows exit codes are just the value, not encoded like POSIX - // Shift left by 8 to match POSIX convention - setExitStatus(exitCode.getValue() << 8); - } else { - setExitStatus(0); - } - - // Remove from our tracked children if it was there - if (isOurChild) { - windowsChildProcesses.remove(pid); - } - - return new RuntimeScalar(pid); - } else if (waitResult == WAIT_TIMEOUT) { - // WNOHANG was specified and process is still running - return new RuntimeScalar(0); - } else { - // Error occurred - return new RuntimeScalar(-1); - } - } finally { - // Clean up handle if we opened it - if (!isOurChild && processHandle != null) { - Kernel32.INSTANCE.CloseHandle(processHandle); - } + var ph = ProcessHandle.of(pid); + if (ph.isEmpty()) return new RuntimeScalar(-1); + if (nonBlocking) { + if (ph.get().isAlive()) return new RuntimeScalar(0); + setExitStatus(0); + return new RuntimeScalar(pid); } + ph.get().onExit().join(); + setExitStatus(0); + return new RuntimeScalar(pid); } - /** - * Register a child process on Windows - * This should be called when creating child processes on Windows - * to enable proper parent-child waitpid semantics - */ - public static void registerWindowsChildProcess(int pid, WinNT.HANDLE handle) { - if (IS_WINDOWS) { - windowsChildProcesses.put(pid, handle); - } + public static void registerChildProcess(long pid, Process process) { + windowsChildProcesses.put(pid, process); } - /** - * Set Perl's exit status variables - * - * @param status The raw status value from waitpid - */ private static void setExitStatus(int status) { - // Set $? (CHILD_ERROR) getGlobalVariable("main::?").set(new RuntimeScalar(status)); - // Set ${^CHILD_ERROR_NATIVE} try { getGlobalVariable("main::^CHILD_ERROR_NATIVE").set(new RuntimeScalar(status)); } catch (Exception e) { - // Variable might not exist in all Perl versions } } - /** - * Check if a process is running (utility method) - * - * @param pid Process ID to check - * @return true if process appears to be running, false if terminated/not found - */ public static boolean isProcessRunning(int pid) { RuntimeScalar result = waitpid(SCALAR, new RuntimeScalar(pid), new RuntimeScalar(WNOHANG) ); - return result.getInt() == 0; // 0 means still running with WNOHANG + return result.getInt() == 0; } - /** - * Extract exit code from status value (WEXITSTATUS macro) - */ public static int getExitCode(int status) { return (status >> 8) & 0xFF; } - /** - * Check if process exited normally (WIFEXITED macro) - */ public static boolean exitedNormally(int status) { return (status & 0xFF) == 0; } - /** - * Get signal that terminated process (WTERMSIG macro) - */ public static int getTerminationSignal(int status) { return status & 0x7F; } - /** - * Check if process was terminated by signal (WIFSIGNALED macro) - */ public static boolean wasSignaled(int status) { int sig = getTerminationSignal(status); return sig != 0 && sig != 0x7F; } - /** - * Check if process was stopped (WIFSTOPPED macro) - */ public static boolean wasStopped(int status) { return (status & 0xFF) == 0x7F; } - /** - * Get signal that stopped process (WSTOPSIG macro) - */ public static int getStopSignal(int status) { return (status >> 8) & 0xFF; } -} \ No newline at end of file +}