Module: Puppet::Util::Windows::File

Extended by:
FFI::Library, String
Defined in:
lib/puppet/util/windows.rb,
lib/puppet/util/windows/file.rb

Defined Under Namespace

Classes: MOUNT_POINT_REPARSE_DATA_BUFFER, SYMLINK_REPARSE_DATA_BUFFER

Constant Summary collapse

FILE_ATTRIBUTE_READONLY =
0x00000001
SYNCHRONIZE =

msdn.microsoft.com/en-us/library/windows/desktop/aa379607(v=vs.85).aspx The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state. Some object types do not support this access right.

0x100000
DELETE =

The right to delete the object.

0x00010000
WRITE_DAC =

The right to read the information in the object's security descriptor, not including the information in the system access control list (SACL). READ_CONTROL = 0x00020000 The right to modify the discretionary access control list (DACL) in the object's security descriptor.

0x00040000
WRITE_OWNER =

The right to change the owner in the object's security descriptor.

0x00080000
STANDARD_RIGHTS_REQUIRED =

Combines DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER access.

0xf0000
STANDARD_RIGHTS_READ =

Currently defined to equal READ_CONTROL.

0x20000
STANDARD_RIGHTS_WRITE =

Currently defined to equal READ_CONTROL.

0x20000
STANDARD_RIGHTS_EXECUTE =

Currently defined to equal READ_CONTROL.

0x20000
STANDARD_RIGHTS_ALL =

Combines DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE access.

0x1F0000
SPECIFIC_RIGHTS_ALL =
0xFFFF
FILE_READ_DATA =
1
FILE_WRITE_DATA =
2
FILE_APPEND_DATA =
4
FILE_READ_EA =
8
FILE_WRITE_EA =
16
FILE_EXECUTE =
32
FILE_DELETE_CHILD =
64
FILE_READ_ATTRIBUTES =
128
FILE_WRITE_ATTRIBUTES =
256
FILE_ALL_ACCESS =
STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF
FILE_GENERIC_READ =
STANDARD_RIGHTS_READ |
FILE_READ_DATA |
FILE_READ_ATTRIBUTES |
FILE_READ_EA |
SYNCHRONIZE
FILE_GENERIC_WRITE =
STANDARD_RIGHTS_WRITE |
FILE_WRITE_DATA |
FILE_WRITE_ATTRIBUTES |
FILE_WRITE_EA |
FILE_APPEND_DATA |
SYNCHRONIZE
FILE_GENERIC_EXECUTE =
STANDARD_RIGHTS_EXECUTE |
FILE_READ_ATTRIBUTES |
FILE_EXECUTE |
SYNCHRONIZE
REPLACEFILE_WRITE_THROUGH =
0x1
REPLACEFILE_IGNORE_MERGE_ERRORS =
0x2
REPLACEFILE_IGNORE_ACL_ERRORS =
0x3
INVALID_FILE_ATTRIBUTES =

define INVALID_FILE_ATTRIBUTES (DWORD (-1))

0xFFFFFFFF
INVALID_HANDLE_VALUE =

define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)

FFI::Pointer.new(-1).address
IO_REPARSE_TAG_MOUNT_POINT =
0xA0000003
IO_REPARSE_TAG_HSM =
0xC0000004
IO_REPARSE_TAG_HSM2 =
0x80000006
IO_REPARSE_TAG_SIS =
0x80000007
IO_REPARSE_TAG_WIM =
0x80000008
IO_REPARSE_TAG_CSV =
0x80000009
IO_REPARSE_TAG_DFS =
0x8000000A
0xA000000C
IO_REPARSE_TAG_DFSR =
0x80000012
IO_REPARSE_TAG_DEDUP =
0x80000013
IO_REPARSE_TAG_NFS =
0x80000014
FILE_ATTRIBUTE_REPARSE_POINT =
0x400
GENERIC_READ =
0x80000000
GENERIC_WRITE =
0x40000000
GENERIC_EXECUTE =
0x20000000
GENERIC_ALL =
0x10000000
FILE_SHARE_READ =
1
FILE_SHARE_WRITE =
2
OPEN_EXISTING =
3
FILE_FLAG_OPEN_REPARSE_POINT =
0x00200000
FILE_FLAG_BACKUP_SEMANTICS =
0x02000000
ERROR_FILE_NOT_FOUND =
2
ERROR_PATH_NOT_FOUND =
3
FSCTL_GET_REPARSE_POINT =
0x900a8
MAXIMUM_REPARSE_DATA_BUFFER_SIZE =
16384

