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
4 changes: 2 additions & 2 deletions src/crystal/system/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ struct Crystal::System::Process
# def self.fork(&)

# Launches a child process with the command + args.
# def self.spawn(command_args : Args, env : Env?, clear_env : Bool, input : Stdio, output : Stdio, error : Stdio, chdir : Path | String?) : ProcessInformation
# def self.spawn(prepared_args : Args, env : Env?, clear_env : Bool, input : Stdio, output : Stdio, error : Stdio, chdir : Path | String?) : ProcessInformation

# Replaces the current process with a new one.
# def self.replace(command_args : Args, env : Env?, clear_env : Bool, input : Stdio, output : Stdio, error : Stdio, chdir : Path | String?) : NoReturn
# def self.replace(command : String, prepared_args : Args, env : Env?, clear_env : Bool, input : Stdio, output : Stdio, error : Stdio, chdir : Path | String?) : NoReturn

# Converts a command and array of arguments to the system-specific representation.
# def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : Args
Expand Down
40 changes: 19 additions & 21 deletions src/crystal/system/unix/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ struct Crystal::System::Process
end
end

def self.spawn(command_args, env, clear_env, input, output, error, chdir)
def self.spawn(prepared_args, env, clear_env, input, output, error, chdir)
r, w = FileDescriptor.system_pipe

pid = fork(will_exec: true) do
Expand All @@ -266,7 +266,7 @@ struct Crystal::System::Process
if !pid
LibC.close(r)
begin
self.try_replace(command_args, env, clear_env, input, output, error, chdir)
self.try_replace(prepared_args, env, clear_env, input, output, error, chdir)
byte = 1_u8
errno = Errno.value.to_i32
FileDescriptor.write_fully(w, pointerof(byte))
Expand All @@ -292,15 +292,15 @@ struct Crystal::System::Process
when 0
# Error message coming
message = reader_pipe.gets_to_end
raise RuntimeError.new("Error executing process: '#{command_args[0]}': #{message}")
raise RuntimeError.new("Error executing process: '#{prepared_args[0]}': #{message}")
when 1
# Errno coming
# can't use IO#read_bytes(Int32) because we skipped system/network
# endianness check when writing the integer while read_bytes would;
# we thus read it in the same as order as written
buf = uninitialized StaticArray(UInt8, 4)
reader_pipe.read_fully(buf.to_slice)
raise_exception_from_errno(command_args[0], Errno.new(buf.unsafe_as(Int32)))
raise_exception_from_errno(prepared_args[0], Errno.new(buf.unsafe_as(Int32)))
else
raise RuntimeError.new("BUG: Invalid error response received from subprocess")
end
Expand All @@ -311,28 +311,30 @@ struct Crystal::System::Process
pid
end

