Skip to content

Commit

Permalink
Merge pull request #82 from james-stocks/windows_paths
Browse files Browse the repository at this point in the history
(maint) Expand Windows 8.3 paths in templatedir
  • Loading branch information
DavidS authored Jun 20, 2017
2 parents 188cd06 + 6a42e8f commit d711690
Show file tree
Hide file tree
Showing 10 changed files with 830 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ AllCops:
# binstubs, and other utilities
- bin/**/*
- vendor/**/*
# Puppet util code follows Puppet's style, not pdk's
- lib/puppet/**/*

Layout/IndentHeredoc:
Description: The `squiggly` style would be preferable, but is only available from ruby 2.3. We'll enable this when we can.
Expand Down
7 changes: 6 additions & 1 deletion lib/pdk/cli/exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ def run_process!
begin
@process.start
rescue ChildProcess::LaunchError => e
raise PDK::CLI::FatalError, _("Failed to execute '%{command}': %{message}") % { command: argv.join(' '), message: e.message }
msg = if @process.respond_to?(:argv)
_("Failed to execute '%{command}': %{message}") % { command: @process.argv.join(' '), message: e.message }
else
_('Failed to execute process: %{message}') % { message: e.message }
end
raise PDK::CLI::FatalError, msg
end

if timeout
Expand Down
3 changes: 2 additions & 1 deletion lib/pdk/module/templatedir.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def initialize(path_or_url)
PDK.logger.error clone_result[:stderr]
raise PDK::CLI::FatalError, _("Unable to clone git repository '%{repo}' to '%{dest}'") % { repo: path_or_url, dest: temp_dir }
end
@path = temp_dir

@path = PDK::Util.canonical_path(temp_dir)
@repo = path_or_url
end

Expand Down
18 changes: 18 additions & 0 deletions lib/pdk/util.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'tmpdir'
require 'tempfile'
require 'puppet/util/windows'

module PDK
module Util
Expand Down Expand Up @@ -32,6 +33,23 @@ def make_tmpdir_name(base)
end
module_function :make_tmpdir_name

# Return an expanded, absolute path
#
# @param path [String] Existing path that may not be canonical
#
# @return [String] Canonical path
def canonical_path(path)
if Gem.win_platform?
unless File.exist?(path)
raise PDK::CLI::FatalError, _("Cannot resolve a full path to '%{path}' as it does not currently exist") % { path: path }
end
Puppet::Util::Windows::File.get_long_pathname(path)
else
File.expand_path(path)
end
end
module_function :canonical_path

# Returns the fully qualified path to a per-user PDK cachedir.
#
# @return [String] Fully qualified path to per-user PDK cachedir.
Expand Down
14 changes: 14 additions & 0 deletions lib/puppet/util/windows.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Puppet
module Util
module Windows
module File; end

if Gem.win_platform?
# these reference platform specific gems
require 'puppet/util/windows/api_types'
require 'puppet/util/windows/string'
require 'puppet/util/windows/file'
end
end
end
end
278 changes: 278 additions & 0 deletions lib/puppet/util/windows/api_types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
require 'ffi'
require 'puppet/util/windows/string'

module Puppet::Util::Windows::APITypes
module ::FFI
WIN32_FALSE = 0

# standard Win32 error codes
ERROR_SUCCESS = 0
end

module ::FFI::Library
# Wrapper method for attach_function + private
def attach_function_private(*args)
attach_function(*args)
private args[0]
end
end

class ::FFI::Pointer
NULL_HANDLE = 0

def self.from_string_to_wide_string(str, &block)
str = Puppet::Util::Windows::String.wide_string(str)
FFI::MemoryPointer.new(:byte, str.bytesize) do |ptr|
# uchar here is synonymous with byte
ptr.put_array_of_uchar(0, str.bytes.to_a)

yield ptr
end

# ptr has already had free called, so nothing to return
nil
end

