From a568ebd5565c1db522c6baf4e1ab7d64a333531d Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 29 Apr 2026 11:56:53 +0200 Subject: [PATCH 1/2] fix(encode,errno): add iso-10646-1 alias and make `local $!` reset errno MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes uncovered while investigating `jcpan -t CSV::Processor`, which cascades through Email::Extractor, Text::AutoCSV, and File::BOM. 1. Encode.java: register `iso-10646-1` / `ISO-10646-1` as aliases for UCS-2 (UTF-16BE for the BMP), matching real Perl Encode's alias list. File::BOM uses this alias when building its enc2bom table; without it, `use File::BOM` died with "Unknown encoding: iso-10646-1" and the whole module failed to load (which in turn broke Text::AutoCSV, etc.). 2. ErrnoVariable.dynamicSaveState: after pushing the saved errno/message, reset the variable to the empty/no-error state so that `local $!;` actually clears $! inside the dynamic scope, the way Perl does. The parent `RuntimeScalar.dynamicSaveState` resets type/value to UNDEF, but ErrnoVariable's `toString()`/`getInt()` read its private `errno` and `message` fields, which were not being cleared. As a result, `local $!;` was a no-op for the visible value of $!. File::BOM relies on this in `_safe_read`: local $!; my $status = read($fh, my $out, $count); die $! if !$status && $!; With the bug, a stale $! from earlier in the program made every end-of-file read look like an error, killing t/01..bom.t at test 41. Test impact (./jperl): - File::BOM t/01..bom.t: 41 → 85 passing tests (cutoff is now POSIX::mkfifo, an unrelated unimplemented function). - Text::AutoCSV: 7/48 → 535/560 passing tests (cascade unblocked). - CSV::Processor itself loads and its Utils.t passes; the remaining failure is the (already known) Email::Extractor → LWPx::TimedHTTP chain, which depends on `fork`. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../java/org/perlonjava/runtime/perlmodule/Encode.java | 4 ++++ .../org/perlonjava/runtime/runtimetypes/ErrnoVariable.java | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/Encode.java b/src/main/java/org/perlonjava/runtime/perlmodule/Encode.java index e65cfada6..3485cdc8e 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/Encode.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/Encode.java @@ -90,6 +90,10 @@ public class Encode extends PerlModuleBase { CHARSET_ALIASES.put("UCS-2LE", StandardCharsets.UTF_16LE); CHARSET_ALIASES.put("ucs-2le", StandardCharsets.UTF_16LE); + // ISO-10646-1 is an Encode alias for UCS-2 (used by File::BOM, etc.) + CHARSET_ALIASES.put("iso-10646-1", StandardCharsets.UTF_16BE); + CHARSET_ALIASES.put("ISO-10646-1", StandardCharsets.UTF_16BE); + // Shift_JIS aliases try { Charset shiftJIS = Charset.forName("Shift_JIS"); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java index 97e7911af..82e5aef01 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java @@ -340,6 +340,13 @@ public void dynamicSaveState() { errnoStack.push(new int[]{errno}); messageStack.push(message); super.dynamicSaveState(); + // After saving, reset to the default "no error" state so that + // `local $!;` actually clears the variable inside the dynamic scope, + // matching Perl's semantics. + this.errno = 0; + this.message = ""; + this.type = RuntimeScalarType.DUALVAR; + this.value = new DualVar(new RuntimeScalar(0), new RuntimeScalar("")); } @Override From 5d35b6ca20f1c22cba6ed47f7441a69f6418ea9a Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 29 Apr 2026 12:04:55 +0200 Subject: [PATCH 2/2] feat(POSIX): implement POSIX::mkfifo Adds a real implementation of POSIX::mkfifo backed by libc's mkfifo() on Linux and macOS via the FFM interface. On Windows there is no POSIX FIFO concept, so the function returns -1 with errno=ENOSYS. - FFMPosixInterface: declare `int mkfifo(String path, int mode)`. - FFMPosixLinux: bind libc's `mkfifo` (used on macOS too via the inherited implementation). Resolution is guarded with `ifPresent` so initialization stays robust on hypothetical libcs without it. - FFMPosixWindows: stub that sets ENOSYS and returns -1. - POSIX.java: register `_mkfifo` -> posix_mkfifo. Returns "0 but true" on success and undef + sets $! on failure, matching real Perl's POSIX::mkfifo contract. - POSIX.pm: add `sub mkfifo { POSIX::_mkfifo(@_) }` wrapper next to the other file-management wrappers (mkdir, rmdir, link, ...). Smoke test: $ ./jperl -MPOSIX -e ' my $p = "/tmp/x.$$"; POSIX::mkfifo($p, 0700) or die $!; print "is_fifo=", (-p $p ? 1 : 0), "\n"; unlink $p; my $r = POSIX::mkfifo("/no/such/dir/x", 0700); print "fail=", (defined $r ? $r : "undef"), " errno=$!\n"; ' is_fifo=1 fail=undef errno=No such file or directory This unblocks File::BOM's t/00..setup.t (which previously aborted with "Undefined subroutine &POSIX::mkfifo"). The next blocker on that test path is `fork`, which is a separate, already-known limitation. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../org/perlonjava/core/Configuration.java | 4 +-- .../runtime/nativ/ffm/FFMPosixInterface.java | 8 +++++ .../runtime/nativ/ffm/FFMPosixLinux.java | 34 +++++++++++++++++++ .../runtime/nativ/ffm/FFMPosixWindows.java | 7 ++++ .../perlonjava/runtime/perlmodule/POSIX.java | 23 +++++++++++++ src/main/perl/lib/POSIX.pm | 1 + 6 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 8fc6664d6..4fd48602a 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "36ce11560"; + public static final String gitCommitId = "bebebd07e"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 29 2026 11:48:26"; + public static final String buildTimestamp = "Apr 29 2026 12:11:44"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java index e1c49d2d5..da70fb1ad 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java @@ -122,6 +122,14 @@ public interface FFMPosixInterface { * @return 0 on success, -1 on error */ int link(String oldPath, String newPath); + + /** + * Create a FIFO (named pipe). + * @param path Path of the FIFO to create + * @param mode Permission mode (modified by umask) + * @return 0 on success, -1 on error (check errno) + */ + int mkfifo(String path, int mode); /** * Set file access and modification times. diff --git a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java index f31f67e88..08c1a1db2 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java @@ -45,6 +45,7 @@ public class FFMPosixLinux implements FFMPosixInterface { // Method handles that need errno capture private static MethodHandle killHandle; private static MethodHandle chmodHandle; + private static MethodHandle mkfifoHandle; private static MethodHandle statHandle; private static MethodHandle lstatHandle; @@ -213,6 +214,16 @@ private static synchronized void ensureInitialized() { FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT), captureErrno ); + + // mkfifo is optional — not every libc exposes it (it always does on + // Linux/macOS; this guard just keeps initialization robust). + stdlib.find("mkfifo").ifPresent(addr -> + mkfifoHandle = linker.downcallHandle( + addr, + FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT), + captureErrno + ) + ); // stat and lstat - take path pointer and stat buffer pointer statHandle = linker.downcallHandle( @@ -805,6 +816,29 @@ public int link(String oldPath, String newPath) { return -1; } } + + @Override + public int mkfifo(String path, int mode) { + ensureInitialized(); + if (mkfifoHandle == null) { + // libc has no mkfifo on this platform + setErrno(38); // ENOSYS - function not implemented + return -1; + } + try (Arena arena = Arena.ofConfined()) { + MemorySegment pathSegment = arena.allocateFrom(path); + MemorySegment capturedState = arena.allocate(Linker.Option.captureStateLayout()); + int result = (int) mkfifoHandle.invokeExact(capturedState, pathSegment, mode); + if (result == -1) { + int err = capturedState.get(ValueLayout.JAVA_INT, errnoOffset); + setErrno(err); + } + return result; + } catch (Throwable e) { + setErrno(1); // EPERM + return -1; + } + } @Override public int utimes(String path, long atime, long mtime) { diff --git a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java index 87759bad8..25ede5402 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java @@ -336,6 +336,13 @@ public int link(String oldPath, String newPath) { return -1; } } + + @Override + public int mkfifo(String path, int mode) { + // Windows has no POSIX FIFOs (named pipes use a different API). + setErrno(38); // ENOSYS + return -1; + } @Override public int utimes(String path, long atime, long mtime) { diff --git a/src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java b/src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java index 2b0b913a4..f82eb815e 100644 --- a/src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java +++ b/src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java @@ -42,6 +42,7 @@ public static void initialize() { module.registerMethod("_getcwd", "getcwd", null); module.registerMethod("_strerror", "strerror", null); module.registerMethod("_access", "access", null); + module.registerMethod("_mkfifo", "posix_mkfifo", null); module.registerMethod("_dup2", "dup2", null); // Low-level FD operations @@ -951,6 +952,28 @@ public static RuntimeList access(RuntimeArray args, int ctx) { return new RuntimeScalar("0 but true").getList(); } + /** + * POSIX mkfifo() - create a named pipe (FIFO). + * Arguments: path, mode (e.g. 0700) + * Returns true on success, undef on failure (and sets $!). + */ + public static RuntimeList posix_mkfifo(RuntimeArray args, int ctx) { + if (args.size() < 2) { + GlobalVariable.getGlobalVariable("main::!").set("Bad arguments to mkfifo"); + return RuntimeScalarCache.scalarUndef.getList(); + } + String path = args.get(0).toString(); + int mode = args.get(1).getInt(); + var posix = FFMPosix.get(); + int result = posix.mkfifo(path, mode); + if (result == -1) { + GlobalVariable.getGlobalVariable("main::!").set(posix.strerror(posix.errno())); + return RuntimeScalarCache.scalarUndef.getList(); + } + // Real Perl returns "0 but true" — true in boolean, 0 numerically. + return new RuntimeScalar("0 but true").getList(); + } + // ==================== Low-level FD Operations ==================== /** diff --git a/src/main/perl/lib/POSIX.pm b/src/main/perl/lib/POSIX.pm index 8e3b3da57..2afc99a62 100644 --- a/src/main/perl/lib/POSIX.pm +++ b/src/main/perl/lib/POSIX.pm @@ -361,6 +361,7 @@ sub unlink { POSIX::_unlink(@_) } sub link { POSIX::_link(@_) } sub rename { POSIX::_rename(@_) } sub mkdir { POSIX::_mkdir(@_) } +sub mkfifo { POSIX::_mkfifo(@_) } sub rmdir { POSIX::_rmdir(@_) } sub getcwd { POSIX::_getcwd() } sub chdir { POSIX::_chdir(@_) }