def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : Array(String)
def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : {String, LibC::Char**}
if shell
command = %(#{command} "${@}") unless command.includes?(' ')
shell_args = ["/bin/sh", "-c", command, "sh"]
argv_ary = ["/bin/sh", "-c", command, "sh"]

if args
unless command.includes?(%("${@}"))
raise ArgumentError.new(%(Can't specify arguments in both command and args without including "${@}" into your command))
end

shell_args.concat(args)
end

shell_args
pathname = "/bin/sh"
else
command_args = [command]
command_args.concat(args) if args
command_args
argv_ary = [command]
pathname = command
end

argv_ary.concat(args) if args

argv = argv_ary.map(&.check_no_null_byte.to_unsafe)
{pathname, argv.to_unsafe}
end

private def self.try_replace(command_args, env, clear_env, input, output, error, chdir)
private def self.try_replace(prepared_args, env, clear_env, input, output, error, chdir)
reopen_io(input, ORIGINAL_STDIN)
reopen_io(output, ORIGINAL_STDOUT)
reopen_io(error, ORIGINAL_STDERR)
Expand All @@ -348,11 +350,7 @@ struct Crystal::System::Process

::Dir.cd(chdir) if chdir

command = command_args[0]
argv = command_args.map &.check_no_null_byte.to_unsafe
argv << Pointer(UInt8).null

lock_write { execvpe(command, argv, LibC.environ) }
lock_write { LibC.execvp(*prepared_args) }
end

private def self.execvpe(command, argv, envp)
Expand All @@ -373,9 +371,9 @@ struct Crystal::System::Process
LibC.execvp(command, argv)
end

def self.replace(command_args, env, clear_env, input, output, error, chdir)
try_replace(command_args, env, clear_env, input, output, error, chdir)
raise_exception_from_errno(command_args[0])
def self.replace(command, prepared_args, env, clear_env, input, output, error, chdir)
try_replace(prepared_args, env, clear_env, input, output, error, chdir)
raise_exception_from_errno(command)
end

private def self.raise_exception_from_errno(command, errno = Errno.value)
Expand Down
4 changes: 2 additions & 2 deletions src/crystal/system/wasi/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ struct Crystal::System::Process
raise NotImplementedError.new("Process.fork")
end

def self.spawn(command_args, env, clear_env, input, output, error, chdir)
def self.spawn(prepared_args, env, clear_env, input, output, error, chdir)
raise NotImplementedError.new("Process.spawn")
end

def self.prepare_args(command : String, args : Enumerable(String)?, shell : Bool) : Array(String)
raise NotImplementedError.new("Process.prepare_args")
end

def self.replace(command_args, env, clear_env, input, output, error, chdir)
def self.replace(command, prepared_args, env, clear_env, input, output, error, chdir)
raise NotImplementedError.new("Process.replace")
end

Expand Down
32 changes: 16 additions & 16 deletions src/crystal/system/win32/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ struct Crystal::System::Process
{new_handle, dup_handle}
end

def self.spawn(command_args, env, clear_env, input, output, error, chdir)
def self.spawn(prepared_args, env, clear_env, input, output, error, chdir)
startup_info = LibC::STARTUPINFOW.new
startup_info.cb = sizeof(LibC::STARTUPINFOW)
startup_info.dwFlags = LibC::STARTF_USESTDHANDLES
Expand All @@ -293,19 +293,19 @@ struct Crystal::System::Process

process_info = LibC::PROCESS_INFORMATION.new

command_args = ::Process.quote_windows(command_args) unless command_args.is_a?(String)
prepared_args = ::Process.quote_windows(prepared_args) unless prepared_args.is_a?(String)

if LibC.CreateProcessW(
nil, System.to_wstr(command_args), nil, nil, true, LibC::CREATE_SUSPENDED | LibC::CREATE_UNICODE_ENVIRONMENT,
nil, System.to_wstr(prepared_args), nil, nil, true, LibC::CREATE_SUSPENDED | LibC::CREATE_UNICODE_ENVIRONMENT,
make_env_block(env, clear_env), chdir.try { |str| System.to_wstr(str) } || Pointer(UInt16).null,
pointerof(startup_info), pointerof(process_info)
) == 0
error = WinError.value
case error.to_errno
when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC
raise ::File::Error.from_os_error("Error executing process", error, file: command_args)
raise ::File::Error.from_os_error("Error executing process", error, file: prepared_args)
else
raise IO::Error.from_os_error("Error executing process: '#{command_args}'", error)
raise IO::Error.from_os_error("Error executing process: '#{prepared_args}'", error)
end
end

Expand Down Expand Up @@ -337,13 +337,13 @@ struct Crystal::System::Process
raise ::File::Error.from_os_error("Error executing process", WinError::ERROR_BAD_EXE_FORMAT, file: command)
end

command_args = [command]
command_args.concat(args) if args
command_args
prepared_args = [command]
prepared_args.concat(args) if args
prepared_args
end
end

private def self.try_replace(command_args, env, clear_env, input, output, error, chdir)
private def self.try_replace(command, prepared_args, env, clear_env, input, output, error, chdir)
old_input_fd = reopen_io(input, ORIGINAL_STDIN)
old_output_fd = reopen_io(output, ORIGINAL_STDOUT)
old_error_fd = reopen_io(error, ORIGINAL_STDERR)
Expand All @@ -359,12 +359,12 @@ struct Crystal::System::Process

::Dir.cd(chdir) if chdir

if command_args.is_a?(String)
command = System.to_wstr(command_args)
if prepared_args.is_a?(String)
command = System.to_wstr(prepared_args)
argv = [command]
else
command = System.to_wstr(command_args[0])
argv = command_args.map { |arg| System.to_wstr(arg) }
command = System.to_wstr(prepared_args[0])
argv = prepared_args.map { |arg| System.to_wstr(arg) }
end
argv << Pointer(LibC::WCHAR).null

Expand All @@ -378,9 +378,9 @@ struct Crystal::System::Process
errno
end

def self.replace(command_args, env, clear_env, input, output, error, chdir) : NoReturn
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)
def self.replace(command, prepared_args, env, clear_env, input, output, error, chdir) : NoReturn
errno = try_replace(command, prepared_args, env, clear_env, input, output, error, chdir)
raise_exception_from_errno(prepared_args.is_a?(String) ? prepared_args : prepared_args[0], errno)
end

private def self.raise_exception_from_errno(command, errno = Errno.value)
Expand Down
8 changes: 4 additions & 4 deletions src/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,13 @@ class Process
# Raises `IO::Error` if executing the command fails (for example if the executable doesn't exist).
def self.exec(command : String, args : Enumerable(String)? = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
input : ExecStdio = Redirect::Inherit, output : ExecStdio = Redirect::Inherit, error : ExecStdio = Redirect::Inherit, chdir : Path | String? = nil) : NoReturn
command_args = Crystal::System::Process.prepare_args(command, args, shell)
prepared_args = Crystal::System::Process.prepare_args(command, args, shell)

input = exec_stdio_to_fd(input, for: STDIN)
output = exec_stdio_to_fd(output, for: STDOUT)
error = exec_stdio_to_fd(error, for: STDERR)

Crystal::System::Process.replace(command_args, env, clear_env, input, output, error, chdir)
Crystal::System::Process.replace(command, prepared_args, env, clear_env, input, output, error, chdir)
end

private def self.exec_stdio_to_fd(stdio : ExecStdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
Expand Down Expand Up @@ -280,13 +280,13 @@ class Process
# Raises `IO::Error` if executing the command fails (for example if the executable doesn't exist).
def initialize(command : String, args : Enumerable(String)? = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false,
input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : Path | String? = nil)
command_args = Crystal::System::Process.prepare_args(command, args, shell)
prepared_args = Crystal::System::Process.prepare_args(command, args, shell)

fork_input = stdio_to_fd(input, for: STDIN)
fork_output = stdio_to_fd(output, for: STDOUT)
fork_error = stdio_to_fd(error, for: STDERR)

pid = Crystal::System::Process.spawn(command_args, env, clear_env, fork_input, fork_output, fork_error, chdir.try &.to_s)
pid = Crystal::System::Process.spawn(prepared_args, env, clear_env, fork_input, fork_output, fork_error, chdir.try &.to_s)
@process_info = Crystal::System::Process.new(pid)

fork_input.close unless fork_input.in?(input, STDIN)
Expand Down