Loading src/client/posix_spawn.cpp +31 −12 Changes for src/client/posix_spawn.cpp: 31 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -63,17 +63,36 @@ extern char** environ; namespace { static int (*real_posix_spawn)( pid_t* __restrict, const char* __restrict, // AArch64 provides dup3, but not the legacy dup2 system call. long spawn_dup2_no_intercept(int oldfd, int newfd) { if(oldfd == newfd) { const long flags = syscall_no_intercept(SYS_fcntl, oldfd, F_GETFD); if(flags < 0) { return flags; } // A spawn dup2 action must keep this descriptor across exec even // when no duplication is needed (unlike a plain dup2(fd, fd)). const long result = syscall_no_intercept(SYS_fcntl, oldfd, F_SETFD, flags & ~FD_CLOEXEC); return result < 0 ? result : newfd; } #ifdef SYS_dup2 return syscall_no_intercept(SYS_dup2, oldfd, newfd); #else return syscall_no_intercept(SYS_dup3, oldfd, newfd, 0); #endif } static int (*real_posix_spawn)(pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; static int (*real_posix_spawnp)( pid_t* __restrict, const char* __restrict, static int (*real_posix_spawnp)(pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; int dlsym_posix_spawn(pid_t* __restrict pid, const char* __restrict path, Loading Loading @@ -335,8 +354,8 @@ gkfs_posix_spawn_impl(pid_t* __restrict pid, break; } case 1: /* spawn_do_dup2 */ { long res = syscall_no_intercept( SYS_dup2, act.action.dup2_action.fd, long res = spawn_dup2_no_intercept( act.action.dup2_action.fd, act.action.dup2_action.newfd); if(res < 0) { fail_child(syscall_error_code(res)); Loading @@ -354,8 +373,8 @@ gkfs_posix_spawn_impl(pid_t* __restrict pid, } int fd = static_cast<int>(res); if(fd != act.action.open_action.fd) { long dres = syscall_no_intercept( SYS_dup2, fd, act.action.open_action.fd); long dres = spawn_dup2_no_intercept( fd, act.action.open_action.fd); syscall_no_intercept(SYS_close, fd); if(dres < 0) { fail_child(syscall_error_code(dres)); Loading tests/integration/rename/test_spawn_dup_actions.py 0 → 100644 +29 −0 Changes for tests/integration/rename/test_spawn_dup_actions.py: 29 added lines, 0 removed lines. Original line number Diff line number Diff line """Exercise spawn descriptor actions from a virtual working directory.""" import sys def test_spawn_dup_actions(gkfs_daemon, gkfs_shell, tmp_path): output = tmp_path / "spawn-output" code = f""" import os import sys os.chdir({str(gkfs_daemon.mountdir)!r}) fd = os.open({str(output)!r}, os.O_CREAT | os.O_RDWR | os.O_CLOEXEC, 0o600) try: # Force GekkoFS's custom spawn path through the virtual cwd. Reuse fd # in the child: POSIX requires dup2(fd, fd) to clear FD_CLOEXEC here. child = 'import os; os.write(' + str(fd) + ', b"same-fd")' pid = os.posix_spawn(sys.executable, [sys.executable, '-c', child], os.environ, file_actions=[(os.POSIX_SPAWN_DUP2, fd, fd)]) assert os.waitpid(pid, 0)[1] == 0 pid = os.posix_spawn('/usr/bin/printf', ['printf', '-different-fd'], os.environ, file_actions=[(os.POSIX_SPAWN_DUP2, fd, 1)]) assert os.waitpid(pid, 0)[1] == 0 os.lseek(fd, 0, os.SEEK_SET) assert os.read(fd, 128) == b'same-fd-different-fd' finally: os.close(fd) """ result = gkfs_shell.run(sys.executable, "-c", code) assert result.exit_code == 0, result.stderr.decode() No newline at end of file Loading
src/client/posix_spawn.cpp +31 −12 Changes for src/client/posix_spawn.cpp: 31 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -63,17 +63,36 @@ extern char** environ; namespace { static int (*real_posix_spawn)( pid_t* __restrict, const char* __restrict, // AArch64 provides dup3, but not the legacy dup2 system call. long spawn_dup2_no_intercept(int oldfd, int newfd) { if(oldfd == newfd) { const long flags = syscall_no_intercept(SYS_fcntl, oldfd, F_GETFD); if(flags < 0) { return flags; } // A spawn dup2 action must keep this descriptor across exec even // when no duplication is needed (unlike a plain dup2(fd, fd)). const long result = syscall_no_intercept(SYS_fcntl, oldfd, F_SETFD, flags & ~FD_CLOEXEC); return result < 0 ? result : newfd; } #ifdef SYS_dup2 return syscall_no_intercept(SYS_dup2, oldfd, newfd); #else return syscall_no_intercept(SYS_dup3, oldfd, newfd, 0); #endif } static int (*real_posix_spawn)(pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; static int (*real_posix_spawnp)( pid_t* __restrict, const char* __restrict, static int (*real_posix_spawnp)(pid_t* __restrict, const char* __restrict, const posix_spawn_file_actions_t* __restrict, const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; const posix_spawnattr_t* __restrict, char* const[], char* const[]) = nullptr; int dlsym_posix_spawn(pid_t* __restrict pid, const char* __restrict path, Loading Loading @@ -335,8 +354,8 @@ gkfs_posix_spawn_impl(pid_t* __restrict pid, break; } case 1: /* spawn_do_dup2 */ { long res = syscall_no_intercept( SYS_dup2, act.action.dup2_action.fd, long res = spawn_dup2_no_intercept( act.action.dup2_action.fd, act.action.dup2_action.newfd); if(res < 0) { fail_child(syscall_error_code(res)); Loading @@ -354,8 +373,8 @@ gkfs_posix_spawn_impl(pid_t* __restrict pid, } int fd = static_cast<int>(res); if(fd != act.action.open_action.fd) { long dres = syscall_no_intercept( SYS_dup2, fd, act.action.open_action.fd); long dres = spawn_dup2_no_intercept( fd, act.action.open_action.fd); syscall_no_intercept(SYS_close, fd); if(dres < 0) { fail_child(syscall_error_code(dres)); Loading
tests/integration/rename/test_spawn_dup_actions.py 0 → 100644 +29 −0 Changes for tests/integration/rename/test_spawn_dup_actions.py: 29 added lines, 0 removed lines. Original line number Diff line number Diff line """Exercise spawn descriptor actions from a virtual working directory.""" import sys def test_spawn_dup_actions(gkfs_daemon, gkfs_shell, tmp_path): output = tmp_path / "spawn-output" code = f""" import os import sys os.chdir({str(gkfs_daemon.mountdir)!r}) fd = os.open({str(output)!r}, os.O_CREAT | os.O_RDWR | os.O_CLOEXEC, 0o600) try: # Force GekkoFS's custom spawn path through the virtual cwd. Reuse fd # in the child: POSIX requires dup2(fd, fd) to clear FD_CLOEXEC here. child = 'import os; os.write(' + str(fd) + ', b"same-fd")' pid = os.posix_spawn(sys.executable, [sys.executable, '-c', child], os.environ, file_actions=[(os.POSIX_SPAWN_DUP2, fd, fd)]) assert os.waitpid(pid, 0)[1] == 0 pid = os.posix_spawn('/usr/bin/printf', ['printf', '-different-fd'], os.environ, file_actions=[(os.POSIX_SPAWN_DUP2, fd, 1)]) assert os.waitpid(pid, 0)[1] == 0 os.lseek(fd, 0, os.SEEK_SET) assert os.read(fd, 128) == b'same-fd-different-fd' finally: os.close(fd) """ result = gkfs_shell.run(sys.executable, "-c", code) assert result.exit_code == 0, result.stderr.decode() No newline at end of file