Class Method Summary collapse

Methods included from String

wide_string

Methods included from FFI::Library

attach_function_private

Class Method Details

.add_attributes(path, flags) ⇒ Object


160
161
162
163
164
165
166
# File 'lib/puppet/util/windows/file.rb', line 160

def add_attributes(path, flags)
  oldattrs = get_attributes(path)

  if (oldattrs | flags) != oldattrs
    set_attributes(path, oldattrs | flags)
  end
end

.create_file(file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file_handle) ⇒ Object


188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/puppet/util/windows/file.rb', line 188

def self.create_file(file_name, desired_access, share_mode, security_attributes,
  creation_disposition, flags_and_attributes, template_file_handle)

  result = CreateFileW(wide_string(file_name.to_s),
    desired_access, share_mode, security_attributes, creation_disposition,
    flags_and_attributes, template_file_handle)

  return result unless result == INVALID_HANDLE_VALUE
  raise Puppet::Util::Windows::Error.new(
    "CreateFile(#{file_name}, #{desired_access.to_s(8)}, #{share_mode.to_s(8)}, " +
      "#{security_attributes}, #{creation_disposition.to_s(8)}, " +
      "#{flags_and_attributes.to_s(8)}, #{template_file_handle})")
end

.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil) ⇒ Object


253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/puppet/util/windows/file.rb', line 253

def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil)
  if out_buffer.nil?
    raise Puppet::Util::Windows::Error.new(_("out_buffer is required"))
  end

  FFI::MemoryPointer.new(:dword, 1) do |bytes_returned_ptr|
    result = DeviceIoControl(
      handle,
      io_control_code,
      in_buffer, in_buffer.nil? ? 0 : in_buffer.size,
      out_buffer, out_buffer.size,
      bytes_returned_ptr,
      nil
    )

    if result == FFI::WIN32_FALSE
      raise Puppet::Util::Windows::Error.new(
        "DeviceIoControl(#{handle}, #{io_control_code}, " +
        "#{in_buffer}, #{in_buffer ? in_buffer.size : ''}, " +
        "#{out_buffer}, #{out_buffer ? out_buffer.size : ''}")
    end
  end

  out_buffer
end

.exist?(path) ⇒ Boolean

Returns:

  • (Boolean)

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/puppet/util/windows/file.rb', line 115

