diff options
author | Salim Alam <salam@chef.io> | 2015-08-13 14:25:48 -0700 |
---|---|---|
committer | Salim Alam <salam@chef.io> | 2015-08-19 11:08:17 -0700 |
commit | 6c4cd5c6d5c40baca723bf95cdbccdda59021198 (patch) | |
tree | f2d6357705440c11467b46c60eac0b4c5ddb871e | |
parent | fe6676bab51390429674b0ecd4670924ffb09cc5 (diff) | |
download | chef-6c4cd5c6d5c40baca723bf95cdbccdda59021198.tar.gz |
Enable 64-bit support for Powershell and Batch scripts
-rw-r--r-- | lib/chef/mixin/windows_architecture_helper.rb | 54 | ||||
-rw-r--r-- | lib/chef/provider/batch.rb | 8 | ||||
-rw-r--r-- | lib/chef/provider/powershell_script.rb | 9 | ||||
-rw-r--r-- | lib/chef/provider/windows_script.rb | 8 | ||||
-rw-r--r-- | lib/chef/win32/api/system.rb | 23 | ||||
-rw-r--r-- | lib/chef/win32/process.rb | 13 | ||||
-rwxr-xr-x | lib/chef/win32/system.rb | 62 | ||||
-rw-r--r-- | spec/functional/resource/powershell_script_spec.rb | 26 | ||||
-rw-r--r-- | spec/spec_helper.rb | 2 | ||||
-rw-r--r-- | spec/support/platform_helpers.rb | 8 | ||||
-rw-r--r-- | spec/unit/mixin/windows_architecture_helper_spec.rb | 21 |
11 files changed, 175 insertions, 59 deletions
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb index c5f3e1bd79..744001f8a2 100644 --- a/lib/chef/mixin/windows_architecture_helper.rb +++ b/lib/chef/mixin/windows_architecture_helper.rb @@ -19,19 +19,13 @@ require 'chef/exceptions' require 'chef/platform/query_helpers' -require 'win32/api' if Chef::Platform.windows? -require 'chef/win32/api/process' if Chef::Platform.windows? -require 'chef/win32/api/error' if Chef::Platform.windows? +require 'chef/win32/process' if Chef::Platform.windows? +require 'chef/win32/system' if Chef::Platform.windows? class Chef module Mixin module WindowsArchitectureHelper - if Chef::Platform.windows? - include Chef::ReservedNames::Win32::API::Process - include Chef::ReservedNames::Win32::API::Error - end - def node_windows_architecture(node) node[:kernel][:machine].to_sym end @@ -42,6 +36,16 @@ class Chef is_i386_process_on_x86_64_windows? end + def forced_32bit_override_required?(node, desired_architecture) + desired_architecture == :i386 && + node_windows_architecture(node) == :x86_64 && + !is_i386_process_on_x86_64_windows? + end + + def wow64_directory + Chef::ReservedNames::Win32::System.get_system_wow64_directory + end + def with_os_architecture(node, architecture: nil) node ||= begin os_arch = ENV['PROCESSOR_ARCHITEW6432'] || @@ -88,49 +92,21 @@ class Chef def is_i386_process_on_x86_64_windows? if Chef::Platform.windows? - is_64_bit_process_result = FFI::MemoryPointer.new(:int) - - # The return value of IsWow64Process is nonzero value if the API call succeeds. - # The result data are returned in the last parameter, not the return value. - call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result) - - # The result is nonzero if IsWow64Process's calling process, in the case here - # this process, is running under WOW64, i.e. the result is nonzero if this - # process is 32-bit (aka :i386). - result = (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0) + Chef::ReservedNames::Win32::Process.is_wow64_process else false end end def disable_wow64_file_redirection( node ) - original_redirection_state = ['0'].pack('P') - if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?) - win32_wow_64_disable_wow_64_fs_redirection = - ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32') - - succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state) - - if succeeded == 0 - raise Win32APIError "Failed to disable Wow64 file redirection" - end - + Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection end - - original_redirection_state end def restore_wow64_file_redirection( node, original_redirection_state ) if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?) - win32_wow_64_revert_wow_64_fs_redirection = - ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32') - - succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state) - - if succeeded == 0 - raise Win32APIError "Failed to revert Wow64 file redirection" - end + Chef::ReservedNames::Win32::System.wow64_revert_wow64_fs_redirection(original_redirection_state) end end diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb index b6b386e5a8..5f0134443d 100644 --- a/lib/chef/provider/batch.rb +++ b/lib/chef/provider/batch.rb @@ -28,6 +28,14 @@ class Chef super(new_resource, run_context, '.bat') end + def command + basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory + + interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter) + + "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\"" + end + def flags @new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c' end diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb index 3c52db1f33..004b182769 100644 --- a/lib/chef/provider/powershell_script.rb +++ b/lib/chef/provider/powershell_script.rb @@ -34,6 +34,15 @@ class Chef super end + def command + basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory + + # Powershell.exe is always in "v1.0" folder (for backwards compatibility) + interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter) + + "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\"" + end + def flags # Must use -File rather than -Command to launch the script # file created by the base class that contains the script diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb index e600bb2837..62b49bd833 100644 --- a/lib/chef/provider/windows_script.rb +++ b/lib/chef/provider/windows_script.rb @@ -23,6 +23,8 @@ class Chef class Provider class WindowsScript < Chef::Provider::Script + attr_reader :is_forced_32bit + protected include Chef::Mixin::WindowsArchitectureHelper @@ -36,11 +38,7 @@ class Chef @is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture) - # if the user wants to run the script 32 bit && we are on a 64bit windows system && we are running a 64bit ruby ==> fail - if ( target_architecture == :i386 ) && node_windows_architecture(run_context.node) == :x86_64 && !is_i386_process_on_x86_64_windows? - raise Chef::Exceptions::Win32ArchitectureIncorrect, - "Support for the i386 architecture from a 64-bit Ruby runtime is not yet implemented" - end + @is_forced_32bit = forced_32bit_override_required?(run_context.node, target_architecture) end public diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb index d57699acb4..a485f89708 100644 --- a/lib/chef/win32/api/system.rb +++ b/lib/chef/win32/api/system.rb @@ -187,6 +187,29 @@ int WINAPI GetSystemMetrics( safe_attach_function :GetSystemMetrics, [:int], :int =begin +UINT WINAPI GetSystemWow64Directory( + _Out_ LPTSTR lpBuffer, + _In_ UINT uSize +); +=end + safe_attach_function :GetSystemWow64DirectoryW, [:LPTSTR, :UINT], :UINT + safe_attach_function :GetSystemWow64DirectoryA, [:LPTSTR, :UINT], :UINT + +=begin +BOOL WINAPI Wow64DisableWow64FsRedirection( + _Out_ PVOID *OldValue +); +=end + safe_attach_function :Wow64DisableWow64FsRedirection, [:PVOID], :BOOL + +=begin +BOOL WINAPI Wow64RevertWow64FsRedirection( + _In_ PVOID OldValue +); +=end + safe_attach_function :Wow64RevertWow64FsRedirection, [:PVOID], :BOOL + +=begin LRESULT WINAPI SendMessageTimeout( _In_ HWND hWnd, _In_ UINT Msg, diff --git a/lib/chef/win32/process.rb b/lib/chef/win32/process.rb index 2df39bb918..767d4f390c 100644 --- a/lib/chef/win32/process.rb +++ b/lib/chef/win32/process.rb @@ -69,6 +69,19 @@ class Chef result end + def self.is_wow64_process + is_64_bit_process_result = FFI::MemoryPointer.new(:int) + + # The return value of IsWow64Process is nonzero value if the API call succeeds. + # The result data are returned in the last parameter, not the return value. + call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result) + + # The result is nonzero if IsWow64Process's calling process, in the case here + # this process, is running under WOW64, i.e. the result is nonzero if this + # process is 32-bit (aka :i386). + (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0) + end + # Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights, # AND the PROCESS_VM_READ right def self.get_process_memory_info(handle) diff --git a/lib/chef/win32/system.rb b/lib/chef/win32/system.rb new file mode 100755 index 0000000000..cdd063f174 --- /dev/null +++ b/lib/chef/win32/system.rb @@ -0,0 +1,62 @@ +# +# Author:: Salim Alam (<salam@chef.io>) +# Copyright:: Copyright 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/win32/api/system' +require 'chef/win32/error' +require 'ffi' + +class Chef + module ReservedNames::Win32 + class System + include Chef::ReservedNames::Win32::API::System + extend Chef::ReservedNames::Win32::API::System + + def self.get_system_wow64_directory + ptr = FFI::MemoryPointer.new(:char, 255, true) + succeeded = GetSystemWow64DirectoryA(ptr, 255) + + if succeeded == 0 + raise Win32APIError, "Failed to get Wow64 system directory" + end + + ptr.read_string.strip + end + + def self.wow64_disable_wow64_fs_redirection + original_redirection_state = FFI::MemoryPointer.new(:pointer) + + succeeded = Wow64DisableWow64FsRedirection(original_redirection_state) + + if succeeded == 0 + raise Win32APIError, "Failed to disable Wow64 file redirection" + end + + original_redirection_state + end + + def self.wow64_revert_wow64_fs_redirection(original_redirection_state) + succeeded = Wow64RevertWow64FsRedirection(original_redirection_state) + + if succeeded == 0 + raise Win32APIError, "Failed to revert Wow64 file redirection" + end + end + + end + end +end diff --git a/spec/functional/resource/powershell_script_spec.rb b/spec/functional/resource/powershell_script_spec.rb index a8e51f4d9d..2e8abedac5 100644 --- a/spec/functional/resource/powershell_script_spec.rb +++ b/spec/functional/resource/powershell_script_spec.rb @@ -229,8 +229,7 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do end - context "when running on a 32-bit version of Windows", :windows32_only do - + context "when running on a 32-bit version of Ruby", :ruby32_only do it "executes a script with a 32-bit process if process architecture :i386 is specified" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:i386) @@ -240,15 +239,28 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do expect(source_contains_case_insensitive_content?( get_script_output, 'x86' )).to eq(true) end - it "raises an exception if :x86_64 process architecture is specified" do - begin - expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect - rescue Chef::Exceptions::Win32ArchitectureIncorrect + context "when running on a 64-bit version of Windows", :windows64_only do + it "executes a script with a 64-bit process if :x86_64 arch is specified" do + resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") + resource.architecture(:x86_64) + resource.returns(0) + resource.run_action(:run) + + expect(source_contains_case_insensitive_content?( get_script_output, 'AMD64' )).to eq(true) + end + end + + context "when running on a 32-bit version of Windows", :windows32_only do + it "raises an exception if :x86_64 process architecture is specified" do + begin + expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect + rescue Chef::Exceptions::Win32ArchitectureIncorrect + end end end end - context "when running on a 64-bit version of Windows", :windows64_only do + context "when running on a 64-bit version of Ruby", :ruby64_only do it "executes a script with a 64-bit process if :x86_64 arch is specified" do resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}") resource.architecture(:x86_64) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3b55ba9de0..d0d02f1a2a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -130,6 +130,8 @@ RSpec.configure do |config| config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later? config.filter_run_excluding :windows64_only => true unless windows64? config.filter_run_excluding :windows32_only => true unless windows32? + config.filter_run_excluding :ruby64_only => true unless ruby_64bit? + config.filter_run_excluding :ruby32_only => true unless ruby_32bit? config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc? config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc? config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined? diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb index da0313758b..1cfad05172 100644 --- a/spec/support/platform_helpers.rb +++ b/spec/support/platform_helpers.rb @@ -26,6 +26,14 @@ def ruby_20? !!(RUBY_VERSION =~ /^2.0/) end +def ruby_64bit? + !!(RbConfig::CONFIG['host_cpu'] =~ /x86_64/) +end + +def ruby_32bit? + !!(RbConfig::CONFIG['host_cpu'] =~ /i686/) +end + def windows? !!(RUBY_PLATFORM =~ /mswin|mingw|windows/) end diff --git a/spec/unit/mixin/windows_architecture_helper_spec.rb b/spec/unit/mixin/windows_architecture_helper_spec.rb index 3803d69371..55eca28dc2 100644 --- a/spec/unit/mixin/windows_architecture_helper_spec.rb +++ b/spec/unit/mixin/windows_architecture_helper_spec.rb @@ -60,23 +60,28 @@ describe Chef::Mixin::WindowsArchitectureHelper do end end - it "returns true for each supported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture" do - enumerate_architecture_node_combinations(true) + it "returns true only for supported desired architecture passed to node_supports_windows_architecture" do + with_node_architecture_combinations do | node, desired_arch | + expect(node_supports_windows_architecture?(node, desired_arch)).to be true if (node_windows_architecture(node) == :x86_64 || desired_arch == :i386 ) + expect(node_supports_windows_architecture?(node, desired_arch)).to be false if (node_windows_architecture(node) == :i386 && desired_arch == :x86_64 ) + end end - it "returns false for each unsupported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture?" do - enumerate_architecture_node_combinations(true) + it "returns true only when forced_32bit_override_required? has 64-bit node architecture and 32-bit desired architecture" do + with_node_architecture_combinations do | node, desired_arch | + expect(forced_32bit_override_required?(node, desired_arch)).to be true if ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386) && !is_i386_process_on_x86_64_windows?) + expect(forced_32bit_override_required?(node, desired_arch)).to be false if ! ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386)) + end end - def enumerate_architecture_node_combinations(only_valid_combinations) + def with_node_architecture_combinations @valid_architectures.each do | node_architecture | new_node = Chef::Node.new new_node.default["kernel"] = Hash.new new_node.default["kernel"][:machine] = node_architecture.to_s - @valid_architectures.each do | supported_architecture | - expect(node_supports_windows_architecture?(new_node, supported_architecture)).to eq(true) if only_valid_combinations && (supported_architecture != :x86_64 && node_architecture != :i386 ) - expect(node_supports_windows_architecture?(new_node, supported_architecture)).to eq(false) if ! only_valid_combinations && (supported_architecture == :x86_64 && node_architecture == :i386 ) + @valid_architectures.each do | architecture | + yield new_node, architecture if block_given? end end end |