def read_win32_bool
# BOOL is always a 32-bit integer in Win32
# some Win32 APIs return 1 for true, while others are non-0
read_int32 != FFI::WIN32_FALSE
end

alias_method :read_dword, :read_uint32
alias_method :read_win32_ulong, :read_uint32
alias_method :read_qword, :read_uint64

alias_method :read_hresult, :read_int32

def read_handle
type_size == 4 ? read_uint32 : read_uint64
end

alias_method :read_wchar, :read_uint16
alias_method :read_word, :read_uint16
alias_method :read_array_of_wchar, :read_array_of_uint16

def read_wide_string(char_length, dst_encoding = Encoding::UTF_8)
# char_length is number of wide chars (typically excluding NULLs), *not* bytes
str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE')
str.encode(dst_encoding)
end

# @param max_char_length [Integer] Maximum number of wide chars to return (typically excluding NULLs), *not* bytes
# @param null_terminator [Symbol] Number of number of null wchar characters, *not* bytes, that determine the end of the string
# null_terminator = :single_null, then the terminating sequence is two bytes of zero. This is UNIT16 = 0
# null_terminator = :double_null, then the terminating sequence is four bytes of zero. This is UNIT32 = 0
def read_arbitrary_wide_string_up_to(max_char_length = 512, null_terminator = :single_null)
if null_terminator != :single_null && null_terminator != :double_null
raise _("Unable to read wide strings with %{null_terminator} terminal nulls") % { null_terminator: null_terminator }
end

terminator_width = null_terminator == :single_null ? 1 : 2
reader_method = null_terminator == :single_null ? :get_uint16 : :get_uint32

# Look for a null terminating characters; if found, read up to that null (exclusive)
(0...max_char_length - terminator_width).each do |i|
return read_wide_string(i) if send(reader_method, (i * 2)) == 0
end

# String is longer than the max; read just to the max
read_wide_string(max_char_length)
end

def read_win32_local_pointer(&block)
ptr = nil
begin
ptr = read_pointer
yield ptr
ensure
if ptr && ! ptr.null?
if FFI::WIN32::LocalFree(ptr.address) != FFI::Pointer::NULL_HANDLE
Puppet.debug "LocalFree memory leak"
end
end
end

# ptr has already had LocalFree called, so nothing to return
nil
end

def read_com_memory_pointer(&block)
ptr = nil
begin
ptr = read_pointer
yield ptr
ensure
FFI::WIN32::CoTaskMemFree(ptr) if ptr && ! ptr.null?
end

# ptr has already had CoTaskMemFree called, so nothing to return
nil
end


alias_method :write_dword, :write_uint32
alias_method :write_word, :write_uint16
end

# FFI Types
# https://github.com/ffi/ffi/wiki/Types

# Windows - Common Data Types
# https://msdn.microsoft.com/en-us/library/cc230309.aspx

# Windows Data Types
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx

FFI.typedef :uint16, :word
FFI.typedef :uint32, :dword
# uintptr_t is defined in an FFI conf as platform specific, either
# ulong_long on x64 or just ulong on x86
FFI.typedef :uintptr_t, :handle
FFI.typedef :uintptr_t, :hwnd

# buffer_inout is similar to pointer (platform specific), but optimized for buffers
FFI.typedef :buffer_inout, :lpwstr
# buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers
FFI.typedef :buffer_in, :lpcwstr
FFI.typedef :buffer_in, :lpcolestr

# string is also similar to pointer, but should be used for const char *
# NOTE that this is not wide, useful only for A suffixed functions
FFI.typedef :string, :lpcstr

# pointer in FFI is platform specific
# NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL
FFI.typedef :pointer, :lpcvoid
FFI.typedef :pointer, :lpvoid
FFI.typedef :pointer, :lpword
FFI.typedef :pointer, :lpbyte
FFI.typedef :pointer, :lpdword
FFI.typedef :pointer, :pdword
FFI.typedef :pointer, :phandle
FFI.typedef :pointer, :ulong_ptr
FFI.typedef :pointer, :pbool
FFI.typedef :pointer, :lpunknown

