diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..060cc22
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,62 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+version: "{build}"
+
+image:
+- Visual Studio 2015
+
+environment:
+ global:
+ VCVARSALL: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat
+ CC: gcc
+ matrix:
+ - MRUBY_VERSION: 1.3.0
+ COMPILER: C:\MinGW # MinGW 32-bit 5.3.0
+ - MRUBY_VERSION: head
+ COMPILER: C:\MinGW # MinGW 32-bit 5.3.0
+ CFLAGS: -m32
+ - MRUBY_VERSION: head
+ COMPILER: C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0 # MinGW-w64 5.3.0
+ - MRUBY_VERSION: head
+ COMPILER: C:\mingw-w64\i686-6.3.0-posix-dwarf-rt_v5-rev1 # MinGW-w64 6.3.0
+ - MRUBY_VERSION: 1.3.0
+ COMPILER: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC # Visual Studio 2015
+ CC: visualcpp
+
+matrix:
+ allow_failures:
+ - CC: visualcpp
+ exclude:
+ - CC: visualcpp
+ - CFLAGS: -m32
+
+init:
+- CALL "%VCVARSALL%" amd64
+- SET PATH=%COMPILER%\bin;%PATH%
+- SET PATH=C:\Ruby23-x64\bin;%PATH%
+- SET PATH=C:\cygwin\bin;%PATH%
+- gcc --version
+
+build_script:
+- rake compile
+
+test_script:
+- rake test
diff --git a/.gitignore b/.gitignore
index ceeb05b..fb0a96a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,61 @@
-/tmp
+*.gem
+*.rbc
+/.config
+/coverage/
+/InstalledFiles
+/mruby/
+/pkg/
+/spec/reports/
+/spec/examples.txt
+/test/tmp/
+/test/version_tmp/
+/tmp/
+/src/origorig
+/.idea/
+/cmake-build-debug/
+
+
+# Used by dotenv library to load environment variables.
+# .env
+
+## Specific to RubyMotion:
+.dat*
+.repl_history
+build/
+*.bridgesupport
+build-iPhoneOS/
+build-iPhoneSimulator/
+
+## Specific to RubyMotion (use of CocoaPods):
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+# vendor/Pods/
+
+## Documentation cache and generated files:
+/.yardoc/
+/_yardoc/
+/doc/
+/rdoc/
+
+## Environment normalization:
+/.bundle/
+/vendor/bundle
+/lib/bundler/man/
+
+# for a library or gem, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# Gemfile.lock
+# .ruby-version
+# .ruby-gemset
+
+# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
+.rvmrc
+
+dummy
+temp
+TODO
+*.gch
+todo
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..d67e25b
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,25 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+Style/SpecialGlobalVars:
+ Enabled: false
+
+Style/NumericPredicate:
+ EnforcedStyle: comparison
diff --git a/.travis.yml b/.travis.yml
index ffe2272..bbb9020 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,2 +1,33 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+language: c
+
+compiler:
+- gcc
+- clang
+
+env:
+- MRUBY_VERSION=1.3.0
+- MRUBY_VERSION=head
+
script:
- - "ruby run_test.rb all test"
+- rake compile
+- rake test
diff --git a/README.md b/README.md
index 1b9f00f..19deb49 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,177 @@
-mruby-process
-=========
-[](https://travis-ci.org/iij/mruby-process)
+# mruby-process
[](https://travis-ci.org/appPlant/mruby-process) [](https://ci.appveyor.com/project/katzer/mruby-process/branch/windows) [](https://codebeat.co/projects/github-com-appplant-mruby-process-windows)
+Implementation of the Ruby 2.4.1 Core Library _Process_ for [mruby][mruby].
+
+All listed methods have been tested with Ubuntu, MacOS and Windows. Cross compilation works as well.
+
+```ruby
+system { VAR: 'var' }, 'echo $VAR', out: pipe
+```
+
+Include [mruby/ext/process.h][process_h] to use the native methods within your own project:
+
+```c
+// To use kill, waitpid, fork, spawn, ... on Unix and Win32
+
+#ifdef HAVE_MRB_PROCESS_H
+# include "mruby/ext/process.h"
+#endif
+
+static pid_t
+spawn_process(const char *path, char *const argv[], char *const envp[])
+{
+ return spawnve(path, argv, envp);
+}
+```
+
+## Installation
+
+Add the line below to your `build_config.rb`:
-## install by mrbgems
- - add conf.gem line to `build_config.rb`
```ruby
MRuby::Build.new do |conf|
+ # ... (snip) ...
+ conf.gem 'mruby-process'
+end
+```
- # ... (snip) ...
+Or add this line to your aplication's `mrbgem.rake`:
- conf.gem :git => 'https://github.com/iij/mruby-process.git'
+```ruby
+MRuby::Gem::Specification.new('your-mrbgem') do |spec|
+ # ... (snip) ...
+ spec.add_dependency 'mruby-process'
end
```
-## Features
- - Process - fork kill pid ppid waitpid waitpid2
- - Process::Status - all methods but `&`, ``>>``
- - You can use ``Process::Status.new(pid, status)`` to set ``$?`` in
- your script or other mrbgems.
- - Kernel - $$ exit exit! fork sleep system
+## Implemented methods
+
+### Process
+
+- https://ruby-doc.org/core-2.4.1/Process.html
+
+| method | mruby-process | Comment |
+| ------------------------- | :-----------: | :------ |
+| $0 | o |
+| $PROGRAM_NAME | o |
+| $$ | o |
+| $PID | o |
+| $PROCESS_ID | o |
+| ::WNOHANG | o |
+| ::WUNTRACED | o |
+| #abort | o |
+| #argv0 | o |
+| #clock_getres | |
+| #clock_gettime | |
+| #daemon | |
+| #detach | |
+| #egid | | Implemented in [mruby-process-ext][mruby-process-ext] |
+| #egid= | | Implemented in [mruby-process-sys][mruby-process-sys] |
+| #euid | | Implemented in [mruby-process-ext][mruby-process-ext] |
+| #euid= | | Implemented in [mruby-process-sys][mruby-process-sys] |
+| #exec | o |
+| #exit | o |
+| #exit! | o |
+| #fork | o | If fork is not usable, Process.respond_to?(:fork) returns false. |
+| #getpgid | |
+| #getpgrp | |
+| #getpriority | |
+| #getrlimit | |
+| #getsid | |
+| #gid | | Implemented in [mruby-process-ext][mruby-process-ext] |
+| #gid= | | Implemented in [mruby-process-sys][mruby-process-sys] |
+| #groups | | Implemented in [mruby-process-sys][mruby-process-sys] |
+| #groups= | | Implemented in [mruby-process-sys][mruby-process-sys] |
+| #initgroups | |
+| #kill | o |
+| #maxgroups | |
+| #maxgroups= | |
+| #pid | o |
+| #ppid | o |
+| #setpgid | |
+| #setpgrp | |
+| #setpriority | |
+| #setproctitle | |
+| #setrlimit | |
+| #setsid | |
+| #spawn | o |
+| #times | |
+| #uid | | Implemented in [mruby-process-ext][mruby-process-ext] |
+| #uid= | | Implemented in [mruby-process-ext][mruby-process-ext] |
+| #wait | o |
+| #wait2 | o |
+| #waitall | o |
+| #waitpid | o |
+| #waitpid2 | o |
+
+
+### Process::Status
+
+- https://ruby-doc.org/core-2.4.1/Process/Status.html
+
+| method | mruby-process |
+| ------------------------- | :-----------: |
+| $? | o |
+| $CHILD_STATUS | o |
+| #& | |
+| #== | o |
+| #>> | o |
+| #coredump? | o |
+| #exited? | o |
+| #exitstatus | o |
+| #inspect | o |
+| #pid | o |
+| #signaled? | o |
+| #stopped? | o |
+| #stopsig | o |
+| #success? | o |
+| #termsig | o |
+| #to_i | o |
+| #to_s | o |
+
+
+### Kernel
+
+- https://ruby-doc.org/core-2.4.1/Kernel.html
+
+| method | mruby-process | Comment |
+| ------------------------- | :-----------: | :----- |
+| #` | | Implemented in [mruby-io][mruby-io]. |
+| #abort | o |
+| #exec | o |
+| #exit | o |
+| #exit! | o |
+| #fork | o | If fork is not usable, Process.respond_to?(:fork) returns false. |
+| #sleep | | Implemented in [mruby-sleep][mruby-sleep]. |
+| #spawn | o |
+| #system | o |
+
+
+### Signal
+
+- https://ruby-doc.org/core-2.4.1/Signal.html
+
+| method | mruby-process | Comment |
+| ------------------------- | :-----------: | :----- |
+| ::signame | o |
+| ::list | o |
+| ::trap | | Implemented in [mruby-signal][mruby-signal]. |
+
+
+## Development
+
+Clone the repo:
+
+ $ git clone https://github.com/appplant/mruby-process.git && cd mruby-process/
+
+Compile the source:
+
+ $ rake compile
+
+Run the tests:
+
+ $ rake test
## Caveats
@@ -32,6 +183,7 @@ end
## License
Copyright (c) 2012 Internet Initiative Japan Inc.
+Copyright (c) 2017 appPlant GmbH.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -51,3 +203,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
+
+[mruby]: https://github.com/mruby/mruby
+[process_h]: https://github.com/appPlant/mruby-process/blob/windows/include/mruby/ext/process.h
+[mruby-process-ext]: https://github.com/ksss/mruby-process-ext
+[mruby-process-sys]: https://github.com/haconiwa/mruby-process-sys
+[mruby-sleep]: https://github.com/matsumotory/mruby-sleep
+[mruby-io]: https://github.com/iij/mruby-io
+[mruby-signal]: https://github.com/ksss/mruby-signal
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..6023d7e
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,50 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+ENV['MRUBY_CONFIG'] ||= File.expand_path('build_config.rb')
+ENV['MRUBY_VERSION'] ||= 'head'
+
+file :mruby do
+ if ENV['MRUBY_VERSION'] == 'head'
+ sh 'git clone --depth 1 git://github.com/mruby/mruby.git'
+ else
+ sh "curl -L --fail --retry 3 --retry-delay 1 https://github.com/mruby/mruby/archive/#{ENV['MRUBY_VERSION']}.tar.gz -s -o - | tar zxf -" # rubocop:disable LineLength
+ mv "mruby-#{ENV['MRUBY_VERSION']}", 'mruby'
+ end
+end
+
+FileUtils.mkdir_p('tmp')
+Rake::Task[:mruby].invoke
+
+namespace :mruby do
+ Dir.chdir('mruby') { load 'Rakefile' }
+end
+
+desc 'compile binary'
+task compile: 'mruby:all'
+
+desc 'test'
+task test: 'mruby:test'
+
+desc 'cleanup'
+task clean: 'mruby:clean'
+
+desc 'cleanup all'
+task cleanall: 'mruby:deep_clean'
diff --git a/build_config.rb b/build_config.rb
new file mode 100755
index 0000000..d5b8ad8
--- /dev/null
+++ b/build_config.rb
@@ -0,0 +1,28 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+MRuby::Build.new do |conf|
+ toolchain ENV.fetch('CC', :gcc)
+
+ conf.enable_debug
+ conf.enable_test
+
+ conf.gem File.expand_path(File.dirname(__FILE__))
+end
diff --git a/include/mruby/ext/process.h b/include/mruby/ext/process.h
new file mode 100644
index 0000000..61914fd
--- /dev/null
+++ b/include/mruby/ext/process.h
@@ -0,0 +1,97 @@
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef MRB_PROCESS_H
+#define MRB_PROCESS_H 1
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#include "mruby.h"
+#include
+#include
+
+#ifndef WNOHANG
+# define WNOHANG -1
+#endif
+
+#ifndef WUNTRACED
+# define WUNTRACED 0
+#endif
+
+#ifndef SIGINT
+# define SIGINT 2
+#endif
+
+#ifndef SIGKILL
+# define SIGKILL 9
+#endif
+
+#ifndef WIFEXITED
+# define WIFEXITED(w) (((w) & 0xff) == 0)
+#endif
+
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f))
+#endif
+
+#ifndef WIFSTOPPED
+# define WIFSTOPPED(w) (((w) & 0xff) == 0x7f)
+#endif
+
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(w) (((w) >> 8) & 0xff)
+#endif
+
+#ifndef WTERMSIG
+# define WTERMSIG(w) ((w) & 0x7f)
+#endif
+
+#ifndef WSTOPSIG
+# define WSTOPSIG WEXITSTATUS
+#endif
+
+mrb_value mrb_progname(mrb_state *mrb);
+mrb_value mrb_argv0(mrb_state *mrb);
+
+mrb_value mrb_last_status_get(mrb_state *mrb);
+void mrb_last_status_set(mrb_state *mrb, pid_t pid, mrb_int status);
+void mrb_last_status_clear(mrb_state *mrb);
+
+void _exit(int status);
+void exit(int status);
+
+int getpid(void);
+pid_t getppid(void);
+pid_t waitpid(pid_t pid, int *stat_loc, int options);
+
+int fork(void);
+pid_t spawnv(const char *path, char *const argv[], mrb_value in, mrb_value out, mrb_value err);
+pid_t spawnve(const char *path, char *const argv[], char *const envp[], mrb_value in, mrb_value out, mrb_value err);
+int execv(const char *path, char *const argv[]);
+int execve(const char *path, char *const argv[], char *const envp[]);
+int kill(pid_t pid, int sig);
+
+#if defined(__cplusplus)
+} /* extern "C" { */
+#endif
+#endif /* MRB_PROCESS_H */
diff --git a/mrbgem.rake b/mrbgem.rake
index f167f86..461c8a6 100644
--- a/mrbgem.rake
+++ b/mrbgem.rake
@@ -1,4 +1,49 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+def target_win32?
+ return true if RUBY_PLATFORM =~ /mingw|mswin/
+ build.is_a?(MRuby::CrossBuild) && build.host_target.to_s =~ /mingw/
+end
+
MRuby::Gem::Specification.new('mruby-process') do |spec|
spec.license = 'MIT'
- spec.authors = 'Internet Initiative Japan Inc.'
+ spec.authors = 'mruby developers'
+
+ spec.add_test_dependency 'mruby-print', core: 'mruby-print'
+ spec.add_test_dependency 'mruby-env', mgem: 'mruby-env'
+ spec.add_test_dependency 'mruby-os', mgem: 'mruby-os'
+
+ spec.mruby.cc.defines << 'HAVE_MRB_PROCESS_H'
+
+ [spec.cc, spec.mruby.cc].each do |cc|
+ cc.include_paths << "#{spec.dir}/include/mruby/ext"
+ end
+
+ ENV['RAND'] = Time.now.to_i.to_s if build.test_enabled?
+
+ if target_win32?
+ spec.objs.delete objfile("#{build_dir}/src/posix")
+ spec.add_test_dependency 'mruby-tiny-io', mgem: 'mruby-tiny-io'
+ else
+ spec.objs.delete objfile("#{build_dir}/src/win32")
+ spec.add_test_dependency 'mruby-io', mgem: 'mruby-io'
+ end
end
diff --git a/mrblib/process.rb b/mrblib/process.rb
deleted file mode 100644
index 4e79431..0000000
--- a/mrblib/process.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Process
- def self.waitpid2(pid, flags=0)
- i = waitpid(pid, flags)
- if i
- [i, $?.dup]
- else
- nil
- end
- end
-end
diff --git a/mrblib/status.rb b/mrblib/status.rb
index 56f8380..39100a7 100644
--- a/mrblib/status.rb
+++ b/mrblib/status.rb
@@ -1,44 +1,79 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
module Process
+ # Process::Status encapsulates the information on the status of a running or
+ # terminated system process. The built-in variable $? is either nil or a
+ # Process::Status object.
class Status
+ # Encapsulates the information on the status of a system process.
def initialize(pid, status)
- @pid = pid
+ @pid = pid
@status = status
end
+ # Returns true if the integer value of stat equals other.
+ #
+ # @return [ String ]
def ==(other)
- self.to_i == other.to_i
+ to_i == other.to_i
end
+ # Shift the bits in stat right num places.
+ #
+ # @return [ Integer ]
+ def >>(other)
+ to_i >> other
+ end
+
+ # Override the inspection method.
def inspect
- if exited?
- s = "exited(#{exitstatus})"
- elsif stopped?
- s = "stopped(#{stopsig})"
- elsif signaled?
- if coredump?
- s = "coredumped"
- else
- s = "signaled(#{termsig})"
- end
- else
- s = "status=#{@status}"
- end
- "#"
+ "#"
end
+ # Returns the process ID that this status object represents.
attr_reader :pid
+ # Returns true if status is successful, false if not.
+ # Returns nil if exited? is not true.
+ #
+ # @return [ Boolean ]
def success?
- self.exitstatus == 0
+ return nil unless exited?
+ exitstatus == 0
end
+ # The bits in status as a Integer.
+ #
+ # @return [ Integer ]
def to_i
@status
end
+
alias to_int to_i
+ # Show pid and exit status.
+ #
+ # @return [ String ]
def to_s
- self.to_i.to_s
+ "pid #{@pid} exit #{@status}"
end
end
end
diff --git a/run_test.rb b/run_test.rb
deleted file mode 100644
index 30fbad3..0000000
--- a/run_test.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env ruby
-#
-# mrbgems test runner
-#
-
-gemname = File.basename(File.dirname(File.expand_path __FILE__))
-
-if __FILE__ == $0
- repository, dir = 'https://github.com/mruby/mruby.git', 'tmp/mruby'
- build_args = ARGV
- build_args = ['all', 'test'] if build_args.nil? or build_args.empty?
-
- Dir.mkdir 'tmp' unless File.exist?('tmp')
- unless File.exist?(dir)
- system "git clone #{repository} #{dir}"
- end
-
- exit system(%Q[cd #{dir}; MRUBY_CONFIG=#{File.expand_path __FILE__} ruby minirake #{build_args.join(' ')}])
-end
-
-MRuby::Build.new do |conf|
- toolchain :gcc
- conf.gembox 'default'
- conf.enable_test
-
- conf.gem File.expand_path(File.dirname(__FILE__))
-end
diff --git a/src/dln.c b/src/dln.c
new file mode 100644
index 0000000..1f33e1a
--- /dev/null
+++ b/src/dln.c
@@ -0,0 +1,252 @@
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef PATH_ENV
+# define PATH_ENV "Path"
+#endif
+#ifndef PATH_SEP
+# define PATH_SEP ";"
+#endif
+
+static char*
+dln_find_1(const char *fname, const char *path, char *buf, size_t size, int exe_flag);
+
+char*
+dln_find_exe_r(const char *fname, const char *path, char *buf, size_t size)
+{
+ if (!path) {
+ path = getenv(PATH_ENV);
+ if (path) path = strdup(path);
+ }
+
+#if defined(__APPLE__) || defined(__linux__)
+ if (!path) {
+ path =
+ "/usr/local/bin" PATH_SEP
+ "/usr/ucb" PATH_SEP
+ "/usr/bin" PATH_SEP
+ "/bin" PATH_SEP
+ ".";
+ }
+#endif
+
+ buf = dln_find_1(fname, path, buf, size, 1);
+
+ return buf;
+}
+
+static char *
+dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, int exe_flag)
+{
+ register const char *dp;
+ register const char *ep;
+ register char *bp;
+ struct stat st;
+ size_t i, fnlen, fspace;
+
+#if !defined(__APPLE__) && !defined(__linux__)
+ static const char extension[][5] = {
+ ".exe", ".com", ".cmd", ".bat"
+ };
+ size_t j;
+ int is_abs = 0, has_path = 0;
+ const char *ext = 0;
+#endif
+
+ const char *p = fname;
+
+ if(!path)
+ return NULL;
+
+#define RETURN_IF(expr) if (expr) return (char *)fname;
+
+ RETURN_IF(!fname);
+
+ fnlen = strlen(fname);
+
+ if (fnlen >= size)
+ return NULL;
+
+#if !defined(__APPLE__) && !defined(__linux__)
+# ifndef CharNext
+# define CharNext(p) ((p)+1)
+# endif
+# ifdef DOSISH_DRIVE_LETTER
+ if (((p[0] | 0x20) - 'a') < 26 && p[1] == ':') {
+ p += 2;
+ is_abs = 1;
+ }
+# endif
+ switch (*p) {
+ case '/': case '\\':
+ is_abs = 1;
+ p++;
+ }
+ has_path = is_abs;
+ while (*p) {
+ switch (*p) {
+ case '/': case '\\':
+ has_path = 1;
+ ext = 0;
+ p++;
+ break;
+ case '.':
+ ext = p;
+ p++;
+ break;
+ default:
+ p = CharNext(p);
+ }
+ }
+ if (ext) {
+ for (j = 0; strcasecmp(ext, extension[j]); ) {
+ if (++j == sizeof(extension) / sizeof(extension[0])) {
+ ext = 0;
+ break;
+ }
+ }
+ }
+ ep = bp = 0;
+
+ if (!exe_flag) {
+ RETURN_IF(is_abs);
+ }
+ else if (has_path) {
+ RETURN_IF(ext);
+ i = p - fname;
+ if (i + 1 > size) goto toolong;
+ fspace = size - i - 1;
+ bp = fbuf;
+ ep = p;
+ memcpy(fbuf, fname, i + 1);
+ goto needs_extension;
+ }
+ p = fname;
+#endif
+
+ if (*p == '.' && *++p == '.') ++p;
+ RETURN_IF(*p == '/');
+ RETURN_IF(exe_flag && strchr(fname, '/'));
+
+#undef RETURN_IF
+
+ for (dp = path;; dp = ++ep) {
+ register size_t l;
+
+ /* extract a component */
+ ep = strchr(dp, PATH_SEP[0]);
+ if (ep == NULL){
+ ep = dp+strlen(dp);
+ }
+ /* find the length of that component */
+ l = ep - dp;
+ bp = fbuf;
+ fspace = size - 2;
+ if (l > 0) {
+ /*
+ ** If the length of the component is zero length,
+ ** start from the current directory. If the
+ ** component begins with "~", start from the
+ ** user's $HOME environment variable. Otherwise
+ ** take the path literally.
+ */
+ if (*dp == '~' && (l == 1 ||
+#if !defined(__APPLE__) && !defined(__linux__)
+ dp[1] == '\\' ||
+#endif
+ dp[1] == '/')) {
+
+ char *home;
+
+ home = getenv("HOME");
+ if (home != NULL) {
+ i = strlen(home);
+ if (fspace < i)
+ goto toolong;
+ fspace -= i;
+ memcpy(bp, home, i);
+ bp += i;
+ }
+ dp++;
+ l--;
+ }
+ if (l > 0) {
+ if (fspace < l)
+ goto toolong;
+ fspace -= l;
+ memcpy(bp, dp, l);
+ bp += l;
+ }
+
+ /* add a "/" between directory and filename */
+ if (ep[-1] != '/')
+ *bp++ = '/';
+ }
+
+ /* now append the file name */
+ i = fnlen;
+ if (fspace < i) {
+ toolong:
+ goto next;
+ }
+ fspace -= i;
+ memcpy(bp, fname, i + 1);
+
+#if !defined(__APPLE__) && !defined(__linux__)
+ if (exe_flag && !ext) {
+ needs_extension:
+ for (j = 0; j < sizeof(extension) / sizeof(extension[0]); j++) {
+ if (fspace < strlen(extension[j])) {
+ continue;
+ }
+ strcpy(bp + i, extension[j]);
+ if (access(fbuf, X_OK) == 0){
+ return fbuf;
+ }
+ }
+ goto next;
+ }
+#endif
+
+ if (stat(fbuf, &st) == 0 && S_ISREG(st.st_mode)) {
+ if (exe_flag == 0){
+ return fbuf;
+ }
+ /* looking for executable */
+ if (access(fbuf, X_OK) == 0){
+ return fbuf;
+ }
+ }
+ next:
+ /* if not, and no other alternatives, life is bleak */
+ if (*ep == '\0') {
+ return NULL;
+ }
+ /* otherwise try the next component in the search path */
+ }
+}
diff --git a/src/gen.rb b/src/gen.rb
deleted file mode 100755
index 0844340..0000000
--- a/src/gen.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env ruby
-
-Dir.chdir(File.dirname($0))
-
-f = File.open("signals.cstub", "w")
-
-IO.readlines("signals.def").each { |name|
- name.sub(/^#.*/, "")
- name.strip!
- next if name.empty?
-
- raise "invalid signal name: #{name}" unless name =~ /^SIG(.+)/
- sym = $1
-
- f.write <
+#include
+
+typedef struct mrb_execarg {
+ struct {
+ mrb_value in;
+ mrb_value out;
+ mrb_value err;
+ } fd;
+ char **envp;
+ char *filename;
+ char **argv;
+ int argc;
+} mrb_execarg;
+
+static int mrb_execarg_argv_to_strv(mrb_state *mrb, mrb_value *argv, mrb_int len, char **result);
+static void mrb_execarg_fill(mrb_state *mrb, mrb_value env, mrb_value *argv, mrb_int argc, mrb_value opts, struct mrb_execarg *eargp);
+static int mrb_build_shell_array(mrb_state *mrb, mrb_value *argv, mrb_int len, char *shell, char *shell_mod, char **result);
+
+struct mrb_execarg*
+mrb_execarg_new(mrb_state *mrb)
+{
+ mrb_int argc;
+ mrb_value *argv, env, opts;
+ struct mrb_execarg *eargp;
+
+ mrb_get_args(mrb, "o|*", &env, &argv, &argc);
+
+ switch (mrb_type(env)) {
+ case MRB_TT_HASH:
+ break;
+
+ case MRB_TT_STRING:
+ mrb_get_args(mrb, "*", &argv, &argc);
+ env = mrb_nil_value();
+ break;
+
+ default:
+ mrb_raisef(mrb, E_TYPE_ERROR, "no implicit conversion of %S into String",
+ mrb_obj_value(mrb_class(mrb, env)));
+ }
+
+ if (argc > 1 && mrb_hash_p(argv[argc - 1])) {
+ opts = argv[argc - 1];
+ argc--;
+ } else opts = mrb_nil_value();
+
+ eargp = malloc(sizeof(struct mrb_execarg));
+ mrb_execarg_fill(mrb, env, argv, argc, opts, eargp);
+
+ return eargp;
+}
+
+static void
+mrb_execarg_fill(mrb_state *mrb, mrb_value env, mrb_value *argv, mrb_int argc, mrb_value opts, struct mrb_execarg *eargp)
+{
+ int ai, use_cmd, do_exit;
+ char **result;
+ char *shell, *shell_mod;
+ const char *tCmd, *fCmd;
+ char buf[160];
+ mrb_value argv0 = mrb_nil_value();
+
+ ai = mrb_gc_arena_save(mrb);
+
+ if (mrb_hash_p(opts)) {
+ eargp->fd.in = mrb_hash_get(mrb, opts, mrb_check_intern(mrb, "in", 2));
+ eargp->fd.out = mrb_hash_get(mrb, opts, mrb_check_intern(mrb, "out", 3));
+ eargp->fd.err = mrb_hash_get(mrb, opts, mrb_check_intern(mrb, "err", 3));
+ } else {
+ eargp->fd.in = eargp->fd.out = eargp->fd.err = mrb_nil_value();
+ }
+
+ tCmd = mrb_string_value_ptr(mrb, argv[0]);
+ fCmd = dln_find_exe_r(tCmd, NULL, buf, sizeof(buf));
+
+ do_exit = !fCmd && strncmp("exit", tCmd, 4) == 0;
+ use_cmd = (!strrchr(tCmd, ' ') && (fCmd || (!do_exit && argc > 1))) ? 1 : 0;
+
+ use_cmd = use_cmd && fCmd;
+
+ if (use_cmd) {
+ result = (char **)mrb_malloc(mrb, sizeof(char *) * (argc + 1));
+ mrb_execarg_argv_to_strv(mrb, argv, argc, result);
+ } else {
+ result = (char **)mrb_malloc(mrb, sizeof(char *) * (argc + 3));
+
+ #if defined(__APPLE__) || defined(__linux__)
+ shell = getenv("SHELL");
+ if (!shell) shell = strdup("bin/sh");
+ shell_mod = strdup("-c");
+ #else
+ shell = getenv("ComSpec");
+ if (!shell) shell = strdup("C:\\WINDOWS\\system32\\cmd.exe");
+ shell_mod = strdup("/c");
+ #endif
+ mrb_build_shell_array(mrb, argv, argc, shell, shell_mod, result);
+ argc+=2;
+ }
+ result[argc] = NULL;
+
+#if defined(__APPLE__) || defined(__linux__)
+ if (fCmd && result[0][0] != '/') {
+ argv0 = mrb_str_new_cstr(mrb, fCmd);
+ }
+#else
+ if (fCmd && result[0][1] != ':') {
+ argv0 = mrb_str_new_cstr(mrb, fCmd);
+ }
+#endif
+
+ if (mrb_bool(argv0)) {
+ result[0] = mrb_str_to_cstr(mrb, argv0);
+ }
+
+ eargp->envp = NULL;
+ eargp->filename = result[0];
+ eargp->argv = result;
+ eargp->argc = argc;
+
+ if (mrb_test(env)) {
+ mrb_int len;
+ mrb_value keys;
+ char **envp;
+ int i;
+
+ keys = mrb_hash_keys(mrb, env);
+ len = RARRAY_LEN(keys);
+ envp = (char **)mrb_malloc(mrb, sizeof(char *) * (len + 1));
+
+ for (i = 0; i < len; ++i) {
+ mrb_value key = mrb_ary_ref(mrb, keys, i);
+ mrb_value val = mrb_hash_get(mrb, env, key);
+ mrb_value skey = mrb_symbol_p(key) ? mrb_sym2str(mrb, mrb_symbol(key)) : key;
+ mrb_value sval = mrb_convert_type(mrb, val, MRB_TT_STRING, "String", "to_s");
+ mrb_int slen = RSTRING_LEN(skey) + RSTRING_LEN(sval) + 1;
+ char str[slen];
+
+ sprintf(str, "%s=%s",
+ mrb_string_value_cstr(mrb, &skey),
+ mrb_string_value_cstr(mrb, &sval));
+
+ envp[i] = strdup(str);
+ }
+
+ envp[i] = NULL;
+ eargp->envp = envp;
+ }
+
+ mrb_gc_arena_restore(mrb, ai);
+}
+
+static int
+mrb_execarg_argv_to_strv(mrb_state *mrb, mrb_value *argv, mrb_int len, char **result)
+{
+ char *buf;
+ int i, ai;
+
+ if (len < 1)
+ mrb_raise(mrb, E_ARGUMENT_ERROR, "must have at least 1 argument");
+
+ ai = mrb_gc_arena_save(mrb);
+
+ for (i = 0; i < len; i++) {
+ buf = (char *)mrb_string_value_cstr(mrb, &argv[i]);
+ *result = buf;
+ result++;
+ }
+
+ *result = NULL;
+ result -= i;
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ return 0;
+}
+
+static int
+mrb_build_shell_array(mrb_state *mrb, mrb_value *argv, mrb_int len, char *shell, char *shell_mod, char **result)
+{
+ char *buf;
+ int i, ai;
+
+ if (len < 1)
+ mrb_raise(mrb, E_ARGUMENT_ERROR, "must have at least 1 argument");
+
+ ai = mrb_gc_arena_save(mrb);
+
+ *result = shell;
+ result++;
+
+ *result = shell_mod;
+ result++;
+
+ for (i = 0; i < len; i++) {
+ buf = (char *)mrb_string_value_cstr(mrb, &argv[i]);
+ *result = buf;
+ result++;
+ }
+
+ *result = NULL;
+ result -= i;
+
+ mrb_gc_arena_restore(mrb, ai);
+
+ return 0;
+}
diff --git a/src/posix.c b/src/posix.c
new file mode 100644
index 0000000..ab513dd
--- /dev/null
+++ b/src/posix.c
@@ -0,0 +1,117 @@
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "mruby.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+mrb_value
+mrb_argv0(mrb_state *mrb)
+{
+ const char *argv0 = getenv("_");
+
+ if (!argv0)
+ return mrb_nil_value();
+
+ return mrb_str_new_cstr(mrb,argv0);
+}
+
+mrb_value
+mrb_progname(mrb_state *mrb)
+{
+ const char *argv0 = getenv("_");
+ const char *progname;
+
+ if (!argv0)
+ return mrb_nil_value();
+
+ progname = strrchr(argv0, '/');
+
+ if (progname)
+ progname++;
+ else
+ progname = argv0;
+
+ return mrb_str_new_cstr(mrb, progname);
+}
+
+pid_t
+spawnv(const char *path, char *const argv[], mrb_value in, mrb_value out, mrb_value err)
+{
+ pid_t pid;
+ posix_spawn_file_actions_t action;
+
+ posix_spawn_file_actions_init(&action);
+
+
+ if(mrb_fixnum_p(in)){
+ posix_spawn_file_actions_adddup2 (&action, mrb_fixnum(in), 0);
+ }
+
+ if(mrb_fixnum_p(out)){
+ posix_spawn_file_actions_adddup2 (&action, mrb_fixnum(out), 1);
+ }
+
+ if(mrb_fixnum_p(err)){
+ posix_spawn_file_actions_adddup2 (&action, mrb_fixnum(err), 2);
+ }
+
+ if (posix_spawn(&pid, path, &action, NULL, argv, NULL) != 0)
+ return -1;
+
+
+ posix_spawn_file_actions_destroy(&action);
+
+ return pid;
+}
+
+pid_t
+spawnve(const char *path, char *const argv[], char *const envp[], mrb_value in, mrb_value out, mrb_value err)
+{
+ pid_t pid;
+ posix_spawn_file_actions_t action;
+
+ posix_spawn_file_actions_init(&action);
+
+ if(mrb_fixnum_p(in)){
+ posix_spawn_file_actions_adddup2 (&action, mrb_fixnum(in), 0);
+ }
+
+ if(mrb_fixnum_p(out)){
+ posix_spawn_file_actions_adddup2 (&action, mrb_fixnum(out), 1);
+ }
+
+ if(mrb_fixnum_p(err)){
+ posix_spawn_file_actions_adddup2 (&action, mrb_fixnum(err), 2);
+ }
+
+ if (posix_spawn(&pid, path, &action, NULL, argv, envp) != 0)
+ return -1;
+
+ posix_spawn_file_actions_destroy(&action);
+
+ return pid;
+}
diff --git a/src/process.c b/src/process.c
index fa6c8a2..33dcc11 100644
--- a/src/process.c
+++ b/src/process.c
@@ -1,137 +1,200 @@
-/*
-** process.c -
-*/
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
#include "mruby.h"
-#include "mruby/array.h"
-#include "mruby/class.h"
-#include "mruby/string.h"
#include "mruby/variable.h"
+#include "mruby/array.h"
#include "mruby/error.h"
+#include "mruby/ext/process.h"
+
+#include "internal.c"
+
+#include "status.c"
+#include "signal.c"
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-static mrb_value mrb_f_exit_common(mrb_state *mrb, int bang);
-static mrb_value mrb_procstat_new(mrb_state *mrb, mrb_int pid, mrb_int status);
-
-static struct {
- const char *name;
- int no;
-} signals[] = {
-#include "signals.cstub"
- { NULL, 0 }
-};
-
-#if MRUBY_RELEASE_NO < 10000
-static struct RClass *
-mrb_module_get(mrb_state *mrb, const char *name)
+static void
+mrb_process_set_pid_gv(mrb_state *mrb)
{
- return mrb_class_get(mrb, name);
+ mrb_value pid = mrb_fixnum_value((mrb_int)getpid());
+
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$$"), pid);
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$PID"), pid);
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$PROCESS_ID"), pid);
+}
+
+static mrb_value
+mrb_proc_argv0(mrb_state *mrb, mrb_value klass)
+{
+ return mrb_argv0(mrb);
+}
+
+static mrb_value
+mrb_proc_progname(mrb_state *mrb)
+{
+ return mrb_funcall(mrb, mrb_progname(mrb), "freeze", 0);
+}
+
+static mrb_value
+mrb_exit_common(mrb_state *mrb, int bang)
+{
+ mrb_value status;
+ int istatus, n;
+
+ n = mrb_get_args(mrb, "|o", &status);
+ if (n == 0) {
+ status = (bang) ? mrb_false_value() : mrb_true_value();
+ }
+
+ switch (mrb_type(status)) {
+ case MRB_TT_TRUE:
+ istatus = EXIT_SUCCESS;
+ break;
+
+ case MRB_TT_FALSE:
+ istatus = EXIT_FAILURE;
+ break;
+
+ default:
+ status = mrb_to_int(mrb, status);
+ istatus = mrb_fixnum(status);
+ }
+
+ if (bang) {
+ _exit(istatus);
+ } else {
+ exit(istatus);
+ }
+
+ /* maybe not reached */
+ return mrb_nil_value();
+}
+
+static mrb_value
+mrb_f_abort(mrb_state *mrb, mrb_value klass)
+{
+ mrb_value error;
+ int n;
+
+ n = mrb_get_args(mrb, "|S", &error);
+
+ if (n != 0) {
+ fprintf(stderr, "%s\n", mrb_str_to_cstr(mrb, error));
+ }
+
+ return mrb_exit_common(mrb, 1);
+}
+
+static mrb_value
+mrb_f_exit(mrb_state *mrb, mrb_value klass)
+{
+ return mrb_exit_common(mrb, 0);
+}
+
+static mrb_value
+mrb_f_exit_bang(mrb_state *mrb, mrb_value klass)
+{
+ return mrb_exit_common(mrb, 1);
+}
+
+static mrb_value
+mrb_f_pid(mrb_state *mrb, mrb_value klass)
+{
+ return mrb_fixnum_value((mrb_int)getpid());
+}
+
+static mrb_value
+mrb_f_ppid(mrb_state *mrb, mrb_value klass)
+{
+ return mrb_fixnum_value((mrb_int)getppid());
}
-#endif
-mrb_value
+static mrb_value
mrb_f_kill(mrb_state *mrb, mrb_value klass)
{
mrb_int pid, argc;
- mrb_value *argv, sigo;
- int i, sent, signo = 0;
- size_t symlen;
- const char *name;
-#if MRUBY_RELEASE_NO < 10000
- size_t namelen;
-#else
- mrb_int namelen;
-#endif
+ mrb_value *argv, sig;
+ int signo, sent;
+ const char *signm;
+
+ mrb_get_args(mrb, "oi*", &sig, &pid, &argv, &argc);
+
+ switch (mrb_type(sig)) {
+ case MRB_TT_FIXNUM:
+ signo = mrb_fixnum(sig);
+ signm = signo2signm(signo);
+ break;
+
+ case MRB_TT_STRING:
+ signm = RSTRING_PTR(sig);
+ signo = signm2signo(signm);
+ break;
+
+ case MRB_TT_SYMBOL:
+ signm = mrb_sym2name(mrb, mrb_symbol(sig));
+ signo = signm2signo(signm);
+ break;
+
+ default:
+ mrb_raisef(mrb, E_TYPE_ERROR, "bad signal type %S",
+ mrb_obj_value(mrb_class(mrb, sig)));
+ }
- mrb_get_args(mrb, "oi*", &sigo, &pid, &argv, &argc);
- if (mrb_fixnum_p(sigo)) {
- signo = mrb_fixnum(sigo);
- } else if (mrb_string_p(sigo) || mrb_symbol_p(sigo)) {
- if (mrb_string_p(sigo)) {
- name = RSTRING_PTR(sigo);
- namelen = (size_t)RSTRING_LEN(sigo);
- } else {
- name = mrb_sym2name_len(mrb, mrb_symbol(sigo), &namelen);
- }
- if (namelen >= 3 && strncmp(name, "SIG", 3) == 0) {
- name += 3;
- namelen -= 3;
- }
- for (i = 0; signals[i].name != NULL; i++) {
- symlen = strlen(signals[i].name);
- if (symlen == namelen && strncmp(name, signals[i].name, symlen) == 0) {
- signo = signals[i].no;
- break;
- }
- }
- if (signals[i].name == NULL) {
- mrb_raisef(mrb, E_ARGUMENT_ERROR, "unsupported name `SIG%S'", mrb_str_new(mrb, name, namelen));
- }
- } else {
- mrb_raisef(mrb, E_TYPE_ERROR, "bad signal type %S",
- mrb_obj_value(mrb_class(mrb, sigo)));
+ if (!signm) {
+ mrb_raisef(mrb, E_ARGUMENT_ERROR, "unsupported signal %S",
+ mrb_fixnum_value(signo));
+ }
+
+ if (strncmp(signame_prefix, signm, sizeof(signame_prefix)) == 0)
+ signm += 3;
+
+ if (strcmp(signm, signo2signm(signo)) > 0) {
+ mrb_raisef(mrb, E_ARGUMENT_ERROR, "unsupported signal name `SIG%S'",
+ mrb_str_new_cstr(mrb, signm));
}
sent = 0;
if (kill(pid, signo) == -1)
- mrb_sys_fail(mrb, "kill");
+ mrb_sys_fail(mrb, "no such process");
sent++;
while (argc-- > 0) {
if (!mrb_fixnum_p(*argv)) {
- mrb_raisef(mrb, E_TYPE_ERROR, "wrong argument type %S (expected Fixnum)",
- mrb_obj_value(mrb_class(mrb, *argv)));
+ mrb_raisef(mrb, E_TYPE_ERROR, "no implicit conversion of %S into Integer",
+ mrb_obj_value(mrb_class(mrb, *argv)));
}
+
if (kill(mrb_fixnum(*argv), signo) == -1)
- mrb_sys_fail(mrb, "kill");
+ mrb_sys_fail(mrb, "no such process");
+
sent++;
argv++;
}
- return mrb_fixnum_value(sent);
-}
-static mrb_value
-mrb_f_fork(mrb_state *mrb, mrb_value klass)
-{
- mrb_value b;
- int pid;
-
- mrb_get_args(mrb, "&", &b);
-
- switch (pid = fork()) {
- case 0:
- mrb_gv_set(mrb, mrb_intern_lit(mrb, "$$"), mrb_fixnum_value((mrb_int)getpid()));
- if (!mrb_nil_p(b)) {
- mrb_yield_argv(mrb, b, 0, NULL);
- _exit(0);
- }
- return mrb_nil_value();
-
- case -1:
- mrb_sys_fail(mrb, "fork failed");
- return mrb_nil_value();
-
- default:
- return mrb_fixnum_value(pid);
- }
+ return mrb_fixnum_value(sent);
}
-static int
-mrb_waitpid(int pid, int flags, int *st)
+static pid_t
+mrb_waitpid(int pid, int *st, int flags)
{
- int result;
+ pid_t result;
retry:
result = waitpid(pid, st, flags);
@@ -146,251 +209,215 @@ mrb_waitpid(int pid, int flags, int *st)
}
static mrb_value
-mrb_f_waitpid(mrb_state *mrb, mrb_value klass)
+mrb_f_wait(mrb_state *mrb, mrb_value klass)
{
- mrb_int pid, flags = 0;
- int status;
+ mrb_int pid, flags;
+ int len, status;
- mrb_get_args(mrb, "i|i", &pid, &flags);
+ len = mrb_get_args(mrb, "|ii", &pid, &flags);
- if ((pid = mrb_waitpid(pid, flags, &status)) < 0)
+ if (len == 0) pid = -1;
+ if (len == 1) flags = 0;
+
+ if ((pid = mrb_waitpid(pid, &status, flags)) < 0)
mrb_sys_fail(mrb, "waitpid failed");
if (!pid && (flags & WNOHANG)) {
- mrb_gv_set(mrb, mrb_intern_lit(mrb, "$?"), mrb_nil_value());
+ mrb_last_status_clear(mrb);
return mrb_nil_value();
}
- mrb_gv_set(mrb, mrb_intern_lit(mrb, "$?"), mrb_procstat_new(mrb, pid, status));
+ mrb_last_status_set(mrb, pid, status);
return mrb_fixnum_value(pid);
}
-mrb_value
-mrb_f_sleep(mrb_state *mrb, mrb_value klass)
+static mrb_value
+mrb_f_wait2(mrb_state *mrb, mrb_value klass)
{
- mrb_int argc;
- mrb_value *argv;
- time_t beg, end;
-
- beg = time(0);
- mrb_get_args(mrb, "*", &argv, &argc);
- if (argc == 0) {
- sleep((32767<<16)+32767);
- } else if(argc == 1) {
- struct timeval tv;
- int n;
-
- if (mrb_fixnum_p(argv[0])) {
- tv.tv_sec = mrb_fixnum(argv[0]);
- tv.tv_usec = 0;
- } else {
- tv.tv_sec = mrb_float(argv[0]);
- tv.tv_usec = (mrb_float(argv[0]) - tv.tv_sec) * 1000000.0;
- }
-
+ mrb_value pid = mrb_f_wait(mrb, klass);
+ mrb_value st = mrb_last_status_get(mrb);
- n = select(0, 0, 0, 0, &tv);
- if (n < 0)
- mrb_sys_fail(mrb, "mrb_f_sleep failed");
- } else {
- mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong # of arguments");
- }
-
- end = time(0) - beg;
-
- return mrb_fixnum_value(end);
+ return mrb_assoc_new(mrb, pid, st);
}
-mrb_value
-mrb_f_system(mrb_state *mrb, mrb_value klass)
+static mrb_value
+mrb_f_waitall(mrb_state *mrb, mrb_value klass)
{
- int ret;
- mrb_value *argv, pname;
- const char *path;
- mrb_int argc;
- void (*chfunc)(int);
-
- fflush(stdout);
- fflush(stderr);
-
- mrb_get_args(mrb, "*", &argv, &argc);
- if (argc == 0) {
- mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arguments");
- }
+ mrb_value result, st;
+ pid_t pid;
+ int status;
- pname = argv[0];
- path = mrb_string_value_cstr(mrb, &pname);
+ result = mrb_ary_new(mrb);
+ mrb_last_status_clear(mrb);
- chfunc = signal(SIGCHLD, SIG_DFL);
- ret = system(path);
- signal(SIGCHLD, chfunc);
+ for (pid = -1;;) {
+ pid = mrb_waitpid(-1, &status, 0);
- if (WIFEXITED(ret) && WEXITSTATUS(ret) == 0) {
- return mrb_true_value();
- }
+ if (pid == -1) {
+ int e = errno;
- return mrb_false_value();
-}
+ if (e == ECHILD)
+ break;
-mrb_value
-mrb_f_exit(mrb_state *mrb, mrb_value klass)
-{
- return mrb_f_exit_common(mrb, 0);
-}
+ mrb_sys_fail(mrb, "waitall failed");
+ }
-mrb_value
-mrb_f_exit_bang(mrb_state *mrb, mrb_value klass)
-{
- return mrb_f_exit_common(mrb, 1);
+ if (!pid)
+ mrb_last_status_clear(mrb);
+ else
+ mrb_last_status_set(mrb, pid, status);
+
+ st = mrb_last_status_get(mrb);
+ mrb_ary_push(mrb, result, mrb_assoc_new(mrb, mrb_fixnum_value(pid), st));
+ }
+
+ return result;
}
static mrb_value
-mrb_f_exit_common(mrb_state *mrb, int bang)
+mrb_f_fork(mrb_state *mrb, mrb_value klass)
{
- mrb_value status;
- int istatus, n;
+ mrb_value b;
+ pid_t pid;
- n = mrb_get_args(mrb, "|o", &status);
- if (n == 0) {
- status = (bang) ? mrb_false_value() : mrb_true_value();
- }
+ mrb_get_args(mrb, "&", &b);
- if (mrb_type(status) == MRB_TT_TRUE) {
- istatus = EXIT_SUCCESS;
- } else if (mrb_type(status) == MRB_TT_FALSE) {
- istatus = EXIT_FAILURE;
- } else {
- status = mrb_convert_type(mrb, status, MRB_TT_FIXNUM, "Integer", "to_int");
- istatus = mrb_fixnum(status);
- }
+ switch (pid = fork()) {
+ case 0:
+ mrb_process_set_pid_gv(mrb);
+ if (!mrb_nil_p(b)) {
+ mrb_yield(mrb, b, mrb_nil_value());
+ _exit(0);
+ }
+ return mrb_nil_value();
- if (bang) {
- _exit(istatus);
- } else {
- exit(istatus);
+ case -1:
+ mrb_sys_fail(mrb, "fork failed");
+ return mrb_nil_value();
+
+ default:
+ return mrb_fixnum_value(pid);
}
}
-mrb_value
-mrb_f_pid(mrb_state *mrb, mrb_value klass)
+static inline mrb_value
+mrb_f_exec(mrb_state *mrb, mrb_value klass)
{
- return mrb_fixnum_value((mrb_int)getpid());
-}
+ struct mrb_execarg *eargp;
-mrb_value
-mrb_f_ppid(mrb_state *mrb, mrb_value klass)
-{
- return mrb_fixnum_value((mrb_int)getppid());
-}
+ eargp = mrb_execarg_new(mrb);
-static mrb_value
-mrb_procstat_new(mrb_state *mrb, mrb_int pid, mrb_int status)
-{
- struct RClass *cls;
- cls = mrb_class_get_under(mrb, mrb_module_get(mrb, "Process"), "Status");
- return mrb_funcall(mrb, mrb_obj_value(cls), "new", 2, mrb_fixnum_value(pid), mrb_fixnum_value(status));
-}
+ if (eargp->envp)
+ execve(eargp->filename, eargp->argv, eargp->envp);
+ else
+ execv(eargp->filename, eargp->argv);
-static mrb_value
-mrb_procstat_coredump(mrb_state *mrb, mrb_value self)
-{
-#ifdef WCOREDUMP
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- return mrb_bool_value(WCOREDUMP(i));
-#else
- return mrb_false_value();
-#endif
-}
+ free(eargp);
+ mrb_sys_fail(mrb, "exec failed");
-static mrb_value
-mrb_procstat_exitstatus(mrb_state *mrb, mrb_value self)
-{
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- if (WIFEXITED(i)) {
- return mrb_fixnum_value(WEXITSTATUS(i));
- } else {
- return mrb_nil_value();
- }
+ return mrb_nil_value();
}
-static mrb_value
-mrb_procstat_exited(mrb_state *mrb, mrb_value self)
+static pid_t
+mrb_spawn_internal(mrb_state *mrb, mrb_value klass)
{
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- return mrb_bool_value(WIFEXITED(i));
-}
+ struct mrb_execarg *eargp;
+ pid_t pid;
-static mrb_value
-mrb_procstat_signaled(mrb_state *mrb, mrb_value self)
-{
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- return mrb_bool_value(WIFSIGNALED(i));
-}
+ eargp = mrb_execarg_new(mrb);
-static mrb_value
-mrb_procstat_stopped(mrb_state *mrb, mrb_value self)
-{
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- return mrb_bool_value(WIFSTOPPED(i));
+ if (eargp->envp)
+ pid = spawnve(eargp->filename, eargp->argv, eargp->envp, eargp->fd.in, eargp->fd.out, eargp->fd.err);
+ else
+ pid = spawnv(eargp->filename, eargp->argv, eargp->fd.in, eargp->fd.out, eargp->fd.err);
+
+ free(eargp);
+
+ return pid;
}
static mrb_value
-mrb_procstat_stopsig(mrb_state *mrb, mrb_value self)
+mrb_f_spawn(mrb_state *mrb, mrb_value klass)
{
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- if (WIFSTOPPED(i)) {
- return mrb_fixnum_value(WSTOPSIG(i));
- } else {
- return mrb_nil_value();
- }
+ pid_t pid;
+ pid = mrb_spawn_internal(mrb, klass);
+
+ if (pid == -1)
+ mrb_sys_fail(mrb, "spawn failed");
+
+ return mrb_fixnum_value(pid);
}
static mrb_value
-mrb_procstat_termsig(mrb_state *mrb, mrb_value self)
+mrb_f_system(mrb_state *mrb, mrb_value klass)
{
- int i = mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
- if (WIFSIGNALED(i)) {
- return mrb_fixnum_value(WTERMSIG(i));
- } else {
+ pid_t pid;
+ int status;
+
+ mrb_last_status_clear(mrb);
+
+ pid = mrb_spawn_internal(mrb, klass);
+
+ if (pid == -1)
return mrb_nil_value();
- }
+
+ pid = mrb_waitpid(pid, &status, 0);
+
+ if (pid == -1)
+ mrb_sys_fail(mrb, "system failed");
+
+ mrb_last_status_set(mrb, pid, status);
+
+ return status == EXIT_SUCCESS ? mrb_true_value() : mrb_false_value();
}
void
mrb_mruby_process_gem_init(mrb_state *mrb)
{
- struct RClass *p, *s;
+ struct RClass *p, *k;
- mrb_define_method(mrb, mrb->kernel_module, "exit", mrb_f_exit, MRB_ARGS_OPT(1));
- mrb_define_method(mrb, mrb->kernel_module, "exit!", mrb_f_exit_bang, MRB_ARGS_OPT(1));
- mrb_define_method(mrb, mrb->kernel_module, "fork", mrb_f_fork, MRB_ARGS_NONE());
- mrb_define_method(mrb, mrb->kernel_module, "sleep", mrb_f_sleep, MRB_ARGS_ANY());
- mrb_define_method(mrb, mrb->kernel_module, "system", mrb_f_system, MRB_ARGS_ANY());
+ k = mrb->kernel_module;
+ mrb_define_method(mrb, k, "abort", mrb_f_abort, MRB_ARGS_OPT(1));
+ mrb_define_method(mrb, k, "exit", mrb_f_exit, MRB_ARGS_OPT(1));
+ mrb_define_method(mrb, k, "exit!", mrb_f_exit_bang, MRB_ARGS_OPT(1));
+ mrb_define_method(mrb, k, "exec", mrb_f_exec, MRB_ARGS_REQ(1)|MRB_ARGS_REST());
+ mrb_define_method(mrb, k, "spawn", mrb_f_spawn, MRB_ARGS_REQ(1)|MRB_ARGS_REST());
+ mrb_define_method(mrb, k, "system", mrb_f_system, MRB_ARGS_REQ(1)|MRB_ARGS_REST());
p = mrb_define_module(mrb, "Process");
- mrb_define_class_method(mrb, p, "kill", mrb_f_kill, MRB_ARGS_ANY());
- mrb_define_class_method(mrb, p, "fork", mrb_f_fork, MRB_ARGS_NONE());
- mrb_define_class_method(mrb, p, "waitpid", mrb_f_waitpid, MRB_ARGS_ANY());
- mrb_define_class_method(mrb, p, "pid", mrb_f_pid, MRB_ARGS_NONE());
- mrb_define_class_method(mrb, p, "ppid", mrb_f_ppid, MRB_ARGS_NONE());
-
- s = mrb_define_class_under(mrb, p, "Status", mrb->object_class);
- mrb_define_method(mrb, s, "coredump?", mrb_procstat_coredump, MRB_ARGS_NONE());
- mrb_define_method(mrb, s, "exited?", mrb_procstat_exited, MRB_ARGS_NONE());
- mrb_define_method(mrb, s, "exitstatus", mrb_procstat_exitstatus, MRB_ARGS_NONE());
- mrb_define_method(mrb, s, "signaled?", mrb_procstat_signaled, MRB_ARGS_NONE());
- mrb_define_method(mrb, s, "stopped?", mrb_procstat_stopped, MRB_ARGS_NONE());
- mrb_define_method(mrb, s, "stopsig", mrb_procstat_stopsig, MRB_ARGS_NONE());
- mrb_define_method(mrb, s, "termsig", mrb_procstat_termsig, MRB_ARGS_NONE());
-
- mrb_define_const(mrb, p, "WNOHANG", mrb_fixnum_value(WNOHANG));
+ mrb_define_class_method(mrb, p, "argv0", mrb_proc_argv0, MRB_ARGS_NONE());
+ mrb_define_class_method(mrb, p, "abort", mrb_f_abort, MRB_ARGS_OPT(1));
+ mrb_define_class_method(mrb, p, "exit", mrb_f_exit, MRB_ARGS_OPT(1));
+ mrb_define_class_method(mrb, p, "exit!", mrb_f_exit_bang, MRB_ARGS_OPT(1));
+ mrb_define_class_method(mrb, p, "kill", mrb_f_kill, MRB_ARGS_REQ(2)|MRB_ARGS_REST());
+ mrb_define_class_method(mrb, p, "exec", mrb_f_exec, MRB_ARGS_REQ(1)|MRB_ARGS_REST());
+ mrb_define_class_method(mrb, p, "waitpid", mrb_f_wait, MRB_ARGS_OPT(2));
+ mrb_define_class_method(mrb, p, "waitpid2", mrb_f_wait2, MRB_ARGS_OPT(2));
+ mrb_define_class_method(mrb, p, "wait", mrb_f_wait, MRB_ARGS_OPT(2));
+ mrb_define_class_method(mrb, p, "wait2", mrb_f_wait2, MRB_ARGS_OPT(2));
+ mrb_define_class_method(mrb, p, "waitall", mrb_f_waitall, MRB_ARGS_NONE());
+ mrb_define_class_method(mrb, p, "pid", mrb_f_pid, MRB_ARGS_NONE());
+ mrb_define_class_method(mrb, p, "ppid", mrb_f_ppid, MRB_ARGS_NONE());
+ mrb_define_class_method(mrb, p, "spawn", mrb_f_spawn, MRB_ARGS_REQ(1)|MRB_ARGS_REST());
+
+#if defined(__APPLE__) || defined(__linux__)
+ mrb_define_method(mrb, k, "fork", mrb_f_fork, MRB_ARGS_BLOCK());
+ mrb_define_class_method(mrb, p, "fork", mrb_f_fork, MRB_ARGS_BLOCK());
+#endif
+
+ mrb_define_const(mrb, p, "WNOHANG", mrb_fixnum_value(WNOHANG));
mrb_define_const(mrb, p, "WUNTRACED", mrb_fixnum_value(WUNTRACED));
- mrb_gv_set(mrb, mrb_intern_lit(mrb, "$$"), mrb_fixnum_value((mrb_int)getpid()));
- mrb_gv_set(mrb, mrb_intern_lit(mrb, "$?"), mrb_nil_value());
+ mrb_process_set_pid_gv(mrb);
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$0"), mrb_proc_progname(mrb));
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$PROGRAM_NAME"), mrb_proc_progname(mrb));
+
+ mrb_mruby_process_gem_signal_init(mrb);
+ mrb_mruby_process_gem_procstat_init(mrb);
}
void
mrb_mruby_process_gem_final(mrb_state *mrb)
{
+
}
diff --git a/src/signal.c b/src/signal.c
new file mode 100644
index 0000000..e4746e9
--- /dev/null
+++ b/src/signal.c
@@ -0,0 +1,274 @@
+/* MIT License
+ *
+ * This code was based on https://github.com/ruby/ruby/blob/trunk/signal.c
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "mruby.h"
+#include "mruby/hash.h"
+
+#include
+#include
+#include
+#include
+
+#if defined(__BEOS__) || defined(__HAIKU__)
+# undef SIGBUS
+#endif
+
+#ifndef NSIG
+# define NSIG (_SIGMAX + 1) /* For QNX */
+#endif
+
+#ifndef SIGINT /* For Win32 */
+# define SIGINT 2
+#endif
+#ifndef SIGKILL /* For Win32 */
+# define SIGKILL 9
+#endif
+
+static const struct signals {
+ const char *signm;
+ int signo;
+} siglist [] = {
+ {"EXIT", 0},
+#ifdef SIGHUP
+ {"HUP", SIGHUP},
+#endif
+ {"INT", SIGINT},
+#ifdef SIGQUIT
+ {"QUIT", SIGQUIT},
+#endif
+#ifdef SIGILL
+ {"ILL", SIGILL},
+#endif
+#ifdef SIGTRAP
+ {"TRAP", SIGTRAP},
+#endif
+#ifdef SIGABRT
+ {"ABRT", SIGABRT},
+#endif
+#ifdef SIGIOT
+ {"IOT", SIGIOT},
+#endif
+#ifdef SIGEMT
+ {"EMT", SIGEMT},
+#endif
+#ifdef SIGFPE
+ {"FPE", SIGFPE},
+#endif
+#ifdef SIGKILL
+ {"KILL", SIGKILL},
+#endif
+#ifdef SIGBUS
+ {"BUS", SIGBUS},
+#endif
+#ifdef SIGSEGV
+ {"SEGV", SIGSEGV},
+#endif
+#ifdef SIGSYS
+ {"SYS", SIGSYS},
+#endif
+#ifdef SIGPIPE
+ {"PIPE", SIGPIPE},
+#endif
+#ifdef SIGALRM
+ {"ALRM", SIGALRM},
+#endif
+#ifdef SIGTERM
+ {"TERM", SIGTERM},
+#endif
+#ifdef SIGURG
+ {"URG", SIGURG},
+#endif
+#ifdef SIGSTOP
+ {"STOP", SIGSTOP},
+#endif
+#ifdef SIGTSTP
+ {"TSTP", SIGTSTP},
+#endif
+#ifdef SIGCONT
+ {"CONT", SIGCONT},
+#endif
+#ifdef SIGCHLD
+ {"CHLD", SIGCHLD},
+#endif
+#ifdef SIGCLD
+ {"CLD", SIGCLD},
+#else
+# ifdef SIGCHLD
+ {"CLD", SIGCHLD},
+# endif
+#endif
+#ifdef SIGTTIN
+ {"TTIN", SIGTTIN},
+#endif
+#ifdef SIGTTOU
+ {"TTOU", SIGTTOU},
+#endif
+#ifdef SIGIO
+ {"IO", SIGIO},
+#endif
+#ifdef SIGXCPU
+ {"XCPU", SIGXCPU},
+#endif
+#ifdef SIGXFSZ
+ {"XFSZ", SIGXFSZ},
+#endif
+#ifdef SIGVTALRM
+ {"VTALRM", SIGVTALRM},
+#endif
+#ifdef SIGPROF
+ {"PROF", SIGPROF},
+#endif
+#ifdef SIGWINCH
+ {"WINCH", SIGWINCH},
+#endif
+#ifdef SIGUSR1
+ {"USR1", SIGUSR1},
+#endif
+#ifdef SIGUSR2
+ {"USR2", SIGUSR2},
+#endif
+#ifdef SIGLOST
+ {"LOST", SIGLOST},
+#endif
+#ifdef SIGMSG
+ {"MSG", SIGMSG},
+#endif
+#ifdef SIGPWR
+ {"PWR", SIGPWR},
+#endif
+#ifdef SIGPOLL
+ {"POLL", SIGPOLL},
+#endif
+#ifdef SIGDANGER
+ {"DANGER", SIGDANGER},
+#endif
+#ifdef SIGMIGRATE
+ {"MIGRATE", SIGMIGRATE},
+#endif
+#ifdef SIGPRE
+ {"PRE", SIGPRE},
+#endif
+#ifdef SIGGRANT
+ {"GRANT", SIGGRANT},
+#endif
+#ifdef SIGRETRACT
+ {"RETRACT", SIGRETRACT},
+#endif
+#ifdef SIGSOUND
+ {"SOUND", SIGSOUND},
+#endif
+#ifdef SIGINFO
+ {"INFO", SIGINFO},
+#endif
+ {NULL, 0}
+};
+
+const char signame_prefix[3] = "SIG";
+
+int
+signm2signo(const char *nm)
+{
+ const struct signals *sigs;
+
+ for (sigs = siglist; sigs->signm; sigs++)
+
+ if (strcmp(sigs->signm, nm) == 0)
+ return sigs->signo;
+
+ return 0;
+}
+
+const char*
+signo2signm(int no)
+{
+ const struct signals *sigs;
+
+ for (sigs = siglist; sigs->signm; sigs++)
+
+ if (sigs->signo == no)
+ return sigs->signm;
+
+ return 0;
+}
+
+/*
+ * call-seq:
+ * Signal.signame(signo) -> string or nil
+ *
+ * Convert signal number to signal name.
+ * Returns +nil+ if the signo is an invalid signal number.
+ */
+static mrb_value
+mrb_sig_signame(mrb_state *mrb, mrb_value klass)
+{
+ mrb_value sigo;
+ int signo;
+ const char *signame;
+
+ mrb_get_args(mrb, "i", &sigo);
+
+ signo = mrb_fixnum(sigo);
+ signame = signo2signm(signo);
+
+ if (!signame)
+ return mrb_nil_value();
+
+ return mrb_str_new_cstr(mrb, signame);
+}
+
+/*
+ * call-seq:
+ * Signal.list -> a_hash
+ *
+ * Returns a list of signal names mapped to the corresponding
+ * underlying signal numbers.
+ *
+ * Signal.list #=> {"EXIT"=>0, "HUP"=>1, "INT"=>2, "QUIT"=>3, "ILL"=>4, "TRAP"=>5, "IOT"=>6, "ABRT"=>6, "FPE"=>8, "KILL"=>9, "BUS"=>7, "SEGV"=>11, "SYS"=>31, "PIPE"=>13, "ALRM"=>14, "TERM"=>15, "URG"=>23, "STOP"=>19, "TSTP"=>20, "CONT"=>18, "CHLD"=>17, "CLD"=>17, "TTIN"=>21, "TTOU"=>22, "IO"=>29, "XCPU"=>24, "XFSZ"=>25, "VTALRM"=>26, "PROF"=>27, "WINCH"=>28, "USR1"=>10, "USR2"=>12, "PWR"=>30, "POLL"=>29}
+ */
+static mrb_value
+mrb_sig_list(mrb_state *mrb, mrb_value klass)
+{
+ mrb_value h = mrb_hash_new(mrb);
+ const struct signals *sigs;
+
+ for (sigs = siglist; sigs->signm; sigs++) {
+ mrb_value sig, signo;
+
+ sig = mrb_str_new_cstr(mrb, sigs->signm);
+ signo = mrb_fixnum_value(sigs->signo);
+
+ mrb_hash_set(mrb, h, sig, signo);
+ }
+
+ return h;
+}
+
+void
+mrb_mruby_process_gem_signal_init(mrb_state *mrb)
+{
+ struct RClass *s;
+
+ s = mrb_define_module(mrb, "Signal");
+ mrb_define_class_method(mrb, s, "signame", mrb_sig_signame, MRB_ARGS_REQ(1));
+ mrb_define_class_method(mrb, s, "list", mrb_sig_list, MRB_ARGS_NONE());
+}
diff --git a/src/signals.cstub b/src/signals.cstub
deleted file mode 100644
index d903467..0000000
--- a/src/signals.cstub
+++ /dev/null
@@ -1,78 +0,0 @@
-#ifdef SIGABRT
- { "ABRT", SIGABRT },
-#endif
-#ifdef SIGALRM
- { "ALRM", SIGALRM },
-#endif
-#ifdef SIGBUS
- { "BUS", SIGBUS },
-#endif
-#ifdef SIGCHLD
- { "CHLD", SIGCHLD },
-#endif
-#ifdef SIGCONT
- { "CONT", SIGCONT },
-#endif
-#ifdef SIGFPE
- { "FPE", SIGFPE },
-#endif
-#ifdef SIGHUP
- { "HUP", SIGHUP },
-#endif
-#ifdef SIGILL
- { "ILL", SIGILL },
-#endif
-#ifdef SIGINT
- { "INT", SIGINT },
-#endif
-#ifdef SIGKILL
- { "KILL", SIGKILL },
-#endif
-#ifdef SIGPIPE
- { "PIPE", SIGPIPE },
-#endif
-#ifdef SIGPROF
- { "PROF", SIGPROF },
-#endif
-#ifdef SIGQUIT
- { "QUIT", SIGQUIT },
-#endif
-#ifdef SIGSEGV
- { "SEGV", SIGSEGV },
-#endif
-#ifdef SIGSTOP
- { "STOP", SIGSTOP },
-#endif
-#ifdef SIGSYS
- { "SYS", SIGSYS },
-#endif
-#ifdef SIGTERM
- { "TERM", SIGTERM },
-#endif
-#ifdef SIGTRAP
- { "TRAP", SIGTRAP },
-#endif
-#ifdef SIGTSTP
- { "TSTP", SIGTSTP },
-#endif
-#ifdef SIGTTIN
- { "TTIN", SIGTTIN },
-#endif
-#ifdef SIGTTOU
- { "TTOU", SIGTTOU },
-#endif
-#ifdef SIGURG
- { "URG", SIGURG },
-#endif
-#ifdef SIGUSR1
- { "USR1", SIGUSR1 },
-#endif
-#ifdef SIGUSR2
- { "USR2", SIGUSR2 },
-#endif
-#ifdef SIGXCPU
- { "XCPU", SIGXCPU },
-#endif
-#ifdef SIGXFSZ
- { "XFSZ", SIGXFSZ },
-#endif
diff --git a/src/signals.def b/src/signals.def
deleted file mode 100644
index ffd9f41..0000000
--- a/src/signals.def
+++ /dev/null
@@ -1,26 +0,0 @@
-SIGABRT
-SIGALRM
-SIGBUS
-SIGCHLD
-SIGCONT
-SIGFPE
-SIGHUP
-SIGILL
-SIGINT
-SIGKILL
-SIGPIPE
-SIGPROF
-SIGQUIT
-SIGSEGV
-SIGSTOP
-SIGSYS
-SIGTERM
-SIGTRAP
-SIGTSTP
-SIGTTIN
-SIGTTOU
-SIGURG
-SIGUSR1
-SIGUSR2
-SIGXCPU
-SIGXFSZ
diff --git a/src/status.c b/src/status.c
new file mode 100644
index 0000000..41e264c
--- /dev/null
+++ b/src/status.c
@@ -0,0 +1,154 @@
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "mruby.h"
+#include "mruby/variable.h"
+#include "mruby/ext/process.h"
+
+static mrb_value
+mrb_pst_new(mrb_state *mrb, pid_t pid, mrb_int status)
+{
+ struct RClass *p, *s;
+ p = mrb_module_get(mrb, "Process");
+ s = mrb_class_get_under(mrb, p, "Status");
+
+ return mrb_funcall(mrb, mrb_obj_value(s), "new", 2,
+ mrb_fixnum_value(pid), mrb_fixnum_value(status));
+}
+
+static int
+mrb_pst_last_status_get(mrb_state *mrb, mrb_value self)
+{
+ return mrb_fixnum(mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "@status")));
+}
+
+static void
+mrb_pst_last_status_set(mrb_state *mrb, mrb_value pst)
+{
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$?"), pst);
+ mrb_gv_set(mrb, mrb_intern_lit(mrb, "$CHILD_STATUS"), pst);
+}
+
+void
+mrb_last_status_set(mrb_state *mrb, pid_t pid, mrb_int status)
+{
+ mrb_pst_last_status_set(mrb, mrb_pst_new(mrb, pid, status));
+}
+
+mrb_value
+mrb_last_status_get(mrb_state *mrb)
+{
+ return mrb_gv_get(mrb, mrb_intern_lit(mrb, "$?"));
+}
+
+void
+mrb_last_status_clear(mrb_state *mrb)
+{
+ return mrb_pst_last_status_set(mrb, mrb_nil_value());
+}
+
+static mrb_value
+mrb_pst_wcoredump(mrb_state *mrb, mrb_value self)
+{
+#ifdef WCOREDUMP
+ int i = mrb_pst_last_status_get(mrb, self);
+ return mrb_bool_value(WCOREDUMP(i));
+#else
+ return mrb_false_value();
+#endif
+}
+
+static mrb_value
+mrb_pst_wexitstatus(mrb_state *mrb, mrb_value self)
+{
+ int i = mrb_pst_last_status_get(mrb, self);
+
+ if (WIFEXITED(i))
+ return mrb_fixnum_value(WEXITSTATUS(i));
+
+ return mrb_nil_value();
+}
+
+static mrb_value
+mrb_pst_wifexited(mrb_state *mrb, mrb_value self)
+{
+ int i = mrb_pst_last_status_get(mrb, self);
+
+ return mrb_bool_value(WIFEXITED(i));
+}
+
+static mrb_value
+mrb_pst_wifsignaled(mrb_state *mrb, mrb_value self)
+{
+ int i = mrb_pst_last_status_get(mrb, self);
+
+ return mrb_bool_value(WIFSIGNALED(i));
+}
+
+static mrb_value
+mrb_pst_wifstopped(mrb_state *mrb, mrb_value self)
+{
+ int i = mrb_pst_last_status_get(mrb, self);
+
+ return mrb_bool_value(WIFSTOPPED(i));
+}
+
+static mrb_value
+mrb_pst_wstopsig(mrb_state *mrb, mrb_value self)
+{
+ int i = mrb_pst_last_status_get(mrb, self);
+
+ if (WIFSTOPPED(i))
+ return mrb_fixnum_value(WSTOPSIG(i));
+
+ return mrb_nil_value();
+}
+
+static mrb_value
+mrb_pst_wtermsig(mrb_state *mrb, mrb_value self)
+{
+ int i = mrb_pst_last_status_get(mrb, self);
+
+ if (WIFSIGNALED(i)) {
+ return mrb_fixnum_value(WTERMSIG(i));
+ } else {
+ return mrb_nil_value();
+ }
+}
+
+void
+mrb_mruby_process_gem_procstat_init(mrb_state *mrb)
+{
+ struct RClass *p, *s;
+
+ p = mrb_module_get(mrb, "Process");
+ s = mrb_define_class_under(mrb, p, "Status", mrb->object_class);
+
+ mrb_define_method(mrb, s, "coredump?", mrb_pst_wcoredump, MRB_ARGS_NONE());
+ mrb_define_method(mrb, s, "exited?", mrb_pst_wifexited, MRB_ARGS_NONE());
+ mrb_define_method(mrb, s, "exitstatus", mrb_pst_wexitstatus, MRB_ARGS_NONE());
+ mrb_define_method(mrb, s, "signaled?", mrb_pst_wifsignaled, MRB_ARGS_NONE());
+ mrb_define_method(mrb, s, "stopped?", mrb_pst_wifstopped, MRB_ARGS_NONE());
+ mrb_define_method(mrb, s, "stopsig", mrb_pst_wstopsig, MRB_ARGS_NONE());
+ mrb_define_method(mrb, s, "termsig", mrb_pst_wtermsig, MRB_ARGS_NONE());
+
+ mrb_last_status_clear(mrb);
+}
diff --git a/src/win32.c b/src/win32.c
new file mode 100644
index 0000000..d4518a8
--- /dev/null
+++ b/src/win32.c
@@ -0,0 +1,593 @@
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "mruby.h"
+#include "mruby/string.h"
+#include "mruby/data.h"
+#include "mruby/ext/process.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#define BUFSIZE 4096
+
+#define MAXCHILDNUM 256 /* max num of child processes */
+
+#ifndef P_OVERLAY
+# define P_OVERLAY 2
+#endif
+
+#ifndef P_NOWAIT
+# define P_NOWAIT 1
+#endif
+
+/* License: Ruby's */
+static struct ChildRecord {
+ HANDLE hProcess;
+ pid_t pid;
+} ChildRecord[MAXCHILDNUM];
+
+/* License: Ruby's */
+#define FOREACH_CHILD(v) do { \
+ struct ChildRecord* v; \
+ for (v = ChildRecord; v < ChildRecord + sizeof(ChildRecord) / sizeof(ChildRecord[0]); ++v)
+#define END_FOREACH_CHILD } while (0)
+
+static FARPROC get_proc_address(const char *module, const char *func, HANDLE *mh);
+static pid_t poll_child_status(struct ChildRecord *child, int *stat_loc);
+static struct ChildRecord *FindChildSlot(pid_t pid);
+static struct ChildRecord *FindChildSlotByHandle(HANDLE h);
+static struct ChildRecord *FindFreeChildSlot(void);
+static void CloseChildHandle(struct ChildRecord *child);
+static struct ChildRecord *CreateChild(const WCHAR *cmd, const WCHAR *prog, SECURITY_ATTRIBUTES *psa, HANDLE hInput, HANDLE hOutput, HANDLE hError, DWORD dwCreationFlags, LPVOID environment);
+static pid_t child_result(struct ChildRecord *child, int mode);
+static char* argv_to_str(char* const* argv);
+static WCHAR* str_to_wstr(const char *utf8, int mlen);
+
+mrb_value
+mrb_argv0(mrb_state *mrb)
+{
+ TCHAR argv0[MAX_PATH + 1];
+
+ GetModuleFileName(NULL, argv0, MAX_PATH + 1);
+
+ return mrb_str_new_cstr(mrb, argv0);
+}
+
+mrb_value
+mrb_progname(mrb_state *mrb)
+{
+ TCHAR argv0[MAX_PATH + 1];
+ char *progname;
+
+ GetModuleFileName(NULL, argv0, MAX_PATH + 1);
+
+ progname = strrchr(argv0, '\\');
+
+ if (progname)
+ progname++;
+ else
+ progname = argv0;
+
+ return mrb_str_new_cstr(mrb, progname);
+}
+
+int
+fork(void)
+{
+ return -1;
+}
+
+pid_t
+getppid(void)
+{
+ typedef long (WINAPI query_func)(HANDLE, int, void *, ULONG, ULONG *);
+ static query_func *pNtQueryInformationProcess = (query_func *) - 1;
+ pid_t ppid = 0;
+
+ if (pNtQueryInformationProcess == (query_func *) - 1)
+ pNtQueryInformationProcess = (query_func *)get_proc_address("ntdll.dll", "NtQueryInformationProcess", NULL);
+
+ if (pNtQueryInformationProcess) {
+ struct {
+ long ExitStatus;
+ void* PebBaseAddress;
+ uintptr_t AffinityMask;
+ uintptr_t BasePriority;
+ uintptr_t UniqueProcessId;
+ uintptr_t ParentProcessId;
+ } pbi;
+
+ ULONG len;
+ long ret = pNtQueryInformationProcess(GetCurrentProcess(), 0, &pbi, sizeof(pbi), &len);
+
+ if (!ret)
+ ppid = pbi.ParentProcessId;
+ }
+
+ return ppid;
+}
+
+pid_t
+waitpid(pid_t pid, int *stat_loc, int options)
+{
+ DWORD timeout;
+ struct ChildRecord* child;
+ int count, retried, ret;
+
+ if (options == WNOHANG)
+ timeout = 0;
+ else
+ timeout = INFINITE;
+
+ if (pid == -1) {
+ HANDLE targets[MAXCHILDNUM];
+ struct ChildRecord* cause;
+
+ count = 0;
+
+ FOREACH_CHILD(child) {
+ if (!child->pid || child->pid < 0) continue;
+ if ((pid = poll_child_status(child, stat_loc))) return pid;
+ targets[count++] = child->hProcess;
+ } END_FOREACH_CHILD;
+
+ if (!count) {
+ errno = ECHILD;
+ return -1;
+ }
+
+ ret = WaitForMultipleObjects(count, targets, FALSE, timeout);
+ if (ret == WAIT_TIMEOUT) return 0;
+ if ((ret -= WAIT_OBJECT_0) == count) return -1;
+ if (ret > count) return -1;
+
+ cause = FindChildSlotByHandle(targets[ret]);
+
+ if (!cause) {
+ errno = ECHILD;
+ return -1;
+ }
+
+ return poll_child_status(cause, stat_loc);
+ }
+ else {
+ child = FindChildSlot(pid);
+ retried = 0;
+
+ if (!child) {
+ errno = ECHILD;
+ return -1;
+ }
+
+ while (!(pid = poll_child_status(child, stat_loc))) {
+ /* wait... */
+ ret = WaitForMultipleObjects(1, &child->hProcess, FALSE, timeout);
+
+ if (ret == WAIT_OBJECT_0 + 1) return -1; /* maybe EINTR */
+ if (ret != WAIT_OBJECT_0) {
+ /* still active */
+ if (options & WNOHANG) {
+ pid = 0;
+ break;
+ }
+ ++retried;
+ }
+ }
+
+ if (pid == -1 && retried) pid = 0;
+ }
+
+ return pid;
+}
+
+int
+kill(pid_t pid, int sig)
+{
+ pid_t ret = 0;
+ DWORD ctrlEvent, status;
+ HANDLE hProc;
+ struct ChildRecord* child;
+
+ if (pid < 0 || (pid == 0 && sig != SIGINT))
+ return -1;
+
+ if ((unsigned int)pid == GetCurrentProcessId() && (sig != 0 && sig != SIGKILL)) {
+ ret = raise(sig);
+ return ret;
+ }
+
+ switch (sig) {
+ case 0:
+ hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, (DWORD)pid);
+
+ if (hProc == NULL || hProc == INVALID_HANDLE_VALUE) {
+ ret = -1;
+ }
+ else {
+ CloseHandle(hProc);
+ }
+
+ break;
+
+ case SIGINT:
+ ctrlEvent = CTRL_C_EVENT;
+
+ if (pid != 0) ctrlEvent = CTRL_BREAK_EVENT;
+ if (!GenerateConsoleCtrlEvent(ctrlEvent, (DWORD)pid)) ret = -1;
+
+ break;
+
+ case SIGKILL:
+ child = FindChildSlot(pid);
+
+ if (child) {
+ hProc = child->hProcess;
+ }
+ else {
+ hProc = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, FALSE, (DWORD)pid);
+ }
+
+ if (hProc == NULL || hProc == INVALID_HANDLE_VALUE) {
+ ret = -1;
+ }
+ else {
+ if (!GetExitCodeProcess(hProc, &status)) {
+ ret = -1;
+ }
+ else if (status == STILL_ACTIVE) {
+ if (!TerminateProcess(hProc, 0)) {
+ ret = -1;
+ }
+ }
+ else {
+ ret = -1;
+ }
+
+ if (!child) {
+ CloseHandle(hProc);
+ }
+ }
+
+ break;
+
+ default:
+ ret = -1;
+ }
+
+ return ret;
+}
+
+pid_t
+spawnve(const char *shell, char *const argv[], char *const envp[], mrb_value in, mrb_value out, mrb_value err)
+{
+ LPTSTR lpszCurrentVariable;
+ TCHAR chNewEnv[BUFSIZE];
+ HANDLE input, output, error;
+
+ int i = 0;
+ char* env = envp[i];
+ pid_t ret = -1;
+ char *cmd = argv_to_str(argv);
+
+ WCHAR *wcmd, *wshell;
+ char tCmd[strlen(cmd)];
+ char tShell[strlen(shell)];
+
+ input = mrb_cptr_p(in) ? mrb_cptr(in) : GetStdHandle(STD_INPUT_HANDLE);
+ output = mrb_cptr_p(out) ? mrb_cptr(out) : GetStdHandle(STD_OUTPUT_HANDLE);
+ error = mrb_cptr_p(err) ? mrb_cptr(err) : GetStdHandle(STD_ERROR_HANDLE);
+
+ lpszCurrentVariable = (LPTSTR) chNewEnv;
+
+ while (env != NULL) {
+ if (FAILED(strcpy(lpszCurrentVariable, TEXT(env)))) {
+ printf("env-string copy failed\n");
+ return FALSE;
+ }
+
+ lpszCurrentVariable += lstrlen(lpszCurrentVariable) + 1;
+
+ i++;
+ env = envp[i];
+ }
+
+ *lpszCurrentVariable = (TCHAR)0;
+
+ strcpy(tCmd,cmd);
+ strcpy(tShell,shell);
+
+ wshell = str_to_wstr(tShell, strlen(tShell));
+ wcmd = str_to_wstr(tCmd, strlen(tCmd));
+
+ ret = child_result(CreateChild(wshell, wcmd, NULL, input, output, error, 0, (LPVOID) chNewEnv), P_NOWAIT);
+
+ free(wshell);
+ free(wcmd);
+ free(cmd);
+
+ return ret;
+}
+
+pid_t
+spawnv(const char *shell, char *const argv[], mrb_value in, mrb_value out, mrb_value err)
+{
+ WCHAR *wcmd, *wshell;
+ pid_t ret = -1;
+ char *cmd = argv_to_str(argv);
+ char tShell[strlen(shell)];
+ HANDLE input, output, error;
+
+ strcpy(tShell, shell);
+
+ input = mrb_cptr_p(in) ? mrb_cptr(in) : GetStdHandle(STD_INPUT_HANDLE);
+ output = mrb_cptr_p(out) ? mrb_cptr(out) : GetStdHandle(STD_OUTPUT_HANDLE);
+ error = mrb_cptr_p(err) ? mrb_cptr(err) : GetStdHandle(STD_ERROR_HANDLE);
+
+ wshell = str_to_wstr(tShell, strlen(tShell));
+ wcmd = str_to_wstr(cmd, strlen(cmd));
+
+ ret = child_result(
+ CreateChild(wshell, wcmd, NULL, input, output, error, 0, NULL), P_NOWAIT);
+
+ free(wshell);
+ free(wcmd);
+ free(cmd);
+
+ return ret;
+}
+
+static FARPROC
+get_proc_address(const char *module, const char *func, HANDLE *mh)
+{
+ HANDLE h;
+ FARPROC ptr;
+
+ if (mh)
+ h = LoadLibrary(module);
+ else
+ h = GetModuleHandle(module);
+
+ if (!h)
+ return NULL;
+
+ ptr = GetProcAddress(h, func);
+
+ if (mh) {
+ if (ptr)
+ *mh = h;
+ else
+ FreeLibrary(h);
+ }
+
+ return ptr;
+}
+
+static struct ChildRecord *
+CreateChild(const WCHAR *shell, const WCHAR *cmd, SECURITY_ATTRIBUTES *psa,
+ HANDLE hInput, HANDLE hOutput, HANDLE hError, DWORD dwCreationFlags, LPVOID env)
+{
+ BOOL fRet;
+ STARTUPINFOW aStartupInfo;
+ PROCESS_INFORMATION aProcessInformation;
+ SECURITY_ATTRIBUTES sa;
+ struct ChildRecord *child;
+
+ if (!cmd && !shell)
+ return NULL;
+
+ child = FindFreeChildSlot();
+
+ if (!child)
+ return NULL;
+
+ if (!psa) {
+ sa.nLength = sizeof (SECURITY_ATTRIBUTES);
+ sa.lpSecurityDescriptor = NULL;
+ sa.bInheritHandle = TRUE;
+ psa = &sa;
+ }
+
+ memset(&aStartupInfo, 0, sizeof(aStartupInfo));
+ memset(&aProcessInformation, 0, sizeof(aProcessInformation));
+
+ aStartupInfo.cb = sizeof(aStartupInfo);
+ aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
+
+ if (hInput) {
+ aStartupInfo.hStdInput = hInput;
+ }
+ else {
+ aStartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ }
+
+ if (hOutput) {
+ aStartupInfo.hStdOutput = hOutput;
+ }
+ else {
+ aStartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ }
+
+ if (hError) {
+ aStartupInfo.hStdError = hError;
+ }
+ else {
+ aStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ }
+
+ dwCreationFlags |= NORMAL_PRIORITY_CLASS;
+
+ if (lstrlenW(cmd) > 32767) {
+ child->pid = 0; /* release the slot */
+ return NULL;
+ }
+
+ fRet = CreateProcessW(shell, (WCHAR *)cmd, psa, psa,
+ psa->bInheritHandle, dwCreationFlags, env, NULL,
+ &aStartupInfo, &aProcessInformation);
+
+ if (!fRet) {
+ child->pid = 0; /* release the slot */
+ return NULL;
+ }
+
+ CloseHandle(aProcessInformation.hThread);
+
+ child->hProcess = aProcessInformation.hProcess;
+ child->pid = (pid_t)aProcessInformation.dwProcessId;
+
+ return child;
+}
+
+static pid_t
+child_result(struct ChildRecord *child, int mode)
+{
+ DWORD exitcode;
+
+ if (!child)
+ return -1;
+
+ if (mode == P_OVERLAY) {
+ WaitForSingleObject(child->hProcess, INFINITE);
+ GetExitCodeProcess(child->hProcess, &exitcode);
+ CloseChildHandle(child);
+ _exit(exitcode);
+ }
+
+ return child->pid;
+}
+
+static pid_t
+poll_child_status(struct ChildRecord *child, int *stat_loc)
+{
+ DWORD exitcode;
+
+ if (!GetExitCodeProcess(child->hProcess, &exitcode)) {
+ /* If an error occurred, return immediately. */
+ error_exit:
+ CloseChildHandle(child);
+ return -1;
+ }
+
+ if (exitcode != STILL_ACTIVE) {
+ pid_t pid;
+
+ /* If already died, wait process's real termination. */
+ if (WaitForSingleObject(child->hProcess, INFINITE) != WAIT_OBJECT_0) {
+ goto error_exit;
+ }
+
+ pid = child->pid;
+ CloseChildHandle(child);
+
+ if (stat_loc)
+ *stat_loc = exitcode << 8;
+
+ return pid;
+ }
+
+ return 0;
+}
+
+static struct ChildRecord *
+FindChildSlot(pid_t pid)
+{
+ FOREACH_CHILD(child) {
+ if (pid == -1 || child->pid == pid)
+ return child;
+ } END_FOREACH_CHILD;
+
+ return NULL;
+}
+
+static struct ChildRecord *
+FindChildSlotByHandle(HANDLE h)
+{
+ FOREACH_CHILD(child) {
+ if (child->hProcess == h)
+ return child;
+ } END_FOREACH_CHILD;
+
+ return NULL;
+}
+
+static struct ChildRecord *
+FindFreeChildSlot(void)
+{
+ FOREACH_CHILD(child) {
+ if (!child->pid) {
+ child->pid = -1;
+ child->hProcess = NULL;
+
+ return child;
+ }
+ } END_FOREACH_CHILD;
+
+ return NULL;
+}
+
+static void
+CloseChildHandle(struct ChildRecord *child)
+{
+ HANDLE h = child->hProcess;
+ child->hProcess = NULL;
+ child->pid = 0;
+
+ CloseHandle(h);
+}
+
+static char*
+argv_to_str(char* const* argv)
+{
+ char args[8191];
+ int i = 0;
+ char* arg = argv[i];
+
+ while (arg != NULL) {
+ if (i == 0)
+ sprintf(args, "%s", arg);
+ else
+ sprintf(args, "%s %s", args, arg);
+
+ i++;
+ arg = argv[i];
+ }
+
+ return strdup(args);
+}
+
+static WCHAR*
+str_to_wstr(const char *utf8, int mlen)
+{
+ int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8, mlen, NULL, 0);
+ wchar_t* utf16 = (wchar_t*)malloc((wlen+1) * sizeof(wchar_t));
+
+ if (utf16 == NULL)
+ return NULL;
+
+ if (MultiByteToWideChar(CP_UTF8, 0, utf8, mlen, utf16, wlen) > 0)
+ utf16[wlen] = 0;
+
+ return utf16;
+}
diff --git a/test/process.rb b/test/process.rb
index 17069e6..789db39 100644
--- a/test/process.rb
+++ b/test/process.rb
@@ -1,38 +1,285 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+class String
+ def strip
+ a = 0
+ z = size - 1
+ a += 1 while a <= z and " \f\n\r\t\v".include?(self[a])
+ z -= 1 while a <= z and " \f\n\r\t\v\0".include?(self[z])
+ z >= 0 ? self[a..z] : ''
+ end
+end unless ''.respond_to? :strip
+
+def IO.sysopen(path, mod)
+ ProcessTest.sysopen(path, mod)
+end if OS.windows?
+
+def read(path)
+ f = File.open(path)
+ f.read.to_s.strip.sub("\r", '').sub("\n", '').downcase
+ensure
+ f.close if f
+end
+
+def wait_for_pid(pid)
+ loop do
+ p = Process.waitpid(pid, Process::WNOHANG)
+ break if p
+ end
+end
+
+def assert_not_windows(*args, &block)
+ assert(*args, &block) if OS.posix?
+end
+
+def assert_windows(*args, &block)
+ assert(*args, &block) if OS.windows?
+end
+
assert('Process') do
- Process.class == Module
+ assert_kind_of Module, Process
end
-assert('Process.kill') do
- # to be sure we won't kill an innocent process!
- Process.pid > 0 and Process.kill(0, Process.pid)
+assert('Process::WNOHANG') do
+ assert_kind_of Integer, Process::WNOHANG
+end
+
+assert('Process::WUNTRACED') do
+ assert_kind_of Integer, Process::WUNTRACED
+end
+
+assert_not_windows('Process.argv0') do
+ assert_equal ENV['_'], Process.argv0
+end
+
+assert_windows('Process.argv0') do
+ assert_include Process.argv0, 'mrbtest.exe'
+end
+
+assert('$0') do
+ assert_raise(RuntimeError, 'Should be frozen') { $0.upcase! }
+ assert_not_include ['/', '\\'], $0
+end
+
+assert('$PROGRAM_NAME') do
+ assert_raise(RuntimeError, 'Should be frozen') { $PROGRAM_NAME.upcase! }
+ assert_equal $0, $PROGRAM_NAME
end
assert('Process.pid') do
- Process.pid is_a? Integer and Process.pid > 0
+ assert_kind_of Integer, Process.pid
+ assert_true Process.pid > 0
+end
+
+assert('$$') do
+ assert_equal Process.pid, $$
+end
+
+assert('$PID') do
+ assert_equal Process.pid, $PID
+end
+
+assert('$PROCESS_ID') do
+ assert_equal Process.pid, $PROCESS_ID
end
assert('Process.ppid') do
- Process.ppid is_a? Integer and Process.ppid > 0
+ assert_kind_of Integer, Process.pid
+ assert_true Process.pid > 0
end
-assert('$$') do
- $$ == Process.pid
+assert('Process.spawn') do
+ assert_raise(ArgumentError) { spawn }
+ assert_raise(TypeError) { spawn 123 }
+
+ pid = spawn 'exit 0'
+ wait_for_pid(pid)
+
+ assert_kind_of Integer, pid
+ assert_true pid > 0
+ assert_not_equal $PID, pid
+ assert_kind_of Process::Status, $?
+ assert_equal $?.pid, pid
+
+ var = "#{ENV['RAND']}x"
+ pid = spawn("echo #{var} > tmp/spawn.txt")
+
+ wait_for_pid(pid)
+ assert_equal var, read('tmp/spawn.txt')
+end
+
+assert('Process.spawn', 'env') do
+ var = "x#{ENV['RAND']}"
+ env = OS.posix? ? '$MYVAR' : '%MYVAR%'
+
+ pid = spawn({ MYVAR: var }, "echo #{env} > tmp/spawn.txt")
+
+ wait_for_pid(pid)
+ assert_equal var, read('tmp/spawn.txt')
+end
+
+assert('Process.spawn', 'pipe stdout') do
+ begin
+ var = ENV['RAND']
+ pip = IO.sysopen('tmp/pipe.txt', 'w')
+
+ pid = spawn("echo #{var}", out: pip)
+
+ wait_for_pid(pid)
+ assert_equal var, read('tmp/pipe.txt')
+
+ env = OS.posix? ? '$MYVAR' : '%MYVAR%'
+ pid = spawn({ MYVAR: var }, "echo #{env}", out: pip)
+
+ wait_for_pid(pid)
+ assert_equal var * 2, read('tmp/pipe.txt')
+
+ pid = spawn 'ruby', '-v', out: pip
+
+ wait_for_pid(pid)
+ assert_include read('tmp/pipe.txt'), 'ruby'
+ ensure
+ IO._sysclose(pip) if OS.posix?
+ end
+end
+
+assert('Process.spawn', 'pipe stderr') do
+ begin
+ pip = IO.sysopen('tmp/pipe.err', 'w')
+ pid = spawn('ruby unknown', err: pip)
+
+ wait_for_pid(pid)
+ assert_false read('tmp/pipe.err').empty?
+ ensure
+ IO._sysclose(pip) if OS.posix?
+ end
+end
+
+assert('Process.exec', 'invalid signatures') do
+ assert_raise(ArgumentError) { exec }
+ assert_raise(TypeError) { exec 123 }
end
-assert("Process.fork with WNOHANG") do
- pid = fork {
- loop {}
- }
- p, s = Process.waitpid2(pid, Process::WNOHANG)
- assert_nil(p)
- assert_nil(s)
+assert_not_windows('Process.exec') do
+ var = ENV['RAND']
+ pid = fork { exec({ MYVAR: var }, 'echo $MYVAR > tmp/exec.txt') }
+
+ wait_for_pid(pid)
+ assert_equal var, read('tmp/exec.txt')
+
+ var = "x#{var}"
+ pid = fork { exec '/bin/sh', '-c', "echo #{var} > tmp/exec.txt" }
+
+ wait_for_pid(pid)
+ assert_equal var, read('tmp/exec.txt')
+end
+
+assert_not_windows('Process.exec', '$SHELL') do
+ ['/bin/bash', '/bin/sh'].each do |shell|
+ ENV['SHELL'] = shell
+
+ pid = fork { exec 'echo $SHELL > tmp/exec.txt' }
+ wait_for_pid(pid)
+
+ assert_equal shell, read('tmp/exec.txt')
+ end
+end
+
+assert('Process.kill') do
+ assert_nothing_raised { Process.kill(:EXIT, Process.pid) }
+ assert_nothing_raised { Process.kill('EXIT', Process.pid) }
+ assert_nothing_raised { Process.kill(0, Process.pid) }
+ assert_equal 1, Process.kill(0, Process.pid), 'killed an innocent process'
+ assert_equal 2, Process.kill(0, Process.pid, Process.pid)
+ assert_raise(TypeError) { Process.kill(0.0, Process.pid) }
+ assert_raise(TypeError) { Process.kill(0, 'Process.pid') }
+ assert_raise(ArgumentError) { Process.kill(:UNKNOWN, Process.pid) }
+end
- Process.kill :TERM, pid
- loop {
- # wait until the process completely killed with non-block mode
- p, s = Process.waitpid2(pid, Process::WNOHANG)
+assert('Process.wait2') do
+ pid = spawn('sleep 2')
+ p, st = Process.waitpid2(pid, Process::WNOHANG)
+
+ assert_nil p
+ assert_nil st
+
+ Process.kill :KILL, pid
+
+ loop do
+ p, st = Process.waitpid2(pid, Process::WNOHANG)
break if p
- }
- assert_equal(pid, p)
- assert_true(s.signaled?)
+ end
+
+ assert_equal pid, p
+ assert_kind_of Process::Status, st
+ assert_include [9, nil], st.termsig
+end
+
+assert('Process.waitall') do
+ assert_true Process.waitall.empty?
+
+ pids = []
+ pids << spawn('exit 2')
+ pids << spawn('exit 1')
+ pids << spawn('exit 0')
+
+ a = Process.waitall
+
+ pids.each do |pid|
+ assert_raise(RuntimeError) { Process.kill(0, pid) }
+ end
+
+ assert_kind_of Array, a
+ assert_equal 3, a.size
+
+ pids.each do |pid|
+ pid_status = a.find { |i| i[0] == pid }
+
+ assert_kind_of Array, pid_status
+ assert_equal 2, pid_status.size
+ assert_equal pid, pid_status.first
+ assert_kind_of Process::Status, pid_status.last
+ end
+end
+
+assert('Process.system') do
+ assert_raise(ArgumentError) { system }
+ assert_raise(TypeError) { system 123 }
+
+ assert_true system 'exit 0'
+ assert_equal 0, $?.exitstatus
+ assert_false system 'exit 1'
+ assert_equal 1, $?.exitstatus
+
+ assert_nothing_raised { system 'exit' }
+
+ var = ENV['RAND']
+ env = OS.posix? ? '$MYVAR' : '%MYVAR%'
+
+ system({ MYVAR: var }, "echo #{env} > tmp/system.txt")
+
+ assert_equal var, read('tmp/system.txt')
+end
+
+assert_windows('Process.fork') do
+ assert_false Process.respond_to? :fork
+ assert_false Kernel.respond_to? :fork
end
diff --git a/test/process_test.c b/test/process_test.c
new file mode 100644
index 0000000..37b057d
--- /dev/null
+++ b/test/process_test.c
@@ -0,0 +1,54 @@
+/* MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include "mruby.h"
+
+#if !defined(__APPLE__) && !defined(__linux__)
+
+#include "mruby/string.h"
+#include
+#include
+
+static mrb_value
+mrb_process_mock_sysopen(mrb_state *mrb, mrb_value self)
+{
+ mrb_int file_len, mod_len;
+ char *file, *mod;
+ HANDLE handle;
+
+ mrb_get_args(mrb, "ss", &file, &file_len, &mod, &mod_len);
+
+ handle = (HANDLE) _get_osfhandle(fileno(fopen(file, mod)));
+
+ return mrb_cptr_value(mrb, handle);
+}
+
+#endif
+
+
+void
+mrb_mruby_process_gem_test(mrb_state* mrb)
+{
+#if !defined(__APPLE__) && !defined(__linux__)
+ struct RClass *pt = mrb_define_module(mrb, "ProcessTest");
+ mrb_define_class_method(mrb, pt, "sysopen", mrb_process_mock_sysopen, MRB_ARGS_REQ(2));
+#endif
+}
diff --git a/test/signal.rb b/test/signal.rb
new file mode 100644
index 0000000..ceba3ca
--- /dev/null
+++ b/test/signal.rb
@@ -0,0 +1,35 @@
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+assert('Signal') do
+ assert_kind_of Module, Signal
+end
+
+assert('Signal.signame') do
+ assert_equal Signal.signame(2), 'INT'
+ assert_equal Signal.signame(9), 'KILL'
+ assert_equal Signal.signame(0), 'EXIT'
+ assert_nil Signal.signame(-1)
+end
+
+assert('Signal.list') do
+ assert_kind_of Hash, Signal.list
+ assert_true Signal.list.any?, 'no signals found'
+end
diff --git a/test/status.rb b/test/status.rb
index 989b095..2aef4ee 100644
--- a/test/status.rb
+++ b/test/status.rb
@@ -1,60 +1,113 @@
-def process_status_fork(exitcode=0)
- pid = fork
- unless pid
- exit! exitcode
- end
- pid
+# MIT License
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+def assert_not_windows(*args, &block)
+ assert(*args, &block) if OS.posix?
end
-assert('Process::Status#==') do
- pid1 = process_status_fork 123
- pid2 = process_status_fork 123
- _, st1 = Process.waitpid2(pid1)
- _, st2 = Process.waitpid2(pid2)
- assert_true st1, st2
+def spawn_and_wait(cmd = 'exit 0')
+ Process.waitpid2(spawn(cmd))
end
-# assert('Process::Status#coredump?') do
+assert('$?') do
+ _, st = spawn_and_wait
-assert('Process::Status#exited? and #exitstatus') do
- pid = process_status_fork 42
- _, st = Process.waitpid2(pid)
- assert_true st.exited?
- assert_equal 42, st.exitstatus
+ assert_kind_of Process::Status, $?
+ assert_equal st, $?
end
-# assert('Process::Status#inspect') do
+assert('$CHILD_STATUS') do
+ spawn_and_wait
+
+ assert_kind_of Process::Status, $CHILD_STATUS
+ assert_equal $?, $CHILD_STATUS
+end
assert('Process::Status#pid') do
- pid = process_status_fork
- _, st = Process.waitpid2(pid)
+ pid, st = spawn_and_wait
+
assert_equal pid, st.pid
end
-assert('Process::Status#signaled? and #termsig') do
+assert('Process::Status#==') do
+ _, st1 = spawn_and_wait
+ _, st2 = spawn_and_wait
+
+ assert_equal st1, st2
+end
+
+assert('Process::Status#>>') do
+ spawn_and_wait 'exit 99'
+
+ assert_equal 99, $? >> 8
+end
+
+# assert('Process::Status#coredump?')
+
+assert('Process::Status#exited?') do
+ spawn_and_wait
+
+ assert_true $?.exited?
+end
+
+assert('Process::Status#exitstatus') do
+ spawn_and_wait 'exit 42'
+
+ assert_equal 42, $?.exitstatus
+end
+
+assert_not_windows('Process::Status#signaled? and #termsig') do
pid = fork
- unless pid
- sleep 10
- end
+
+ sleep 10 unless pid
Process.kill 15, pid
+
_, st = Process.waitpid2(pid)
+
assert_true st.signaled?
assert_equal 15, st.termsig
end
-# assert('Process::Status#stopped and #stopsig') do
+# assert('Process::Status#stopped and #stopsig')
assert('Process::Status#success?') do
- pid = process_status_fork 42
- _, st = Process.waitpid2(pid)
- assert_true st.exited?
- assert_equal 42, st.exitstatus
+ spawn_and_wait 'exit 42'
+
+ assert_true $?.exited?
+ assert_equal 42, $?.exitstatus
end
-assert('Process::Status#to_i, to_int, to_s') do
- pid = process_status_fork 123
- _, st = Process.waitpid2(pid)
- assert_true st.to_i.is_a? Fixnum
- assert_true st.to_int.is_a? Fixnum
- assert_equal st.to_i.to_s, st.to_s
+assert('Process::Status#to_i') do
+ assert_kind_of Integer, $?.to_i
+ assert_kind_of Integer, $?.to_int
+ assert_equal $?.to_i, $?.to_int
+end
+
+assert('Process::Status#to_s') do
+ pid, = spawn_and_wait
+
+ assert_equal "pid #{pid} exit 0", $?.to_s
+end
+
+assert('Process::Status#inspect') do
+ pid, = spawn_and_wait
+
+ assert_equal "#", $?.inspect
end