Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F/process spawn #984

Closed
wants to merge 5 commits into from
Closed
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
38 changes: 30 additions & 8 deletions spec/std/process_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe Process do
end

it "returns status 127 if command could not be executed" do
Process.run("foobarbaz", output: true).exit.should eq(127)
Process.run("foobarbaz", output: nil).exit.should eq(127)
end

it "includes PID in process status " do
Expand All @@ -32,24 +32,46 @@ describe Process do
end

it "gets output as string" do
Process.run("/bin/sh", {"-c", "echo hello"}, output: true).output.should eq("hello\n")
Process.run("/bin/sh", {"-c", "echo hello"}, output: nil).output.to_s.should eq("hello\n")
end

it "send input from string" do
Process.run("/bin/cat", input: "hello", output: true).output.should eq("hello")
Process.run("/bin/cat", input: StringIO.new("hello"), output: nil).output.to_s.should eq("hello")
end

it "send input from IO" do
File.open(__FILE__, "r") do |file|
Process.run("/bin/cat", input: file, output: true).output.should eq(File.read(__FILE__))
Process.run("/bin/cat", input: file, output: nil).output.to_s.should eq(File.read(__FILE__))
end
end

it "send output to IO" do
io = StringIO.new
Process.run("/bin/cat", input: "hello", output: io).output.should be_nil
Process.run("/bin/cat", input: StringIO.new("hello"), output: io).output.to_s.should eq("hello")
io.to_s.should eq("hello")
end

it "gets status code from successful process" do
system("true").should eq(true)
end

it "gets status code from failed process" do
system("false").should eq(false)
end

it "gets output as string" do
`echo hello`.should eq("hello\n")
end
end

describe "popen" do
it "test alive?" do
status = Process.popen("sleep", ["60"])
status.alive?.should be_true
status.kill
status.close
status.alive?.should be_false
end
end

describe "kill" do
Expand All @@ -59,14 +81,14 @@ describe Process do
end

it "kills many process" do
pid1 = fork { loop {} }
pid2 = fork { loop {} }
pid1 = fork { loop { sleep 60 } }
pid2 = fork { loop { sleep 60 } }
Process.kill(Signal::KILL, pid1, pid2).should eq(0)
end
end

it "gets the pgid of a process id" do
pid = fork { loop {} }
pid = fork { loop { sleep 60 } }
Process.getpgid(pid).should be_a(Int32)
Process.kill(Signal::KILL, pid)
end
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ module Crystal

timing("Codegen (clang)") do
quoted_object_names = object_names.map { |name| %("#{name}") }.join " "
system "#{CC} -o #{output_filename} #{quoted_object_names} #{@link_flags} #{lib_flags}"
cmd = "#{CC} -o #{output_filename} #{quoted_object_names} #{@link_flags} #{lib_flags}"
status = Process.run "/bin/sh", input: StringIO.new(cmd)
raise "exit=#{status.exit} #{cmd.inspect} failed" unless status.success?
end
end

Expand Down
12 changes: 6 additions & 6 deletions src/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ class File < FileDescriptorIO
# :nodoc:
DEFAULT_CREATE_MODE = LibC::S_IRUSR | LibC::S_IWUSR | LibC::S_IRGRP | LibC::S_IROTH

def initialize(filename, mode = "r")
def initialize(filename, mode = "r", perm = DEFAULT_CREATE_MODE)
oflag = open_flag(mode)

fd = LibC.open(filename, oflag, DEFAULT_CREATE_MODE)
fd = LibC.open(filename, oflag, perm)
if fd < 0
raise Errno.new("Error opening file '#{filename}' with mode '#{mode}'")
end
Expand Down Expand Up @@ -213,12 +213,12 @@ class File < FileDescriptorIO
(stat.st_mode & LibC::S_IFMT) == LibC::S_IFLNK
end

def self.open(filename, mode = "r")
new filename, mode
def self.open(filename, mode = "r", perm = DEFAULT_CREATE_MODE)
new filename, mode, perm
end

