Skip to content

Commit

Permalink
restore original fds on failure
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil committed Sep 8, 2024
1 parent 0fa04c6 commit 9e03115
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 18 deletions.
48 changes: 30 additions & 18 deletions src/crystal/system/win32/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,9 @@ struct Crystal::System::Process
end

private def self.try_replace(command_args, env, clear_env, input, output, error, chdir)
reopen_io(input, ORIGINAL_STDIN)
reopen_io(output, ORIGINAL_STDOUT)
reopen_io(error, ORIGINAL_STDERR)
old_input_fd = reopen_io(input, ORIGINAL_STDIN)
old_output_fd = reopen_io(output, ORIGINAL_STDOUT)
old_error_fd = reopen_io(error, ORIGINAL_STDERR)

ENV.clear if clear_env
env.try &.each do |key, val|
Expand All @@ -351,11 +351,18 @@ struct Crystal::System::Process
argv << Pointer(LibC::WCHAR).null

LibC._wexecvp(command, argv)

# exec failed; restore the original C runtime file descriptors
errno = Errno.value
LibC._dup2(old_input_fd, 0)
LibC._dup2(old_output_fd, 1)
LibC._dup2(old_error_fd, 2)
errno
end

def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn
try_replace(command_args, env, clear_env, input, output, error, chdir)
raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0])
errno = try_replace(command_args, env, clear_env, input, output, error, chdir)
raise_exception_from_errno(command_args.is_a?(String) ? command_args : command_args[0], errno)
end

private def self.raise_exception_from_errno(command, errno = Errno.value)
Expand All @@ -368,14 +375,22 @@ struct Crystal::System::Process
end

# Replaces the C standard streams' file descriptors, not Win32's, since
# `try_replace` uses the C `LibC._wexecvp` and only cares about the former
# `try_replace` uses the C `LibC._wexecvp` and only cares about the former.
# Returns a duplicate of the original file descriptor
private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor)
src_io = to_real_fd(src_io)

unless src_io.system_blocking?
raise IO::Error.new("Non-blocking streams are not supported in `Process.exec`", target: src_io)
end

src_fd =
case src_io
when STDIN then 0
when STDOUT then 1
when STDERR then 2
else
LibC._open_osfhandle(src_io.windows_handle, 0)
end

dst_fd =
case dst_io
when ORIGINAL_STDIN then 0
Expand All @@ -385,18 +400,15 @@ struct Crystal::System::Process
raise "BUG: Invalid destination IO"
end

if LibC._dup2(LibC._open_osfhandle(src_io.windows_handle, 0), dst_fd) == -1
raise RuntimeError.from_errno("Failed to replace C file descriptor")
end
end
return src_fd if dst_fd == src_fd

private def self.to_real_fd(fd : IO::FileDescriptor)
case fd
when STDIN then ORIGINAL_STDIN
when STDOUT then ORIGINAL_STDOUT
when STDERR then ORIGINAL_STDERR
else fd
orig_src_fd = LibC._dup(src_fd)

if LibC._dup2(src_fd, dst_fd) == -1
raise IO::Error.from_errno("Failed to replace C file descriptor", target: dst_io)
end

orig_src_fd
end

def self.chroot(path)
Expand Down
1 change: 1 addition & 0 deletions src/lib_c/x86_64-windows-msvc/c/io.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ require "c/stdint"
lib LibC
fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT
fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
fun _dup(fd : Int) : Int
fun _dup2(fd1 : Int, fd2 : Int) : Int

# unused
Expand Down

0 comments on commit 9e03115

Please sign in to comment.