def exist?(path)
  path = path.to_str if path.respond_to?(:to_str) # support WatchedFile
  path = path.to_s # support String and Pathname

  seen_paths = []  # follow up to 64 symlinks before giving up

  0.upto(64) do |depth|
    # return false if this path has been seen before.  This is protection against circular symlinks
    return false if seen_paths.include?(path.downcase)

    result = get_attributes(path,false)

    # return false for path not found
    return false if result == INVALID_FILE_ATTRIBUTES

    # return true if path exists and it's not a symlink
    # Other file attributes are ignored. https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx
    reparse_point = (result & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
    if reparse_point && symlink_reparse_point?(path)      # walk the symlink and try again...

      seen_paths << path.downcase
      path = readlink(path)
    else
      # file was found and its not a symlink
      return true
    end
  end

  false
end

.get_attributes(file_name, raise_on_invalid = true) ⇒ Object


150
151
152
153
154
155
156
157
# File 'lib/puppet/util/windows/file.rb', line 150

def get_attributes(file_name, raise_on_invalid = true)
  result = GetFileAttributesW(wide_string(file_name.to_s))
  if raise_on_invalid && result == INVALID_FILE_ATTRIBUTES
    raise Puppet::Util::Windows::Error.new("GetFileAttributes(#{file_name})")
  end

  result
end

.get_long_pathname(path) ⇒ Object


335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/puppet/util/windows/file.rb', line 335

def get_long_pathname(path)
  converted = ''
  FFI::Pointer.from_string_to_wide_string(path) do |path_ptr|
    # includes terminating NULL
    buffer_size = GetLongPathNameW(path_ptr, FFI::Pointer::NULL, 0)
    FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr|
      if GetLongPathNameW(path_ptr, converted_ptr, buffer_size) == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error.new(_("Failed to call GetLongPathName"))
      end

      converted = converted_ptr.read_wide_string(buffer_size - 1)
    end
  end

  converted
end

.get_reparse_point_data(handle, &block) ⇒ Object


214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/puppet/util/windows/file.rb', line 214

def self.get_reparse_point_data(handle, &block)
  # must be multiple of 1024, min 10240
  FFI::MemoryPointer.new(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) do |reparse_data_buffer_ptr|
    device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, reparse_data_buffer_ptr)

    reparse_tag = reparse_data_buffer_ptr.read_win32_ulong
    buffer_type = case reparse_tag
    when IO_REPARSE_TAG_SYMLINK
      SYMLINK_REPARSE_DATA_BUFFER
    when IO_REPARSE_TAG_MOUNT_POINT
      MOUNT_POINT_REPARSE_DATA_BUFFER
    when IO_REPARSE_TAG_NFS
      raise Puppet::Util::Windows::Error.new("Retrieving NFS reparse point data is unsupported")
    else
      raise Puppet::Util::Windows::Error.new("DeviceIoControl(#{handle}, " +
        "FSCTL_GET_REPARSE_POINT) returned unknown tag 0x#{reparse_tag.to_s(16).upcase}")
    end

    yield buffer_type.new(reparse_data_buffer_ptr)
  end

  # underlying struct MemoryPointer has been cleaned up by this point, nothing to return
  nil
end

.get_reparse_point_tag(handle) ⇒ Object


239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/puppet/util/windows/file.rb', line 239

def self.get_reparse_point_tag(handle)
  reparse_tag = nil

  # must be multiple of 1024, min 10240
  FFI::MemoryPointer.new(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) do |reparse_data_buffer_ptr|
    device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, reparse_data_buffer_ptr)

    # DWORD ReparseTag is the first member of the struct
    reparse_tag = reparse_data_buffer_ptr.read_win32_ulong
  end

  reparse_tag
end

.get_short_pathname(path) ⇒ Object


353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/puppet/util/windows/file.rb', line 353

def get_short_pathname(path)
  converted = ''
  FFI::Pointer.from_string_to_wide_string(path) do |path_ptr|
    # includes terminating NULL
    buffer_size = GetShortPathNameW(path_ptr, FFI::Pointer::NULL, 0)
    FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr|
      if GetShortPathNameW(path_ptr, converted_ptr, buffer_size) == FFI::WIN32_FALSE
        raise Puppet::Util::Windows::Error.new("Failed to call GetShortPathName")
      end

      converted = converted_ptr.read_wide_string(buffer_size - 1)
    end
  end

  converted
end

.lstat(file_name) ⇒ Object


395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/puppet/util/windows/file.rb', line 395

def lstat(file_name)
  file_name = file_name.to_s # accommodate PathName or String
  # monkey'ing around!
  stat = File.lstat(file_name)

  singleton_class = class << stat; self; end
  singleton_class.send(:define_method, :mode) do
    Puppet::Util::Windows::Security.get_mode(file_name)
  end

  if symlink?(file_name)
    def stat.ftype
      "link"
    end
  end
  stat
end

.move_file_ex(source, target, flags = 0) ⇒ Object


93
94
95
96
97
98
99
100
101
# File 'lib/puppet/util/windows/file.rb', line 93

def move_file_ex(source, target, flags = 0)
  result = MoveFileExW(wide_string(source.to_s),
                       wide_string(target.to_s),
                       flags)

  return true if result != FFI::WIN32_FALSE
  raise Puppet::Util::Windows::Error.
    new("MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})")
