Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,27 @@ pending_interpreted describe: Process do
{% end %}

describe ".exec" do
it "redirects STDIN and STDOUT to files", tags: %w[slow] do
with_tempfile("crystal-exec-stdin", "crystal-exec-stdout") do |stdin_path, stdout_path|
File.write(stdin_path, "foobar")

status, _, _ = compile_and_run_source <<-CRYSTAL
command = #{stdin_to_stdout_command[0].inspect}
args = #{stdin_to_stdout_command[1].to_a} of String
stdin_path = #{stdin_path.inspect}
stdout_path = #{stdout_path.inspect}
File.open(stdin_path) do |input|
File.open(stdout_path, "w") do |output|
Process.exec(command, args, input: input, output: output)
end
end
CRYSTAL

status.success?.should be_true
File.read(stdout_path).chomp.should eq("foobar")
end
end

it "gets error from exec" do
expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do
Process.exec("foobarbaz")
Expand Down
59 changes: 43 additions & 16 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 @@ -367,21 +374,41 @@ struct Crystal::System::Process
end
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.
# 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

dst_io.reopen(src_io)
dst_io.blocking = true
dst_io.close_on_exec = false
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

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
dst_fd =
case dst_io
when ORIGINAL_STDIN then 0
when ORIGINAL_STDOUT then 1
when ORIGINAL_STDERR then 2
else
raise "BUG: Invalid destination IO"
end

return src_fd if dst_fd == src_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
5 changes: 3 additions & 2 deletions src/lib_c/x86_64-windows-msvc/c/io.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ 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
fun _open_osfhandle(osfhandle : HANDLE, flags : LibC::Int) : LibC::Int
fun _get_osfhandle(fd : Int) : IntPtrT
fun _close(fd : Int) : Int
fun _dup2(fd1 : Int, fd2 : Int) : Int
fun _isatty(fd : Int) : Int
fun _write(fd : Int, buffer : UInt8*, count : UInt) : Int
fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int
Expand Down