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 -========= -[![Build Status](https://travis-ci.org/iij/mruby-process.svg?branch=master)](https://travis-ci.org/iij/mruby-process) +# mruby-process
[![Build Status](https://travis-ci.org/appPlant/mruby-process.svg?branch=windows)](https://travis-ci.org/appPlant/mruby-process) [![Build status](https://ci.appveyor.com/api/projects/status/1uu04u7wtbup1oqk/branch/windows?svg=true)](https://ci.appveyor.com/project/katzer/mruby-process/branch/windows) [![codebeat badge](https://codebeat.co/badges/02e9d761-e8b6-4939-9ef2-b88fd0c93a84)](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