# any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width
# which is what FFI uses by default
# instead create new aliases for these very special cases
# NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it
FFI.typedef :uint32, :win32_ulong
FFI.typedef :int32, :win32_long
# FFI bool can be only 1 byte at times,
# Win32 BOOL is a signed int, and is always 4 bytes, even on x64
# https://blogs.msdn.com/b/oldnewthing/archive/2011/03/28/10146459.aspx
FFI.typedef :int32, :win32_bool

# BOOLEAN (unlike BOOL) is a BYTE - typedef unsigned char BYTE;
FFI.typedef :uchar, :boolean

# Same as a LONG, a 32-bit signed integer
FFI.typedef :int32, :hresult

# NOTE: FFI already defines (u)short as a 16-bit (un)signed like this:
# FFI.typedef :uint16, :ushort
# FFI.typedef :int16, :short

# 8 bits per byte
FFI.typedef :uchar, :byte
FFI.typedef :uint16, :wchar

module ::FFI::WIN32
extend ::FFI::Library

# https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
# typedef struct _GUID {
# DWORD Data1;
# WORD Data2;
# WORD Data3;
# BYTE Data4[8];
# } GUID;
class GUID < FFI::Struct
layout :Data1, :dword,
:Data2, :word,
:Data3, :word,
:Data4, [:byte, 8]

def self.[](s)
raise _('Bad GUID format.') unless s =~ /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i

new.tap do |guid|
guid[:Data1] = s[0, 8].to_i(16)
guid[:Data2] = s[9, 4].to_i(16)
guid[:Data3] = s[14, 4].to_i(16)
guid[:Data4][0] = s[19, 2].to_i(16)
guid[:Data4][1] = s[21, 2].to_i(16)
s[24, 12].split('').each_slice(2).with_index do |a, i|
guid[:Data4][i + 2] = a.join('').to_i(16)
end
end
end

def ==(other) Windows.memcmp(other, self, size) == 0 end
end

# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx
# typedef struct _SYSTEMTIME {
# WORD wYear;
# WORD wMonth;
# WORD wDayOfWeek;
# WORD wDay;
# WORD wHour;
# WORD wMinute;
# WORD wSecond;
# WORD wMilliseconds;
# } SYSTEMTIME, *PSYSTEMTIME;
class SYSTEMTIME < FFI::Struct
layout :wYear, :word,
:wMonth, :word,
:wDayOfWeek, :word,
:wDay, :word,
:wHour, :word,
:wMinute, :word,
:wSecond, :word,
:wMilliseconds, :word

def to_local_time
Time.local(self[:wYear], self[:wMonth], self[:wDay],
self[:wHour], self[:wMinute], self[:wSecond], self[:wMilliseconds] * 1000)
end
end

# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
# Contains a 64-bit value representing the number of 100-nanosecond
# intervals since January 1, 1601 (UTC).
# typedef struct _FILETIME {
# DWORD dwLowDateTime;
# DWORD dwHighDateTime;
# } FILETIME, *PFILETIME;
class FILETIME < FFI::Struct
layout :dwLowDateTime, :dword,
:dwHighDateTime, :dword
end

ffi_convention :stdcall

# https://msdn.microsoft.com/en-us/library/windows/desktop/aa366730(v=vs.85).aspx
# HLOCAL WINAPI LocalFree(
# _In_ HLOCAL hMem
# );
ffi_lib :kernel32
attach_function :LocalFree, [:handle], :handle

# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
# BOOL WINAPI CloseHandle(
# _In_ HANDLE hObject
# );
ffi_lib :kernel32
attach_function_private :CloseHandle, [:handle], :win32_bool

# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680722(v=vs.85).aspx
# void CoTaskMemFree(
# _In_opt_ LPVOID pv
# );
ffi_lib :ole32
attach_function :CoTaskMemFree, [:lpvoid], :void
end
end
Loading

0 comments on commit d711690

Please sign in to comment.