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
|