File: windows.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (213 lines) | stat: -rw-r--r-- 7,026 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
require_relative '../../puppet/file_system/posix'
require_relative '../../puppet/util/windows'

class Puppet::FileSystem::Windows < Puppet::FileSystem::Posix
  FULL_CONTROL = Puppet::Util::Windows::File::FILE_ALL_ACCESS
  FILE_READ = Puppet::Util::Windows::File::FILE_GENERIC_READ

  def open(path, mode, options, &block)
    # PUP-6959 mode is explicitly ignored until it can be implemented
    # Ruby on Windows uses mode for setting file attributes like read-only and
    # archived, not for setting permissions like POSIX
    raise TypeError.new('mode must be specified as an Integer') if mode && !mode.is_a?(Numeric)
    ::File.open(path, options, nil, &block)
  end

  def expand_path(path, dir_string = nil)
    # ensure `nil` values behave like underlying File.expand_path
    string_path = ::File.expand_path(path.nil? ? nil : path_string(path), dir_string)
    # if no tildes, nothing to expand, no need to call Windows API, return original string
    return string_path if !string_path.index('~')

    begin
      # no need to do existence check up front as GetLongPathName implies that check is performed
      # and it should be the exception that files aren't actually present
      string_path = Puppet::Util::Windows::File.get_long_pathname(string_path)
    rescue Puppet::Util::Windows::Error => e
      # preserve original File.expand_path behavior for file / path not found by returning string
      raise if (e.code != Puppet::Util::Windows::File::ERROR_FILE_NOT_FOUND &&
        e.code != Puppet::Util::Windows::File::ERROR_PATH_NOT_FOUND)
    end

    string_path
  end

  def exist?(path)
    return Puppet::Util::Windows::File.exist?(path)
  end

  def symlink(path, dest, options = {})
    raise_if_symlinks_unsupported

    dest_exists = exist?(dest) # returns false on dangling symlink
    dest_stat = Puppet::Util::Windows::File.stat(dest) if dest_exists

    # silent fail to preserve semantics of original FileUtils
    return 0 if dest_exists && dest_stat.ftype == 'directory'

    if dest_exists && dest_stat.ftype == 'file' && options[:force] != true
      raise(Errno::EEXIST, _("%{dest} already exists and the :force option was not specified") % { dest: dest })
    end

    if options[:noop] != true
      ::File.delete(dest) if dest_exists # can only be file
      Puppet::Util::Windows::File.symlink(path, dest)
    end

    0
  end

  def symlink?(path)
    return false if ! Puppet.features.manages_symlinks?
    Puppet::Util::Windows::File.symlink?(path)
  end

  def readlink(path)
    raise_if_symlinks_unsupported
    Puppet::Util::Windows::File.readlink(path)
  end

  def unlink(*file_names)
    if ! Puppet.features.manages_symlinks?
      return ::File.unlink(*file_names)
    end

    file_names.each do |file_name|
      file_name = file_name.to_s # handle PathName
      stat = Puppet::Util::Windows::File.stat(file_name) rescue nil

      # sigh, Ruby + Windows :(
      if !stat
        ::File.unlink(file_name) rescue Dir.rmdir(file_name)
      elsif stat.ftype == 'directory'
        if Puppet::Util::Windows::File.symlink?(file_name)
          Dir.rmdir(file_name)
        else
          raise Errno::EPERM.new(file_name)
        end
      else
        ::File.unlink(file_name)
      end
    end

    file_names.length
  end

  def stat(path)
    Puppet::Util::Windows::File.stat(path)
  end

  def lstat(path)
    if ! Puppet.features.manages_symlinks?
      return Puppet::Util::Windows::File.stat(path)
    end
    Puppet::Util::Windows::File.lstat(path)
  end

  def chmod(mode, path)
    Puppet::Util::Windows::Security.set_mode(mode, path.to_s)
  end

  def read_preserve_line_endings(path)
    contents = path.read( :mode => 'rb', :encoding => 'bom|utf-8')
    contents = path.read( :mode => 'rb', :encoding => "bom|#{Encoding::default_external.name}") unless contents.valid_encoding?
    contents = path.read unless contents.valid_encoding?

    contents
  end

  # https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--0-499-
  FILE_NOT_FOUND = 2
  ACCESS_DENIED = 5
  SHARING_VIOLATION = 32
  LOCK_VIOLATION = 33

  def replace_file(path, mode = nil)
    if directory?(path)
      raise Errno::EISDIR, _("Is a directory: %{directory}") % { directory: path }
    end

    current_sid = Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name)
    current_sid = Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_sam_compatible_user_name) unless current_sid

    dacl = case mode
           when 0644
             dacl = secure_dacl(current_sid)
             dacl.allow(Puppet::Util::Windows::SID::BuiltinUsers, FILE_READ)
             dacl
           when 0660, 0640, 0600, 0440
             secure_dacl(current_sid)
           when nil
             get_dacl_from_file(path) || secure_dacl(current_sid)
           else
             raise ArgumentError, "#{mode} is invalid: Only modes 0644, 0640, 0660, and 0440 are allowed"
           end


    tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(path), Puppet::FileSystem.dir_string(path))
    begin
      tempdacl = Puppet::Util::Windows::AccessControlList.new
      tempdacl.allow(current_sid, FULL_CONTROL)
      set_dacl(tempfile.path, tempdacl)

      begin
        yield tempfile
        tempfile.flush
        tempfile.fsync
      ensure
        tempfile.close
      end

      set_dacl(tempfile.path, dacl) if dacl
      ::File.rename(tempfile.path, path_string(path))
    ensure
      tempfile.close!
    end
  rescue Puppet::Util::Windows::Error => e
    case e.code
    when ACCESS_DENIED, SHARING_VIOLATION, LOCK_VIOLATION
      raise Errno::EACCES.new(path_string(path), e)
    else
      raise SystemCallError.new(e.message)
    end
  end

  private

  def set_dacl(path, dacl)
    sd = Puppet::Util::Windows::Security.get_security_descriptor(path)
    new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, dacl, true)
    Puppet::Util::Windows::Security.set_security_descriptor(path, new_sd)
  end

  def secure_dacl(current_sid)
    dacl = Puppet::Util::Windows::AccessControlList.new
    [
      Puppet::Util::Windows::SID::LocalSystem,
      Puppet::Util::Windows::SID::BuiltinAdministrators,
      current_sid
    ].uniq.map do |sid|
      dacl.allow(sid, FULL_CONTROL)
    end
    dacl
  end

  def get_dacl_from_file(path)
    sd = Puppet::Util::Windows::Security.get_security_descriptor(path_string(path))
    sd.dacl
  rescue Puppet::Util::Windows::Error => e
    raise e unless e.code == FILE_NOT_FOUND
  end

  def raise_if_symlinks_unsupported
    if ! Puppet.features.manages_symlinks?
      msg = _("This version of Windows does not support symlinks.  Windows Vista / 2008 or higher is required.")
      raise Puppet::Util::Windows::Error.new(msg)
    end

    if ! Puppet::Util::Windows::Process.process_privilege_symlink?
      Puppet.warning _("The current user does not have the necessary permission to manage symlinks.")
    end
  end

end