lib/bio/command.rb in bio-1.4.0 vs lib/bio/command.rb in bio-1.4.1
- old
+ new
@@ -1,9 +1,9 @@
#
# = bio/command.rb - general methods for external command execution
#
-# Copyright:: Copyright (C) 2003-2008
+# Copyright:: Copyright (C) 2003-2010
# Naohisa Goto <ng@bioruby.org>,
# Toshiaki Katayama <k@bioruby.org>
# License:: The Ruby License
#
# $Id:$
@@ -32,10 +32,63 @@
QUOTE_CHARS_WINDOWS = /[^A-Za-z0-9\_\-\.\:\,\/\@\\]/n
UNESCAPABLE_CHARS = /[\x00-\x08\x10-\x1a\x1c-\x1f\x7f\xff]/n
module_function
+ # *CAUTION* Bio::Command INTERNAL USE ONLY.
+ # Users must NOT use the method.
+ # The method will be removed when it is not needed.
+ #
+ # Checks if the program is running on Microsoft Windows.
+ # If Windows, returns true. Otherwise, returns false.
+ # Note that Cygwin is not treated as Windows.
+ #
+ # Known issues:
+ # * It might make a mistake in minor platforms/architectures/interpreters.
+ # * When running JRuby on Cygwin, the result is unknown.
+ # ---
+ # *Returns*:: true or false
+ def windows_platform?
+ case RUBY_PLATFORM
+ when /(?:mswin|bccwin|mingw)(?:32|64)/i
+ true
+ when /java/i
+ # Reference: Redmine's platform.rb
+ # http://www.redmine.org/projects/redmine/repository/revisions/1753/entry/trunk/lib/redmine/platform.rb
+ if /windows/i =~ (ENV['OS'] || ENV['os']).to_s then
+ true
+ else
+ false
+ end
+ else
+ false
+ end
+ end
+ private_class_method :windows_platform?
+
+ # *CAUTION* Bio::Command INTERNAL USE ONLY.
+ # Users must NOT use the method.
+ # The method will be removed when it is not needed.
+ #
+ # Checks if the OS does not support fork(2) system call.
+ # When not supported, it returns true.
+ # When supported or unknown, it returns false or nil.
+ #
+ # Known issues:
+ # * It might make a mistake in minor platforms/architectures/interpreters.
+ # ---
+ # *Returns*:: true, false or nil.
+ def no_fork?
+ if (defined?(@@no_fork) && @@no_fork) or
+ windows_platform? or /java/i =~ RUBY_PLATFORM then
+ true
+ else
+ false
+ end
+ end
+ private_class_method :no_fork?
+
# Escape special characters in command line string for cmd.exe on Windows.
# ---
# *Arguments*:
# * (required) _str_: String
# *Returns*:: String object
@@ -64,12 +117,11 @@
# ---
# *Arguments*:
# * (required) _str_: String
# *Returns*:: String object
def escape_shell(str)
- case RUBY_PLATFORM
- when /mswin32|bccwin32/
+ if windows_platform? then
escape_shell_windows(str)
else
escape_shell_unix(str)
end
end
@@ -78,12 +130,11 @@
# ---
# *Arguments*:
# * (required) _ary_: Array containing String objects
# *Returns*:: String object
def make_command_line(ary)
- case RUBY_PLATFORM
- when /mswin32|bccwin32/
+ if windows_platform? then
make_command_line_windows(ary)
else
make_command_line_unix(ary)
end
end
@@ -128,12 +179,12 @@
arg0 = [ arg0, arg0 ]
end
[ arg0 ]
end
- # Executes the program. Automatically select popen for Windows
- # environment and fork for the others.
+ # Executes the program. Automatically select popen for Ruby 1.9 or
+ # Windows environment and fork for the others.
# A block must be given. An IO object is passed to the block.
#
# Available options:
# :chdir => "path" : changes working directory to the specified path.
#
@@ -141,31 +192,66 @@
# *Arguments*:
# * (required) _cmd_: Array containing String objects
# * (optional) _options_: Hash
# *Returns*:: (undefined)
def call_command(cmd, options = {}, &block) #:yields: io
- case RUBY_PLATFORM
- when /mswin32|bccwin32/
+ if RUBY_VERSION >= "1.9.0" then
+ return call_command_popen(cmd, options, &block)
+ elsif no_fork? then
call_command_popen(cmd, options, &block)
else
- call_command_fork(cmd, options, &block)
+ begin
+ call_command_fork(cmd, options, &block)
+ rescue NotImplementedError
+ # fork(2) not implemented
+ @@no_fork = true
+ call_command_popen(cmd, options, &block)
+ end
end
end
+ # This method is internally called from the call_command method.
+ # In normal case, use call_command, and do not call this method directly.
+ #
# Executes the program via IO.popen for OS which doesn't support fork.
# A block must be given. An IO object is passed to the block.
+ #
+ # See the document of call_command for available options.
+ #
+ # Note for Ruby 1.8:
+ # In Ruby 1.8, although shell unsafe characters are escaped.
+ # If inescapable characters exists, it raises RuntimeError.
+ # So, call_command_fork is normally recommended.
+ #
+ # Note for Ruby 1.9:
+ # In Ruby 1.9, call_command_popen is safe and robust enough, and is the
+ # recommended way, because IO.popen is improved to get a command-line
+ # as an array without calling shell.
+ #
# ---
# *Arguments*:
# * (required) _cmd_: Array containing String objects
# * (optional) _options_: Hash
# *Returns*:: (undefined)
def call_command_popen(cmd, options = {})
+ if RUBY_VERSION >= "1.9.0" then
+ # For Ruby 1.9 or later, using command line array with options.
+ dir = options[:chdir]
+ cmd = safe_command_line_array(cmd)
+ if dir then
+ cmd = cmd + [ { :chdir => dir } ]
+ end
+ r = IO.popen(cmd, "r+") do |io|
+ yield io
+ end
+ return r
+ end
+ # For Ruby 1.8, using command line string.
str = make_command_line(cmd)
# processing options
if dir = options[:chdir] then
- case RUBY_PLATFORM
- when /mswin32|bccwin32/
+ if windows_platform?
# Unix-like dir separator is changed to Windows dir separator
# by using String#gsub.
dirstr = dir.gsub(/\//, "\\")
chdirstr = make_command_line([ 'cd', '/D', dirstr ])
str = chdirstr + ' && ' + str
@@ -180,30 +266,48 @@
io.sync = true
yield io
end
end
+ # This method is internally called from the call_command method.
+ # In normal case, use call_command, and do not call this method directly.
+ #
# Executes the program via fork (by using IO.popen("-")) and exec.
# A block must be given. An IO object is passed to the block.
#
- # From the view point of security, this method is recommended
- # rather than call_command_popen.
+ # See the document of call_command for available options.
#
+ # Note for Ruby 1.8:
+ # In Ruby 1.8, from the view point of security, this method is recommended
+ # rather than call_command_popen. However, this method might have problems
+ # with multi-threads.
+ #
+ # Note for Ruby 1.9:
+ # In Ruby 1.9, this method can not be used, because Thread.critical is
+ # removed. In Ruby 1.9, call_command_popen is safe and robust enough, and
+ # is the recommended way, because IO.popen is improved to get a
+ # command-line as an array without calling shell.
+ #
# ---
# *Arguments*:
# * (required) _cmd_: Array containing String objects
# * (optional) _options_: Hash
# *Returns*:: (undefined)
def call_command_fork(cmd, options = {})
dir = options[:chdir]
cmd = safe_command_line_array(cmd)
+ begin
+ tc, Thread.critical, flag0, flag1 = Thread.critical, true, true, true
IO.popen("-", "r+") do |io|
if io then
# parent
+ flag0, Thread.critical, flag1 = false, tc, false
yield io
else
# child
+ Thread.critical = true # for safety, though already true
+ GC.disable
# chdir to options[:chdir] if available
begin
Dir.chdir(dir) if dir
rescue Exception
Process.exit!(1)
@@ -216,10 +320,15 @@
rescue Exception
end
Process.exit!(1)
end
end
+ ensure
+ # When IO.popen("-") raises error, Thread.critical will be set here.
+ Thread.critical = tc if flag0 or flag1
+ #warn 'Thread.critical might have wrong value.' if flag0 != flag1
+ end
end
# Executes the program via Open3.popen3
# A block must be given. IO objects are passed to the block.
#
@@ -238,11 +347,12 @@
# Executes the program with the query (String) given to the standard input,
# waits the program termination, and returns the output data printed to the
# standard output as a string.
#
- # Automatically select popen for Windows environment and fork for the others.
+ # Automatically select popen for Ruby 1.9 or Windows environment and
+ # fork for the others.
#
# Available options:
# :chdir => "path" : changes working directory to the specified path.
#
# ---
@@ -250,24 +360,37 @@
# * (required) _cmd_: Array containing String objects
# * (optional) _query_: String
# * (optional) _options_: Hash
# *Returns*:: String or nil
def query_command(cmd, query = nil, options = {})
- case RUBY_PLATFORM
- when /mswin32|bccwin32/
+ if RUBY_VERSION >= "1.9.0" then
+ return query_command_popen(cmd, query, options)
+ elsif no_fork? then
query_command_popen(cmd, query, options)
else
- query_command_fork(cmd, query, options)
+ begin
+ query_command_fork(cmd, query, options)
+ rescue NotImplementedError
+ # fork(2) not implemented
+ @@no_fork = true
+ query_command_fork(cmd, query, options)
+ end
end
end
+ # This method is internally called from the query_command method.
+ # In normal case, use query_command, and do not call this method directly.
+ #
# Executes the program with the query (String) given to the standard input,
# waits the program termination, and returns the output data printed to the
# standard output as a string.
#
- # IO.popen is used for OS which doesn't support fork.
+ # See the document of query_command for available options.
#
+ # See the document of call_command_popen for the security and Ruby
+ # version specific issues.
+ #
# ---
# *Arguments*:
# * (required) _cmd_: Array containing String objects
# * (optional) _query_: String
# * (optional) _options_: Hash
@@ -281,17 +404,22 @@
ret = io.read
end
ret
end
+ # This method is internally called from the query_command method.
+ # In normal case, use query_command, and do not call this method directly.
+ #
# Executes the program with the query (String) given to the standard input,
# waits the program termination, and returns the output data printed to the
# standard output as a string.
#
# Fork (by using IO.popen("-")) and exec is used to execute the program.
#
- # From the view point of security, this method is recommended
- # rather than query_command_popen.
+ # See the document of query_command for available options.
+ #
+ # See the document of call_command_fork for the security and Ruby
+ # version specific issues.
#
# ---
# *Arguments*:
# * (required) _cmd_: Array containing String objects
# * (optional) _query_: String