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
+}