end

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/puppet/util/windows/file.rb', line 304

def self.open_symlink(link_name)
  begin
    yield handle = create_file(
    link_name,
    GENERIC_READ,
    FILE_SHARE_READ,
    nil, # security_attributes
    OPEN_EXISTING,
    FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
    0) # template_file
  ensure
    FFI::WIN32.CloseHandle(handle) if handle
  end

  # handle has had CloseHandle called against it, so nothing to return
  nil
end

322
323
324
325
326
327
328
329
# File 'lib/puppet/util/windows/file.rb', line 322

def readlink(link_name)
  link = nil
  open_symlink(link_name) do |handle|
    link = resolve_symlink(handle)
  end

  link
end

.remove_attributes(path, flags) ⇒ Object


169
170
171
172
173
174
175
# File 'lib/puppet/util/windows/file.rb', line 169

def remove_attributes(path, flags)
  oldattrs = get_attributes(path)

  if (oldattrs & ~flags) != oldattrs
    set_attributes(path, oldattrs & ~flags)
  end
end

.reparse_point?(file_name) ⇒ Boolean

Returns:

  • (Boolean)

280
281
282
283
284
285
# File 'lib/puppet/util/windows/file.rb', line 280

def reparse_point?(file_name)
  attributes = get_attributes(file_name, false)

  return false if (attributes == INVALID_FILE_ATTRIBUTES)
  (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT
end

.replace_file(target, source) ⇒ Object


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/puppet/util/windows/file.rb', line 73

def replace_file(target, source)
  target_encoded = wide_string(target.to_s)
  source_encoded = wide_string(source.to_s)

  flags = REPLACEFILE_IGNORE_MERGE_ERRORS
  backup_file = nil
  result = ReplaceFileW(
    target_encoded,
    source_encoded,
    backup_file,
    flags,
    FFI::Pointer::NULL,
    FFI::Pointer::NULL
  )

  return true if result != FFI::WIN32_FALSE
  raise Puppet::Util::Windows::Error.new("ReplaceFile(#{target}, #{source})")
end

.set_attributes(path, flags) ⇒ Object


178
179
180
181
182
183
# File 'lib/puppet/util/windows/file.rb', line 178

def set_attributes(path, flags)
  success = SetFileAttributesW(wide_string(path), flags) != FFI::WIN32_FALSE
  raise Puppet::Util::Windows::Error.new(_("Failed to set file attributes")) if !success

  success
end

.stat(file_name) ⇒ Object


371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/puppet/util/windows/file.rb', line 371

def stat(file_name)
  file_name = file_name.to_s # accommodate PathName or String
  stat = File.stat(file_name)
  singleton_class = class << stat; self; end
  target_path = file_name

  if symlink?(file_name)
    target_path = readlink(file_name)
    link_ftype = File.stat(target_path).ftype

    # sigh, monkey patch instance method for instance, and close over link_ftype
    singleton_class.send(:define_method, :ftype) do
      link_ftype
    end
  end

  singleton_class.send(:define_method, :mode) do
    Puppet::Util::Windows::Security.get_mode(target_path)
  end

  stat
end

104
105
106
107
108
109
110
111
# File 'lib/puppet/util/windows/file.rb', line 104

def symlink(target, symlink)
  flags = File.directory?(target) ? 0x1 : 0x0
  result = CreateSymbolicLinkW(wide_string(symlink.to_s),
    wide_string(target.to_s), flags)
  return true if result != FFI::WIN32_FALSE
  raise Puppet::Util::Windows::Error.new(
    "CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})")
end

.symlink?(file_name) ⇒ Boolean

Returns:

  • (Boolean)

288
289
290
291
# File 'lib/puppet/util/windows/file.rb', line 288

def symlink?(file_name)
  # Puppet currently only handles mount point and symlink reparse points, ignores others
  reparse_point?(file_name) && symlink_reparse_point?(file_name)
end