def self.open(filename, mode = "r")
file = File.new filename, mode
def self.open(filename, mode = "r", perm = DEFAULT_CREATE_MODE)
file = File.new filename, mode, perm
begin
yield file
ensure
Expand Down
1 change: 1 addition & 0 deletions src/io.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
lib LibC
enum FCNTL
FD_CLOEXEC = 1
F_GETFL = 3
F_SETFL = 4
end
Expand Down
14 changes: 12 additions & 2 deletions src/io/file_descriptor_io.cr
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class FileDescriptorIO
@out_count = 0

unless blocking
before = LibC.fcntl(@fd, LibC::FCNTL::F_GETFL)
LibC.fcntl(@fd, LibC::FCNTL::F_SETFL, before | LibC::O_NONBLOCK)
before = fcntl(LibC::FCNTL::F_GETFL)
fcntl(LibC::FCNTL::F_SETFL, before | LibC::O_NONBLOCK)
if @edge_triggerable
@event = Scheduler.create_fd_events(self)
end
Expand Down Expand Up @@ -89,6 +89,16 @@ class FileDescriptorIO
self
end

def close_on_exec=(arg : Bool)
fcntl(LibC::FCNTL::FD_CLOEXEC, arg ? 1 : 0)
end

def fcntl cmd, arg = 0
r = LibC.fcntl @fd, cmd, arg
raise Errno.new("fcntl() failed") if r == -1
r
end

private def unbuffered_read(slice : Slice(UInt8), count)
loop do
bytes_read = LibC.read(@fd, slice.pointer(count), LibC::SizeT.cast(count))
Expand Down
57 changes: 57 additions & 0 deletions src/process/popen.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Executes a command and returns a Process::Status object which contains pipes to stdin/stdout/stderr depending on the arguments passed.
#
# See Process.spawn for arguments
# Passing nil to input|output|error creates pipes that may be used to communicate with the child. The pipes are returned in the status object.
def Process.popen(command, args = nil, input = nil : Nil | FileDescriptorIO | Bool, output = nil : Nil | FileDescriptorIO | Bool, error = nil : Nil | FileDescriptorIO | Bool, chdir = nil : String?)
case input
when FileDescriptorIO, Bool
# passed to spawn
when nil
fork_input, process_input = IO.pipe(read_blocking: true)
fork_input.close_on_exec = true
process_input.close_on_exec = true
else
raise "unknown type #{input.inspect}"
end

case output
when FileDescriptorIO, Bool
# passed to spawn
when nil
process_output, fork_output = IO.pipe(write_blocking: true)
process_output.close_on_exec = true
fork_output.close_on_exec = true
else
raise "unknown type #{error.inspect}"
end

case error
when FileDescriptorIO, Bool
# passed to spawn
when nil
process_error, fork_error = IO.pipe(write_blocking: true)
process_error.close_on_exec = true
fork_error.close_on_exec = true
else
raise "unknown type #{error.inspect}"
end

status = spawn(command, args, input: (fork_input || input), output: (fork_output || output), error: (fork_error || error), chdir: chdir) do
process_input.close if process_input
process_output.close if process_output
process_error.close if process_error
end

fork_input.close if fork_input
fork_output.close if fork_output
fork_error.close if fork_error

status.input = process_input || input
status.output = process_output || output
status.error = process_error || error
status.manage_input = !!process_input
status.manage_output = !!process_output
status.manage_error = !!process_error

status
end
2 changes: 2 additions & 0 deletions src/process/process.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ lib LibC
fun getpid : Int32
fun getppid : Int32
fun exit(status : Int32) : NoReturn
fun _exit(status : Int32) : NoReturn

ifdef x86_64
ClockT = UInt64
Expand Down Expand Up @@ -69,6 +70,7 @@ module Process

def self.fork
pid = LibC.fork
raise Errno.new("fork") if pid == -1
pid = nil if pid == 0
pid
end
Expand Down
Loading