Skip to content

Commit

Permalink
Implement Process for Win32
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-zajic committed Nov 28, 2019
1 parent ff7971d commit c0e8eb9
Show file tree
Hide file tree
Showing 24 changed files with 831 additions and 138 deletions.
10 changes: 5 additions & 5 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,24 @@ describe Process do
end
end

describe "kill" do
describe "signal" do
it "kills a process" do
process = Process.new("yes")
process.kill(Signal::KILL).should be_nil
process.signal(Signal::KILL).should be_nil
end

it "kills many process" do
process1 = Process.new("yes")
process2 = Process.new("yes")
process1.kill(Signal::KILL).should be_nil
process2.kill(Signal::KILL).should be_nil
process1.signal(Signal::KILL).should be_nil
process2.signal(Signal::KILL).should be_nil
end
end

it "gets the pgid of a process id" do
process = Process.new("yes")
Process.pgid(process.pid).should be_a(Int32)
process.kill(Signal::KILL)
process.signal(Signal::KILL)
Process.pgid.should eq(Process.pgid(Process.pid))
end

Expand Down
4 changes: 2 additions & 2 deletions spec/std/signal_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe "Signal" do
Signal::USR1.trap do
ran = true
end
Process.kill Signal::USR1, Process.pid
Process.signal Signal::USR1, Process.pid
10.times do |i|
break if ran
sleep 0.1
Expand All @@ -21,7 +21,7 @@ describe "Signal" do

it "ignores a signal" do
Signal::USR2.ignore
Process.kill Signal::USR2, Process.pid
Process.signal Signal::USR2, Process.pid
end

it "CHLD.reset sets default Crystal child handler" do
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/crystal/tools/playground/server.cr
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ module Crystal::Playground
@logger.info "Code execution killed (session=#{@session_key}, filename=#{@running_process_filename})."
@process = nil
File.delete @running_process_filename rescue nil
process.terminate_gracefully rescue nil
process.terminate rescue nil
end
end

Expand Down
93 changes: 58 additions & 35 deletions src/crystal/system/unix/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,72 +9,69 @@ class Process
@wait_count = 0
end

def wait(pid : LibC::PidT) : Channel(Int32)
return Crystal::SignalChildHandler.wait(pid)
protected def wait(pid : Int64) : Channel(Int32)
Crystal::SignalChildHandler.wait(pid)
end

protected def self.exit_system(status = 0) : NoReturn
LibC.exit(status)
end

protected def self.pid_system : LibC::PidT
LibC.getpid
protected def self.pid_system : Int64
LibC.getpid.to_i64
end

# Returns `true` if the process identified by *pid* is valid for
# a currently registered process, `false` otherwise. Note that this
# returns `true` for a process in the zombie or similar state.
def self.exists_system(pid : Int)
ret = LibC.kill(pid, 0)
if ret == 0
true
else
return false if Errno.value == Errno::ESRCH
raise Errno.new("kill")
end
# Returns the process group identifier of the current process.
def self.pgid : Int64
self.pgid_system
end

protected def self.exists_system(pid : Int64)
ret = LibC.kill(pid.to_i32, 0)
return true if ret == 0
return false if Errno.value == Errno::ESRCH
raise Errno.new("kill")
end

# Returns the process group identifier of the current process.
def self.pgid : LibC::PidT
pgid(0)
protected def self.pgid_system : Int64
pgid(0).to_i64
end

# Returns the process group identifier of the process identified by *pid*.
def self.pgid(pid : Int32) : LibC::PidT
private def self.pgid(pid : Int64) : LibC::PidT
ret = LibC.getpgid(pid)
raise Errno.new("getpgid") if ret < 0
ret
end

# Returns the process identifier of the parent process of the current process.
def self.ppid : LibC::PidT
LibC.getppid
protected def self.ppid_system : Int64
LibC.getppid.to_i64
end

def terminate_system
kill Signal::TERM
protected def terminate_system
signal Signal::TERM
end

def kill_system
kill Signal::KILL
protected def kill_system
signal Signal::KILL
end

# See also: `Process.kill`
def kill(sig = Signal::TERM)
Process.kill sig, @pid
# See also: `Process.signal`
def signal(sig = Signal::TERM)
Process.signal sig, @pid
end

# Sends a *signal* to the processes identified by the given *pids*.
def self.kill(signal : Signal, *pids : Int)
def self.signal(signal : Signal, *pids : Int)
pids.each do |pid|
ret = LibC.kill(pid, signal.value)
raise Errno.new("kill") if ret < 0
end
nil
end

# Creates a process, executes it
def create_and_exec(command : String, args : (Array | Tuple)?, env : Env?, clear_env : Bool, fork_input : IO::FileDescriptor, fork_output : IO::FileDescriptor, fork_error : IO::FileDescriptor, chdir : String?, reader_pipe, writer_pipe)
protected def create_and_exec(command : String, args : (Array | Tuple)?, env : Env?, clear_env : Bool, fork_input : IO::FileDescriptor, fork_output : IO::FileDescriptor, fork_error : IO::FileDescriptor, chdir : String?, reader_pipe, writer_pipe)
if pid = Process.fork_internal(will_exec: true)
pid
else
Expand Down Expand Up @@ -186,7 +183,7 @@ class Process
raise Errno.new(error_message)
end

