pdk 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +3 -9
  4. data/exe/pdk +1 -1
  5. data/lib/pdk.rb +5 -4
  6. data/lib/pdk/cli.rb +62 -59
  7. data/lib/pdk/cli/errors.rb +1 -1
  8. data/lib/pdk/cli/exec.rb +154 -29
  9. data/lib/pdk/cli/input.rb +2 -2
  10. data/lib/pdk/cli/new.rb +12 -27
  11. data/lib/pdk/cli/new/class.rb +28 -41
  12. data/lib/pdk/cli/new/module.rb +30 -41
  13. data/lib/pdk/cli/test.rb +9 -20
  14. data/lib/pdk/cli/test/unit.rb +38 -0
  15. data/lib/pdk/cli/util/option_normalizer.rb +45 -19
  16. data/lib/pdk/cli/util/option_validator.rb +24 -20
  17. data/lib/pdk/cli/validate.rb +65 -65
  18. data/lib/pdk/generate.rb +5 -0
  19. data/lib/pdk/generators/module.rb +37 -33
  20. data/lib/pdk/generators/puppet_class.rb +1 -1
  21. data/lib/pdk/generators/puppet_object.rb +19 -20
  22. data/lib/pdk/logger.rb +1 -1
  23. data/lib/pdk/module/metadata.rb +35 -18
  24. data/lib/pdk/module/templatedir.rb +40 -33
  25. data/lib/pdk/report.rb +76 -19
  26. data/lib/pdk/report/event.rb +276 -0
  27. data/lib/pdk/template_file.rb +8 -6
  28. data/lib/pdk/tests/unit.rb +8 -3
  29. data/lib/pdk/util.rb +65 -0
  30. data/lib/pdk/util/bundler.rb +167 -0
  31. data/lib/pdk/util/version.rb +34 -0
  32. data/lib/pdk/validate.rb +3 -4
  33. data/lib/pdk/validators/base_validator.rb +60 -4
  34. data/lib/pdk/validators/metadata.rb +29 -0
  35. data/lib/pdk/validators/puppet/puppet_lint.rb +47 -0
  36. data/lib/pdk/validators/puppet/puppet_parser.rb +34 -0
  37. data/lib/pdk/validators/puppet_validator.rb +30 -0
  38. data/lib/pdk/validators/ruby/rubocop.rb +59 -0
  39. data/lib/pdk/validators/ruby_validator.rb +29 -0
  40. data/lib/pdk/version.rb +1 -1
  41. data/lib/puppet/util/windows.rb +14 -0
  42. data/lib/puppet/util/windows/api_types.rb +278 -0
  43. data/lib/puppet/util/windows/file.rb +488 -0
  44. data/lib/puppet/util/windows/string.rb +16 -0
  45. data/locales/de/pdk.po +263 -78
  46. data/locales/pdk.pot +224 -65
  47. metadata +60 -8
  48. data/lib/pdk/cli/tests/unit.rb +0 -52
  49. data/lib/pdk/validators/puppet_lint.rb +0 -17
  50. data/lib/pdk/validators/puppet_parser.rb +0 -17
  51. data/lib/pdk/validators/ruby_lint.rb +0 -17
