# frozen_string_literal: true module Puppet::FileSystem require_relative '../puppet/util' require_relative 'file_system/path_pattern' require_relative 'file_system/file_impl' require_relative 'file_system/memory_file' require_relative 'file_system/memory_impl' require_relative 'file_system/uniquefile' # create instance of the file system implementation to use for the current platform @impl = if Puppet::Util::Platform.windows? require_relative 'file_system/windows' Puppet::FileSystem::Windows elsif Puppet::Util::Platform.jruby? require_relative 'file_system/jruby' Puppet::FileSystem::JRuby else require_relative 'file_system/posix' Puppet::FileSystem::Posix end.new() # Allows overriding the filesystem for the duration of the given block. # The filesystem will only contain the given file(s). # # @param files [Puppet::FileSystem::MemoryFile] the files to have available # # @api private # def self.overlay(*files, &block) old_impl = @impl @impl = Puppet::FileSystem::MemoryImpl.new(*files) yield ensure @impl = old_impl end # Opens the given path with given mode, and options and optionally yields it to the given block. # # @param path [String, Pathname] the path to the file to operate on # @param mode [Integer] The mode to apply to the file if it is created # @param options [String] Extra file operation mode information to use # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be specified explicitly as fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 # @yield The file handle, in the mode given by options, else read-write mode # @return [Void] # # @api public # def self.open(path, mode, options, &block) @impl.open(assert_path(path), mode, options, &block) end # @return [Object] The directory of this file as an opaque handle # # @api public # def self.dir(path) @impl.dir(assert_path(path)) end # @return [String] The directory of this file as a String # # @api public # def self.dir_string(path) @impl.path_string(@impl.dir(assert_path(path))) end # @return [Boolean] Does the directory of the given path exist? def self.dir_exist?(path) @impl.exist?(@impl.dir(assert_path(path))) end # Creates all directories down to (inclusive) the dir of the given path def self.dir_mkpath(path) @impl.mkpath(@impl.dir(assert_path(path))) end # @return [Object] the name of the file as a opaque handle # # @api public # def self.basename(path) @impl.basename(assert_path(path)) end # @return [String] the name of the file # # @api public # def self.basename_string(path) @impl.path_string(@impl.basename(assert_path(path))) end # Allows exclusive updates to a file to be made by excluding concurrent # access using flock. This means that if the file is on a filesystem that # does not support flock, this method will provide no protection. # # While polling to acquire the lock the process will wait ever increasing # amounts of time in order to prevent multiple processes from wasting # resources. # # @param path [Pathname] the path to the file to operate on # @param mode [Integer] The mode to apply to the file if it is created # @param options [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be specified explicitly as fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 # @param timeout [Integer] Number of seconds to wait for the lock (defaults to 300) # @yield The file handle, in the mode given by options, else read-write mode # @return [Void] # @raise [Timeout::Error] If the timeout is exceeded while waiting to acquire the lock # # @api public # def self.exclusive_open(path, mode, options = 'r', timeout = 300, &block) @impl.exclusive_open(assert_path(path), mode, options, timeout, &block) end # Processes each line of the file by yielding it to the given block # # @api public # def self.each_line(path, &block) @impl.each_line(assert_path(path), &block) end # @return [String] The contents of the file # # @api public # def self.read(path, opts = {}) @impl.read(assert_path(path), opts) end # Read a file keeping the original line endings intact. This # attempts to open files using binary mode using some encoding # overrides and falling back to IO.read when none of the # encodings are valid. # # @return [String] The contents of the file # # @api public # def self.read_preserve_line_endings(path) @impl.read_preserve_line_endings(assert_path(path)) end # @return [String] The binary contents of the file # # @api public # def self.binread(path) @impl.binread(assert_path(path)) end # Determines if a file exists by verifying that the file can be stat'd. # Will follow symlinks and verify that the actual target path exists. # # @return [Boolean] true if the named file exists. # # @api public # def self.exist?(path) @impl.exist?(assert_path(path)) end # Determines if a file is a directory. # # @return [Boolean] true if the given file is a directory. # # @api public def self.directory?(path) @impl.directory?(assert_path(path)) end # Determines if a file is a file. # # @return [Boolean] true if the given file is a file. # # @api public def self.file?(path) @impl.file?(assert_path(path)) end # Determines if a file is executable. # # @todo Should this take into account extensions on the windows platform? # # @return [Boolean] true if this file can be executed # # @api public # def self.executable?(path) @impl.executable?(assert_path(path)) end # @return [Boolean] Whether the file is writable by the current process # # @api public # def self.writable?(path) @impl.writable?(assert_path(path)) end # Touches the file. On most systems this updates the mtime of the file. # # @param mtime [Time] The last modified time or nil to use the current time # # @api public # def self.touch(path, mtime: nil) @impl.touch(assert_path(path), mtime: mtime) end # Creates directories for all parts of the given path. # # @api public # def self.mkpath(path) @impl.mkpath(assert_path(path)) end # @return [Array] references to all of the children of the given # directory path, excluding `.` and `..`. # @api public def self.children(path) @impl.children(assert_path(path)) end # Creates a symbolic link dest which points to the current file. # If dest already exists: # # * and is a file, will raise Errno::EEXIST # * and is a directory, will return 0 but perform no action # * and is a symlink referencing a file, will raise Errno::EEXIST # * and is a symlink referencing a directory, will return 0 but perform no action # # With the :force option set to true, when dest already exists: # # * and is a file, will replace the existing file with a symlink (DANGEROUS) # * and is a directory, will return 0 but perform no action # * and is a symlink referencing a file, will modify the existing symlink # * and is a symlink referencing a directory, will return 0 but perform no action # # @param dest [String] The path to create the new symlink at # @param [Hash] options the options to create the symlink with # @option options [Boolean] :force overwrite dest # @option options [Boolean] :noop do not perform the operation # @option options [Boolean] :verbose verbose output # # @raise [Errno::EEXIST] dest already exists as a file and, :force is not set # # @return [Integer] 0 # # @api public # def self.symlink(path, dest, options = {}) @impl.symlink(assert_path(path), dest, options) end # @return [Boolean] true if the file is a symbolic link. # # @api public # def self.symlink?(path) @impl.symlink?(assert_path(path)) end # @return [String] the name of the file referenced by the given link. # # @api public # def self.readlink(path) @impl.readlink(assert_path(path)) end # Deletes the given paths, returning the number of names passed as arguments. # See also Dir::rmdir. # # @raise an exception on any error. # # @return [Integer] the number of paths passed as arguments # # @api public # def self.unlink(*paths) @impl.unlink(*(paths.map { |p| assert_path(p) })) end # @return [File::Stat] object for the named file. # # @api public # def self.stat(path) @impl.stat(assert_path(path)) end # @return [Integer] the size of the file # # @api public # def self.size(path) @impl.size(assert_path(path)) end # @return [File::Stat] Same as stat, but does not follow the last symbolic # link. Instead, reports on the link itself. # # @api public # def self.lstat(path) @impl.lstat(assert_path(path)) end # Compares the contents of this file against the contents of a stream. # # @param stream [IO] The stream to compare the contents against # @return [Boolean] Whether the contents were the same # # @api public # def self.compare_stream(path, stream) @impl.compare_stream(assert_path(path), stream) end # Produces an opaque pathname "handle" object representing the given path. # Different implementations of the underlying file system may use different runtime # objects. The produced "handle" should be used in all other operations # that take a "path". No operation should be directly invoked on the returned opaque object # # @param path [String] The string representation of the path # @return [Object] An opaque path handle on which no operations should be directly performed # # @api public # def self.pathname(path) @impl.pathname(path) end # Produces a string representation of the opaque path handle, with expansions # performed on ~. For Windows, this means that C:\Users\Admini~1\AppData will # be expanded to C:\Users\Administrator\AppData. On POSIX filesystems, the # value ~ will be expanded to something like /Users/Foo # # This method exists primarlily to resolve a Ruby deficiency where # File.expand_path doesn't convert short paths to long paths, which is # important when resolving the path to load. # # @param path [Object] a path handle produced by {#pathname} # @return [String] a string representation of the path # def self.expand_path(path, dir_string = nil) @impl.expand_path(path, dir_string) end # Asserts that the given path is of the expected type produced by #pathname # # @raise [ArgumentError] when path is not of the expected type # # @api public # def self.assert_path(path) @impl.assert_path(path) end # Produces a string representation of the opaque path handle. # # @param path [Object] a path handle produced by {#pathname} # @return [String] a string representation of the path # def self.path_string(path) @impl.path_string(path) end # Create and open a file for write only if it doesn't exist. # # @see Puppet::FileSystem::open # # @raise [Errno::EEXIST] path already exists. # # @api public # def self.exclusive_create(path, mode, &block) @impl.exclusive_create(assert_path(path), mode, &block) end # Changes permission bits on the named path to the bit pattern represented # by mode. # # @param mode [Integer] The mode to apply to the file if it is created # @param path [String] The path to the file, can also accept [PathName] # # @raise [Errno::ENOENT]: path doesn't exist # # @api public # def self.chmod(mode, path) @impl.chmod(mode, assert_path(path)) end # Replace the contents of a file atomically, creating the file if necessary. # If a `mode` is specified, then it will always be applied to the file. If # a `mode` is not specified and the file exists, its mode will be preserved. # If the file doesn't exist, the mode will be set to a platform-specific # default. # # @param path [String] The path to the file, can also accept [PathName] # @param mode [Integer] Optional mode for the file. # # @raise [Errno::EISDIR]: path is a directory # # @api public # def self.replace_file(path, mode = nil, &block) @impl.replace_file(assert_path(path), mode, &block) end end