def self.system_prepare_shell(command, args)
protected def self.system_prepare_shell(command, args)
command = %(#{command} "${@}") unless command.includes?(' ')
shell_args = ["-c", command, "--"]

Expand All @@ -208,7 +205,7 @@ class Process
end

# :nodoc:
protected def self.fork_internal(*, will_exec : Bool)
protected def self.fork_internal(*, will_exec : Bool) : Int64?
newmask = uninitialized LibC::SigsetT
oldmask = uninitialized LibC::SigsetT

Expand Down Expand Up @@ -241,10 +238,36 @@ class Process
LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(oldmask), nil)
end

pid
pid.try &.to_i64
end

protected def system_close
protected def close_system
end

# Changes the root directory and the current working directory for the current
# process.
#
# Security: `chroot` on its own is not an effective means of mitigation. At minimum
# the process needs to also drop privileges as soon as feasible after the `chroot`.
# Changes to the directory hierarchy or file descriptors passed via `recvmsg(2)` from
# outside the `chroot` jail may allow a restricted process to escape, even if it is
# unprivileged.
#
# ```
# Process.chroot("/var/empty")
# ```
def self.chroot(path : String) : Nil
path.check_no_null_byte
if LibC.chroot(path) != 0
raise Errno.new("Failed to chroot")
end

if LibC.chdir("/") != 0
errno = Errno.new("chdir after chroot failed")
errno.callstack = CallStack.new
errno.inspect_with_backtrace(STDERR)
abort("Unresolvable state, exiting...")
end
end
end

Expand Down
23 changes: 13 additions & 10 deletions src/crystal/system/win32/env.cr
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,22 @@ module Crystal::System::Env

# Iterates all environment variables.
def self.each(&block : String, String ->)
orig_pointer = pointer = LibC.GetEnvironmentStringsW
pointer = LibC.GetEnvironmentStringsW
raise WinError.new("GetEnvironmentStringsW") if pointer.null?

begin
while !pointer.value.zero?
string, pointer = String.from_utf16(pointer)
key_value = string.split('=', 2)
key = key_value[0]
value = key_value[1]? || ""
yield key, value
end
self.parse_env_block(pointer) { |key, val| yield key, val }
ensure
LibC.FreeEnvironmentStringsW(orig_pointer)
LibC.FreeEnvironmentStringsW(pointer)
end
end

def self.parse_env_block(pointer : Pointer(UInt16), &block : String, String ->)
while !pointer.value.zero?
string, pointer = String.from_utf16(pointer)
key_value = string.split('=', 2)
key = key_value[0]
value = key_value[1]? || ""
yield key, value
end
end
end
46 changes: 22 additions & 24 deletions src/crystal/system/win32/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,21 @@ module Crystal::System::FileDescriptor
false
end

private def windows_handle
ret = LibC._get_osfhandle(@fd)
def self.windows_handle_for?(fd)
ret = LibC._get_osfhandle(fd)
raise Errno.new("_get_osfhandle") if ret == -1
return nil if ret == -2
LibC::HANDLE.new(ret)
end

def windows_handle?
Crystal::System::FileDescriptor.windows_handle_for?(@fd)
end

def windows_handle
windows_handle? || raise "FD isnt't associated with a stream"
end

private def system_info
handle = windows_handle

Expand Down Expand Up @@ -92,23 +101,9 @@ module Crystal::System::FileDescriptor
end

private def system_reopen(other : IO::FileDescriptor)
{% if LibC.methods.includes? "dup3".id %}
# dup doesn't copy the CLOEXEC flag, so copy it manually using dup3
flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0
if LibC.dup3(other.fd, self.fd, flags) == -1
raise Errno.new("Could not reopen file descriptor")
end
{% else %}
# dup doesn't copy the CLOEXEC flag, copy it manually to the new
if LibC.dup2(other.fd, self.fd) == -1
raise Errno.new("Could not reopen file descriptor")
end

if other.close_on_exec?
self.close_on_exec = true
end
{% end %}

if LibC._dup2(other.fd, self.fd) == -1
raise Errno.new("Could not reopen file descriptor")
end
# Mark the handle open, since we had to have dup'd a live handle.
@closed = false
end
Expand All @@ -125,9 +120,13 @@ module Crystal::System::FileDescriptor
end
end

def self.pipe(read_blocking, write_blocking)
def self.pipe(read_blocking, write_blocking, inheritable = true)
pipe_fds = uninitialized StaticArray(LibC::Int, 2)
if LibC._pipe(pipe_fds, 8192, LibC::O_BINARY) != 0
flags = LibC::O_BINARY
if !inheritable
flags |= LibC::O_NOINHERIT
end
if LibC._pipe(pipe_fds, 8192, flags) != 0
raise Errno.new("Could not create pipe")
end

Expand All @@ -139,9 +138,8 @@ module Crystal::System::FileDescriptor
end

def self.pread(fd, buffer, offset)
handle = LibC._get_osfhandle(fd)
raise Errno.new("_get_osfhandle") if handle == -1
handle = LibC::HANDLE.new(handle)
handle = windows_handle_for?(fd)
raise Errno.new("_get_osfhandle") if !handle

overlapped = LibC::OVERLAPPED.new
overlapped.union.offset.offset = LibC::DWORD.new(offset)
Expand Down
Loading

0 comments on commit c0e8eb9

Please sign in to comment.