Current File : //opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/util/windows/monkey_patches/process.rb |
# frozen_string_literal: true
require 'ffi'
require_relative '../../../../puppet/ffi/windows'
require_relative '../../../../puppet/util/windows/string'
module Process
extend FFI::Library
extend Puppet::Util::Windows::String
extend Puppet::FFI::Windows::APITypes
extend Puppet::FFI::Windows::Functions
extend Puppet::FFI::Windows::Structs
include Puppet::FFI::Windows::Constants
include Puppet::FFI::Windows::Structs
ProcessInfo = Struct.new(
'ProcessInfo',
:process_handle,
:thread_handle,
:process_id,
:thread_id
)
private_constant :ProcessInfo
# Disable popups. This mostly affects the Process.kill method.
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX)
class << self
private :SetHandleInformation, :SetErrorMode, :CreateProcessW, :OpenProcess,
:SetPriorityClass, :CreateProcessWithLogonW, :get_osfhandle, :get_errno
# Process.create(key => value, ...) => ProcessInfo
#
# This is a wrapper for the CreateProcess() function. It executes a process,
# returning a ProcessInfo struct. It accepts a hash as an argument.
# There are several primary keys:
#
# * command_line (this or app_name must be present)
# * app_name (default: nil)
# * inherit (default: false)
# * process_inherit (default: false)
# * thread_inherit (default: false)
# * creation_flags (default: 0)
# * cwd (default: Dir.pwd)
# * startup_info (default: nil)
# * environment (default: nil)
# * close_handles (default: true)
# * with_logon (default: nil)
# * domain (default: nil)
# * password (default: nil, mandatory if with_logon)
#
# Of these, the 'command_line' or 'app_name' must be specified or an
# error is raised. Both may be set individually, but 'command_line' should
# be preferred if only one of them is set because it does not (necessarily)
# require an explicit path or extension to work.
#
# The 'domain' and 'password' options are only relevent in the context
# of 'with_logon'. If 'with_logon' is set, then the 'password' option is
# mandatory.
#
# The startup_info key takes a hash. Its keys are attributes that are
# part of the StartupInfo struct, and are generally only meaningful for
# GUI or console processes. See the documentation on CreateProcess()
# and the StartupInfo struct on MSDN for more information.
#
# * desktop
# * title
# * x
# * y
# * x_size
# * y_size
# * x_count_chars
# * y_count_chars
# * fill_attribute
# * sw_flags
# * startf_flags
# * stdin
# * stdout
# * stderr
#
# Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
# IO objects or file descriptors (i.e. a fileno). However, StringIO objects
# are not currently supported. Unfortunately, setting these is not currently
# an option for JRuby.
#
# If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
# is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
# automatically OR'd to the +startf_flags+ value.
#
# The ProcessInfo struct contains the following members:
#
# * process_handle - The handle to the newly created process.
# * thread_handle - The handle to the primary thread of the process.
# * process_id - Process ID.
# * thread_id - Thread ID.
#
# If the 'close_handles' option is set to true (the default) then the
# process_handle and the thread_handle are automatically closed for you
# before the ProcessInfo struct is returned.
#
# If the 'with_logon' option is set, then the process runs the specified
# executable file in the security context of the specified credentials.
VALID_KEYS = %i[
app_name command_line inherit creation_flags cwd environment
startup_info thread_inherit process_inherit close_handles with_logon
domain password
].freeze
VALID_SI_KEYS = %i[
startf_flags desktop title x y x_size y_size x_count_chars
y_count_chars fill_attribute sw_flags stdin stdout stderr
].freeze
private_constant :VALID_KEYS, :VALID_SI_KEYS
def create(args)
# Validate that args is a Hash
validate_args(args)
initialize_defaults
# Validate the keys, and convert symbols and case to lowercase strings.
validate_keys(args)
# If the startup_info key is present, validate its subkeys
validate_startup_info if hash[:startup_info]
# validates that 'app_name' or 'command_line' is set
validate_command_line
if hash[:app_name] && !hash[:command_line]
hash[:command_line] = hash[:app_name]
hash[:app_name] = nil
end
# Setup stdin, stdout and stderr handlers
setup_std_handlers
if logon
create_process_with_logon
else
create_process
end
# Automatically close the process and thread handles in the
# PROCESS_INFORMATION struct unless explicitly told not to.
if hash[:close_handles]
FFI::WIN32.CloseHandle(procinfo[:hProcess])
FFI::WIN32.CloseHandle(procinfo[:hThread])
end
ProcessInfo.new(
procinfo[:hProcess],
procinfo[:hThread],
procinfo[:dwProcessId],
procinfo[:dwThreadId]
)
end
remove_method :setpriority
# Sets the priority class for the specified process id +int+.
#
# The +kind+ parameter is ignored but present for API compatibility.
# You can only retrieve process information, not process group or user
# information, so it is effectively always Process::PRIO_PROCESS.
#
# Possible +int_priority+ values are:
#
# * Process::NORMAL_PRIORITY_CLASS
# * Process::IDLE_PRIORITY_CLASS
# * Process::HIGH_PRIORITY_CLASS
# * Process::REALTIME_PRIORITY_CLASS
# * Process::BELOW_NORMAL_PRIORITY_CLASS
# * Process::ABOVE_NORMAL_PRIORITY_CLASS
def setpriority(kind, int, int_priority)
raise TypeError unless kind.is_a?(Integer)
raise TypeError unless int.is_a?(Integer)
raise TypeError unless int_priority.is_a?(Integer)
int = Process.pid if int == 0
handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)
if handle == 0
raise SystemCallError, FFI.errno, "OpenProcess"
end
begin
result = SetPriorityClass(handle, int_priority)
raise SystemCallError, FFI.errno, "SetPriorityClass" unless result
ensure
FFI::WIN32.CloseHandle(handle)
end
return 0
end
private
def initialize_defaults
@hash = {
app_name: nil,
creation_flags: 0,
close_handles: true
}
@si_hash = nil
@procinfo = nil
end
def validate_args(args)
raise TypeError, 'hash keyword arguments expected' unless args.is_a?(Hash)
end
def validate_keys(args)
args.each do |key, val|
key = key.to_s.to_sym
raise ArgumentError, "invalid key '#{key}'" unless VALID_KEYS.include?(key)
hash[key] = val
end
end
def validate_startup_info
hash[:startup_info].each do |key, val|
key = key.to_s.to_sym
raise ArgumentError, "invalid startup_info key '#{key}'" unless VALID_SI_KEYS.include?(key)
si_hash[key] = val
end
end
def validate_command_line
raise ArgumentError, 'command_line or app_name must be specified' unless hash[:app_name] || hash[:command_line]
end
def procinfo
@procinfo ||= PROCESS_INFORMATION.new
end
def hash
@hash ||= {}
end
def si_hash
@si_hash ||= {}
end
def app
wide_string(hash[:app_name])
end
def cmd
wide_string(hash[:command_line])
end
def cwd
wide_string(hash[:cwd])
end
def password
wide_string(hash[:password])
end
def logon
wide_string(hash[:with_logon])
end
def domain
wide_string(hash[:domain])
end
def env
env = hash[:environment]
return unless env
env = env.split(File::PATH_SEPARATOR) unless env.respond_to?(:join)
env = env.map { |e| e + 0.chr }.join('') + 0.chr
env = wide_string(env) if hash[:with_logon]
env
end
def process_security
return unless hash[:process_inherit]
process_security = SECURITY_ATTRIBUTES.new
process_security[:nLength] = SECURITY_ATTRIBUTES.size
process_security[:bInheritHandle] = 1
process_security
end
def thread_security
return unless hash[:thread_inherit]
thread_security = SECURITY_ATTRIBUTES.new
thread_security[:nLength] = SECURITY_ATTRIBUTES.size
thread_security[:bInheritHandle] = 1
thread_security
end
# Automatically handle stdin, stdout and stderr as either IO objects
# or file descriptors. This won't work for StringIO, however. It also
# will not work on JRuby because of the way it handles internal file
# descriptors.
def setup_std_handlers
%i[stdin stdout stderr].each do |io|
next unless si_hash[io]
handle = if si_hash[io].respond_to?(:fileno)
get_osfhandle(si_hash[io].fileno)
else
get_osfhandle(si_hash[io])
end
if handle == INVALID_HANDLE_VALUE
ptr = FFI::MemoryPointer.new(:int)
errno = if get_errno(ptr).zero?
ptr.read_int
else
FFI.errno
end
raise SystemCallError.new('get_osfhandle', errno)
end
# Most implementations of Ruby on Windows create inheritable
# handles by default, but some do not. RF bug #26988.
bool = SetHandleInformation(
handle,
HANDLE_FLAG_INHERIT,
HANDLE_FLAG_INHERIT
)
raise SystemCallError.new('SetHandleInformation', FFI.errno) unless bool
si_hash[io] = handle
si_hash[:startf_flags] ||= 0
si_hash[:startf_flags] |= STARTF_USESTDHANDLES
hash[:inherit] = true
end
end
def startinfo
startinfo = STARTUPINFO.new
return startinfo if si_hash.empty?
startinfo[:cb] = startinfo.size
startinfo[:lpDesktop] = si_hash[:desktop] if si_hash[:desktop]
startinfo[:lpTitle] = si_hash[:title] if si_hash[:title]
startinfo[:dwX] = si_hash[:x] if si_hash[:x]
startinfo[:dwY] = si_hash[:y] if si_hash[:y]
startinfo[:dwXSize] = si_hash[:x_size] if si_hash[:x_size]
startinfo[:dwYSize] = si_hash[:y_size] if si_hash[:y_size]
startinfo[:dwXCountChars] = si_hash[:x_count_chars] if si_hash[:x_count_chars]
startinfo[:dwYCountChars] = si_hash[:y_count_chars] if si_hash[:y_count_chars]
startinfo[:dwFillAttribute] = si_hash[:fill_attribute] if si_hash[:fill_attribute]
startinfo[:dwFlags] = si_hash[:startf_flags] if si_hash[:startf_flags]
startinfo[:wShowWindow] = si_hash[:sw_flags] if si_hash[:sw_flags]
startinfo[:cbReserved2] = 0
startinfo[:hStdInput] = si_hash[:stdin] if si_hash[:stdin]
startinfo[:hStdOutput] = si_hash[:stdout] if si_hash[:stdout]
startinfo[:hStdError] = si_hash[:stderr] if si_hash[:stderr]
startinfo
end
def create_process_with_logon
raise ArgumentError, 'password must be specified if with_logon is used' unless password
hash[:creation_flags] |= CREATE_UNICODE_ENVIRONMENT
bool = CreateProcessWithLogonW(
logon, # User
domain, # Domain
password, # Password
LOGON_WITH_PROFILE, # Logon flags
app, # App name
cmd, # Command line
hash[:creation_flags], # Creation flags
env, # Environment
cwd, # Working directory
startinfo, # Startup Info
procinfo # Process Info
)
raise SystemCallError.new('CreateProcessWithLogonW', FFI.errno) unless bool
end
def create_process
inherit = hash[:inherit] ? 1 : 0
bool = CreateProcessW(
app, # App name
cmd, # Command line
process_security, # Process attributes
thread_security, # Thread attributes
inherit, # Inherit handles?
hash[:creation_flags], # Creation flags
env, # Environment
cwd, # Working directory
startinfo, # Startup Info
procinfo # Process Info
)
raise SystemCallError.new('CreateProcess', FFI.errno) unless bool
end
end
end