@@ -0,0 +1,488 @@
1
+ require 'puppet/util/windows'
2
+
3
+ module Puppet::Util::Windows::File
4
+ require 'ffi'
5
+ extend FFI::Library
6
+ extend Puppet::Util::Windows::String
7
+
8
+ FILE_ATTRIBUTE_READONLY = 0x00000001
9
+
10
+ SYNCHRONIZE = 0x100000
11
+ STANDARD_RIGHTS_REQUIRED = 0xf0000
12
+ STANDARD_RIGHTS_READ = 0x20000
13
+ STANDARD_RIGHTS_WRITE = 0x20000
14
+ STANDARD_RIGHTS_EXECUTE = 0x20000
15
+ STANDARD_RIGHTS_ALL = 0x1F0000
16
+ SPECIFIC_RIGHTS_ALL = 0xFFFF
17
+
18
+ FILE_READ_DATA = 1
19
+ FILE_WRITE_DATA = 2
20
+ FILE_APPEND_DATA = 4
21
+ FILE_READ_EA = 8
22
+ FILE_WRITE_EA = 16
23
+ FILE_EXECUTE = 32
24
+ FILE_DELETE_CHILD = 64
25
+ FILE_READ_ATTRIBUTES = 128
26
+ FILE_WRITE_ATTRIBUTES = 256
27
+
28
+ FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
29
+
30
+ FILE_GENERIC_READ =
31
+ STANDARD_RIGHTS_READ |
32
+ FILE_READ_DATA |
33
+ FILE_READ_ATTRIBUTES |
34
+ FILE_READ_EA |
35
+ SYNCHRONIZE
36
+
37
+ FILE_GENERIC_WRITE =
38
+ STANDARD_RIGHTS_WRITE |
39
+ FILE_WRITE_DATA |
40
+ FILE_WRITE_ATTRIBUTES |
41
+ FILE_WRITE_EA |
42
+ FILE_APPEND_DATA |
43
+ SYNCHRONIZE
44
+
45
+ FILE_GENERIC_EXECUTE =
46
+ STANDARD_RIGHTS_EXECUTE |
47
+ FILE_READ_ATTRIBUTES |
48
+ FILE_EXECUTE |
49
+ SYNCHRONIZE
50
+
51
+ REPLACEFILE_WRITE_THROUGH = 0x1
52
+ REPLACEFILE_IGNORE_MERGE_ERRORS = 0x2
53
+ REPLACEFILE_IGNORE_ACL_ERRORS = 0x3
54
+
55
+ def replace_file(target, source)
56
+ target_encoded = wide_string(target.to_s)
57
+ source_encoded = wide_string(source.to_s)
58
+
59
+ flags = REPLACEFILE_IGNORE_MERGE_ERRORS
60
+ backup_file = nil
61
+ result = ReplaceFileW(
62
+ target_encoded,
63
+ source_encoded,
64
+ backup_file,
65
+ flags,
66
+ FFI::Pointer::NULL,
67
+ FFI::Pointer::NULL
68
+ )
69
+
70
+ return true if result != FFI::WIN32_FALSE
71
+ raise Puppet::Util::Windows::Error.new("ReplaceFile(#{target}, #{source})")
72
+ end
73
+ module_function :replace_file
74
+
75
+ def move_file_ex(source, target, flags = 0)
76
+ result = MoveFileExW(wide_string(source.to_s),
77
+ wide_string(target.to_s),
78
+ flags)
79
+
80
+ return true if result != FFI::WIN32_FALSE
81
+ raise Puppet::Util::Windows::Error.
82
+ new("MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})")
83
+ end
84
+ module_function :move_file_ex
85
+
86
+ def symlink(target, symlink)
87
+ flags = File.directory?(target) ? 0x1 : 0x0
88
+ result = CreateSymbolicLinkW(wide_string(symlink.to_s),
89
+ wide_string(target.to_s), flags)
90
+ return true if result != FFI::WIN32_FALSE
91
+ raise Puppet::Util::Windows::Error.new(
92
+ "CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})")
93
+ end
94
+ module_function :symlink
95
+
96
+
97
+ def exist?(path)
98
+ path = path.to_str if path.respond_to?(:to_str) # support WatchedFile
99
+ path = path.to_s # support String and Pathname
100
+
101
+ seen_paths = []
102
+ # follow up to 64 symlinks before giving up
103
+ 0.upto(64) do |depth|
104
+ # return false if this path has been seen before. This is protection against circular symlinks
105
+ return false if seen_paths.include?(path.downcase)
106
+
107
+ result = get_attributes(path,false)
108
+
109
+ # return false for path not found
110
+ return false if result == INVALID_FILE_ATTRIBUTES
111
+
112
+ # return true if path exists and it's not a symlink
113
+ # Other file attributes are ignored. https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx
114
+ return true if (result & FILE_ATTRIBUTE_REPARSE_POINT) != FILE_ATTRIBUTE_REPARSE_POINT
115
+
116
+ # walk the symlink and try again...
117
+ seen_paths << path.downcase
118
+ path = readlink(path)
119
+ end
120
+
121
+ false
122
+ end
123
+ module_function :exist?
124
+
125
+
126
+ INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF #define INVALID_FILE_ATTRIBUTES (DWORD (-1))
127
+
128
+ def get_attributes(file_name, raise_on_invalid = true)
129
+ result = GetFileAttributesW(wide_string(file_name.to_s))
130
+ if raise_on_invalid && result == INVALID_FILE_ATTRIBUTES
131
+ raise Puppet::Util::Windows::Error.new("GetFileAttributes(#{file_name})")
132
+ end
133
+
134
+ result
135
+ end
136
+ module_function :get_attributes
137
+
138
+ def add_attributes(path, flags)
139
+ oldattrs = get_attributes(path)
140
+
141
+ if (oldattrs | flags) != oldattrs
142
+ set_attributes(path, oldattrs | flags)
143
+ end
144
+ end
145
+ module_function :add_attributes
146
+
147
+ def remove_attributes(path, flags)
148
+ oldattrs = get_attributes(path)
149
+
150
+ if (oldattrs & ~flags) != oldattrs
151
+ set_attributes(path, oldattrs & ~flags)
152
+ end
153
+ end
154
+ module_function :remove_attributes
155
+
156
+ def set_attributes(path, flags)
157
+ success = SetFileAttributesW(wide_string(path), flags) != FFI::WIN32_FALSE
158
+ raise Puppet::Util::Windows::Error.new(_("Failed to set file attributes")) if !success
159
+
160
+ success
161
+ end
162
+ module_function :set_attributes
163
+
164
+ #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)
165
+ INVALID_HANDLE_VALUE = FFI::Pointer.new(-1).address
166
+ def self.create_file(file_name, desired_access, share_mode, security_attributes,
167
+ creation_disposition, flags_and_attributes, template_file_handle)
168
+
169
+ result = CreateFileW(wide_string(file_name.to_s),
170
+ desired_access, share_mode, security_attributes, creation_disposition,
171
+ flags_and_attributes, template_file_handle)
172
+
173
+ return result unless result == INVALID_HANDLE_VALUE
174
+ raise Puppet::Util::Windows::Error.new(
175
+ "CreateFile(#{file_name}, #{desired_access.to_s(8)}, #{share_mode.to_s(8)}, " +
176
+ "#{security_attributes}, #{creation_disposition.to_s(8)}, " +
177
+ "#{flags_and_attributes.to_s(8)}, #{template_file_handle})")
178
+ end
179
+
180
+ def self.get_reparse_point_data(handle, &block)
181
+ # must be multiple of 1024, min 10240
182
+ FFI::MemoryPointer.new(REPARSE_DATA_BUFFER.size) do |reparse_data_buffer_ptr|
183
+ device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, reparse_data_buffer_ptr)
184
+ yield REPARSE_DATA_BUFFER.new(reparse_data_buffer_ptr)
185
+ end
186
+
187
+ # underlying struct MemoryPointer has been cleaned up by this point, nothing to return
188
+ nil
189
+ end
190
+
191
+ def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil)
192
+ if out_buffer.nil?
193
+ raise Puppet::Util::Windows::Error.new(_("out_buffer is required"))
194
+ end
195
+
196
+ FFI::MemoryPointer.new(:dword, 1) do |bytes_returned_ptr|
197
+ result = DeviceIoControl(
198
+ handle,
199
+ io_control_code,
200
+ in_buffer, in_buffer.nil? ? 0 : in_buffer.size,
201
+ out_buffer, out_buffer.size,
202
+ bytes_returned_ptr,
203
+ nil
204
+ )
205
+
206
+ if result == FFI::WIN32_FALSE
207
+ raise Puppet::Util::Windows::Error.new(
208
+ "DeviceIoControl(#{handle}, #{io_control_code}, " +
209
+ "#{in_buffer}, #{in_buffer ? in_buffer.size : ''}, " +
210
+ "#{out_buffer}, #{out_buffer ? out_buffer.size : ''}")
211
+ end
212
+ end
213
+
214
+ out_buffer
215
+ end
216
+
217
+ FILE_ATTRIBUTE_REPARSE_POINT = 0x400
218
+ def symlink?(file_name)
219
+ attributes = get_attributes(file_name, false)
220
+
221
+ return false if (attributes == INVALID_FILE_ATTRIBUTES)
222
+ (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
223
+ end
224
+ module_function :symlink?
225
+
226
+ GENERIC_READ = 0x80000000
227
+ GENERIC_WRITE = 0x40000000
228
+ GENERIC_EXECUTE = 0x20000000
229
+ GENERIC_ALL = 0x10000000
230
+ FILE_SHARE_READ = 1
231
+ FILE_SHARE_WRITE = 2
232
+ OPEN_EXISTING = 3
233
+ FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
234
+ FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
235
+
236
+ def self.open_symlink(link_name)
237
+ begin
238
+ yield handle = create_file(
239
+ link_name,
240
+ GENERIC_READ,
241
+ FILE_SHARE_READ,
242
+ nil, # security_attributes
243
+ OPEN_EXISTING,
244
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
245
+ 0) # template_file
246
+ ensure
247
+ FFI::WIN32.CloseHandle(handle) if handle
248
+ end
249
+
250
+ # handle has had CloseHandle called against it, so nothing to return
251
+ nil
252
+ end
253
+
254
+ def readlink(link_name)
255
+ link = nil
256
+ open_symlink(link_name) do |handle|
257
+ link = resolve_symlink(handle)
258
+ end
259
+
260
+ link
261
+ end
262
+ module_function :readlink
263
+
264
+ ERROR_FILE_NOT_FOUND = 2
265
+ ERROR_PATH_NOT_FOUND = 3
266
+
267
+ def get_long_pathname(path)
268
+ converted = ''
269
+ FFI::Pointer.from_string_to_wide_string(path) do |path_ptr|
270
+ # includes terminating NULL
271
+ buffer_size = GetLongPathNameW(path_ptr, FFI::Pointer::NULL, 0)
272
+ FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr|
273
+ if GetLongPathNameW(path_ptr, converted_ptr, buffer_size) == FFI::WIN32_FALSE
274
+ raise Puppet::Util::Windows::Error.new(_("Failed to call GetLongPathName"))
275
+ end
276
+
277
+ converted = converted_ptr.read_wide_string(buffer_size - 1)
278
+ end
279
+ end
280
+
281
+ converted
282
+ end
283
+ module_function :get_long_pathname
284
+
285
+ def get_short_pathname(path)
286
+ converted = ''
287
+ FFI::Pointer.from_string_to_wide_string(path) do |path_ptr|
288
+ # includes terminating NULL
289
+ buffer_size = GetShortPathNameW(path_ptr, FFI::Pointer::NULL, 0)
290
+ FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr|
291
+ if GetShortPathNameW(path_ptr, converted_ptr, buffer_size) == FFI::WIN32_FALSE
292
+ raise Puppet::Util::Windows::Error.new("Failed to call GetShortPathName")
293
+ end
294
+
295
+ converted = converted_ptr.read_wide_string(buffer_size - 1)
296
+ end
297
+ end
298
+
299
+ converted
300
+ end
301
+ module_function :get_short_pathname
302
+
303
+ def stat(file_name)
304
+ file_name = file_name.to_s # accommodate PathName or String
305
+ stat = File.stat(file_name)
306
+ singleton_class = class << stat; self; end
307
+ target_path = file_name
308
+
309
+ if symlink?(file_name)
310
+ target_path = readlink(file_name)
311
+ link_ftype = File.stat(target_path).ftype
312
+
313
+ # sigh, monkey patch instance method for instance, and close over link_ftype
314
+ singleton_class.send(:define_method, :ftype) do
315
+ link_ftype
316
+ end
317
+ end
318
+
319
+ singleton_class.send(:define_method, :mode) do
320
+ Puppet::Util::Windows::Security.get_mode(target_path)
321
+ end
322
+
323
+ stat
324
+ end
325
+ module_function :stat
326
+
327
+ def lstat(file_name)
328
+ file_name = file_name.to_s # accommodate PathName or String
329
+ # monkey'ing around!
330
+ stat = File.lstat(file_name)
331
+
332
+ singleton_class = class << stat; self; end
333
+ singleton_class.send(:define_method, :mode) do
334
+ Puppet::Util::Windows::Security.get_mode(file_name)
335
+ end
336
+
337
+ if symlink?(file_name)
338
+ def stat.ftype
339
+ "link"
340
+ end
341
+ end
342
+ stat
343
+ end
344
+ module_function :lstat
345
+
346
+ private
347
+
348
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx
349
+ FSCTL_GET_REPARSE_POINT = 0x900a8
350
+
351
+ def self.resolve_symlink(handle)
352
+ path = nil
353
+ get_reparse_point_data(handle) do |reparse_data|
354
+ offset = reparse_data[:PrintNameOffset]
355
+ length = reparse_data[:PrintNameLength]
356
+
357
+ ptr = reparse_data.pointer + reparse_data.offset_of(:PathBuffer) + offset
358
+ path = ptr.read_wide_string(length / 2) # length is bytes, need UTF-16 wchars
359
+ end
360
+
361
+ path
362
+ end
363
+
364
+ ffi_convention :stdcall
365
+
366
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx
367
+ # BOOL WINAPI ReplaceFile(
368
+ # _In_ LPCTSTR lpReplacedFileName,
369
+ # _In_ LPCTSTR lpReplacementFileName,
370
+ # _In_opt_ LPCTSTR lpBackupFileName,
371
+ # _In_ DWORD dwReplaceFlags - 0x1 REPLACEFILE_WRITE_THROUGH,
372
+ # 0x2 REPLACEFILE_IGNORE_MERGE_ERRORS,
373
+ # 0x4 REPLACEFILE_IGNORE_ACL_ERRORS
374
+ # _Reserved_ LPVOID lpExclude,
375
+ # _Reserved_ LPVOID lpReserved
376
+ # );
377
+ ffi_lib :kernel32
378
+ attach_function_private :ReplaceFileW,
379
+ [:lpcwstr, :lpcwstr, :lpcwstr, :dword, :lpvoid, :lpvoid], :win32_bool
380
+
381
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx
382
+ # BOOL WINAPI MoveFileEx(
383
+ # _In_ LPCTSTR lpExistingFileName,
384
+ # _In_opt_ LPCTSTR lpNewFileName,
385
+ # _In_ DWORD dwFlags
386
+ # );
387
+ ffi_lib :kernel32
388
+ attach_function_private :MoveFileExW,
389
+ [:lpcwstr, :lpcwstr, :dword], :win32_bool
390
+
391
+ # BOOLEAN WINAPI CreateSymbolicLink(
392
+ # _In_ LPTSTR lpSymlinkFileName, - symbolic link to be created
393
+ # _In_ LPTSTR lpTargetFileName, - name of target for symbolic link
394
+ # _In_ DWORD dwFlags - 0x0 target is a file, 0x1 target is a directory
395
+ # );
396
+ # rescue on Windows < 6.0 so that code doesn't explode
397
+ begin
398
+ ffi_lib :kernel32
399
+ attach_function_private :CreateSymbolicLinkW,
400
+ [:lpwstr, :lpwstr, :dword], :boolean
401
+ rescue LoadError
402
+ end
403
+
404
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364944(v=vs.85).aspx
405
+ # DWORD WINAPI GetFileAttributes(
406
+ # _In_ LPCTSTR lpFileName
407
+ # );
408
+ ffi_lib :kernel32
409
+ attach_function_private :GetFileAttributesW,
410
+ [:lpcwstr], :dword
411
+
412
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365535(v=vs.85).aspx
413
+ # BOOL WINAPI SetFileAttributes(
414
+ # _In_ LPCTSTR lpFileName,
415
+ # _In_ DWORD dwFileAttributes
416
+ # );
417
+ ffi_lib :kernel32
418
+ attach_function_private :SetFileAttributesW,
419
+ [:lpcwstr, :dword], :win32_bool
420
+
421
+ # HANDLE WINAPI CreateFile(
422
+ # _In_ LPCTSTR lpFileName,
423
+ # _In_ DWORD dwDesiredAccess,
424
+ # _In_ DWORD dwShareMode,
425
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
426
+ # _In_ DWORD dwCreationDisposition,
427
+ # _In_ DWORD dwFlagsAndAttributes,
428
+ # _In_opt_ HANDLE hTemplateFile
429
+ # );
430
+ ffi_lib :kernel32
431
+ attach_function_private :CreateFileW,
432
+ [:lpcwstr, :dword, :dword, :pointer, :dword, :dword, :handle], :handle
433
+
434
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx
435
+ # BOOL WINAPI DeviceIoControl(
436
+ # _In_ HANDLE hDevice,
437
+ # _In_ DWORD dwIoControlCode,
438
+ # _In_opt_ LPVOID lpInBuffer,
439
+ # _In_ DWORD nInBufferSize,
440
+ # _Out_opt_ LPVOID lpOutBuffer,
441
+ # _In_ DWORD nOutBufferSize,
442
+ # _Out_opt_ LPDWORD lpBytesReturned,
443
+ # _Inout_opt_ LPOVERLAPPED lpOverlapped
444
+ # );
445
+ ffi_lib :kernel32
446
+ attach_function_private :DeviceIoControl,
447
+ [:handle, :dword, :lpvoid, :dword, :lpvoid, :dword, :lpdword, :pointer], :win32_bool
448
+
449
+ MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384
450
+
451
+ # REPARSE_DATA_BUFFER
452
+ # https://msdn.microsoft.com/en-us/library/cc232006.aspx
453
+ # https://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
454
+ # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes
455
+ class REPARSE_DATA_BUFFER < FFI::Struct
456
+ layout :ReparseTag, :win32_ulong,
457
+ :ReparseDataLength, :ushort,
458
+ :Reserved, :ushort,
459
+ :SubstituteNameOffset, :ushort,
460
+ :SubstituteNameLength, :ushort,
461
+ :PrintNameOffset, :ushort,
462
+ :PrintNameLength, :ushort,
463
+ :Flags, :win32_ulong,
464
+ # max less above fields dword / uint 4 bytes, ushort 2 bytes
465
+ # technically a WCHAR buffer, but we care about size in bytes here
466
+ :PathBuffer, [:byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 20]
467
+ end
468
+
469
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364980(v=vs.85).aspx
470
+ # DWORD WINAPI GetLongPathName(
471
+ # _In_ LPCTSTR lpszShortPath,
472
+ # _Out_ LPTSTR lpszLongPath,
473
+ # _In_ DWORD cchBuffer
474
+ # );
475
+ ffi_lib :kernel32
476
+ attach_function_private :GetLongPathNameW,
477
+ [:lpcwstr, :lpwstr, :dword], :dword
478
+
479
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
480
+ # DWORD WINAPI GetShortPathName(
481
+ # _In_ LPCTSTR lpszLongPath,
482
+ # _Out_ LPTSTR lpszShortPath,
483
+ # _In_ DWORD cchBuffer
484
+ # );
485
+ ffi_lib :kernel32
486
+ attach_function_private :GetShortPathNameW,
487
+ [:lpcwstr, :lpwstr, :dword], :dword
488
+ end