lib/god_object/file_permissions/complex_mode.rb
# encoding: UTF-8
=begin
Copyright GodObject Team <dev@godobject.net>, 2012-2016
This file is part of FilePermissions.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
=end
module GodObject
module FilePermissions
# An aggregate of Mode and SpecialMode to represent normal file
# permissions in a POSIX environment.
class ComplexMode
# @return [GodObject::FilePermissions::Mode] permissions for the owner
attr_accessor :user
# @return [GodObject::FilePermissions::Mode] permissions for the owning
# group
attr_accessor :group
# @return [GodObject::FilePermissions::Mode] permissions for everyone
# else
attr_accessor :other
# @return [GodObject::FilePermissions::SpecialMode] special file flags
attr_accessor :special
extend Forwardable
include HelperMixin
class << self
include HelperMixin
# @overload build(complex_mode)
# Returns an existing instance of
# GodObject::FilePermissions::ComplexMode
# @param [GodObject::FilePermissions::ComplexMode] complex_mode an
# already existing ComplexMode
# @return [GodObject::FilePermissions::ComplexMode] the same
# ComplexMode object
#
# @overload build(numeric)
# Returns a new ComplexMode object with the given numeric
# representation.
# @param [Integer] numeric a numeric representation
# @return [GodObject::FilePermissions::ComplexMode] a new ComplexMode
# object
#
# @overload build(enabled_digits)
# Returns a new ComplexMode object with the given enabled digits
# @param [Array<:user_read, :user_write, :user_execute,
# :group_read, :group_write, :group_execute, :other_read,
# :other_write, :other_execute, :setuid, :setgid, :sticky>]
# enabled_digits a list of enabled digits
# @return [GodObject::FilePermissions::ComplexMode] a new ComplexMode
# object
def build(mode)
if mode.kind_of?(self)
mode
else
new(mode)
end
end
# Creates a new complex mode from filesystem object given by path.
#
# @param [Pathname, String] path path of the source filesystem object
# @param [:resolve_symlinks, :target_symlinks] symlink_handling if set
# to :target_symlinks and the target is a symlink, the symlink will
# not be resolved but is itself used as source. By default, the
# symlink will be resolved
def from_file(path, symlink_handling = :resolve_symlinks)
file = to_pathname(path)
case symlink_handling
when :resolve_symlinks
new(file.stat.mode)
when :target_symlinks
new(file.lstat.mode)
else
raise ArgumentError, "Invalid symlink handling: #{symlink_handling.inspect}"
end
end
end
# @overload initialize(numeric)
# Returns a new ComplexMode object with the given numeric
# representation.
# @param [Integer] numeric a numeric representation
# @return [GodObject::FilePermissions::ComplexMode] a new ComplexMode
# object
#
# @overload initialize(enabled_digits)
# Returns a new ComplexMode object with the given enabled digits
# @param [Array<:user_read, :user_write, :user_execute, :group_read,
# :group_write, :group_execute, :other_read, :other_write,
# :other_execute, :setuid, :setgid, :sticky>] enabled_digits a list
# of enabled digits
# @return [GodObject::FilePermissions::ComplexMode] a new ComplexMode
# object
def initialize(*mode_components)
sub_mode_components = Hash.new{|hash, key| hash[key] = Set.new }
if mode_components.size == 1 && mode_components.first.respond_to?(:to_int)
integer = mode_components.first
[:other, :group, :user, :special].each do |mode|
sub_mode_components[mode] = integer & 0b111
integer = integer >> 3 unless mode == :special
end
else
if mode_components.size == 1 && mode_components.first.is_a?(Enumerable)
mode_components = mode_components.first
end
mode_components.flatten.each do |digit|
case digit
when /^user_(.*)$/
sub_mode_components[:user] << $1.to_sym
when /^group_(.*)$/
sub_mode_components[:group] << $1.to_sym
when /^other_(.*)$/
sub_mode_components[:other] << $1.to_sym
else
sub_mode_components[:special] << digit
end
end
end
@user = Mode.new(sub_mode_components[:user])
@group = Mode.new(sub_mode_components[:group])
@other = Mode.new(sub_mode_components[:other])
@special = SpecialMode.new(sub_mode_components[:special])
end
# Assigns the mode to a filesystem object given by path.
#
# @param [Pathname, String] path path of the target filesystem object
# @param [:resolve_symlinks, :target_symlinks] symlink_handling if set to
# :target_symlinks and the target is a symlink, the symlink will not be
# resolved but is itself used as target. By default, the symlink will
# be resolved
def assign_to_file(path, symlink_handling = :resolve_symlinks)
file = to_pathname(path)
case symlink_handling
when :resolve_symlinks
file.chmod(to_i)
when :target_symlinks
begin
file.lchmod(to_i)
rescue ::NotImplementedError, Errno::ENOSYS
raise NotImplementedError, "lchmod function is not available in current OS or Ruby environment"
end
else
raise ArgumentError, "Invalid symlink handling: #{symlink_handling.inspect}"
end
end
# Represents the ComplexMode as String for debugging.
#
# @return [String] a String representation for debugging
def inspect
"#<#{self.class}: #{to_s.inspect}>"
end
# Represents the ComplexMode as String.
#
# Uses the format used by the `ls` utility.
#
# @return [String] a String representation
def to_s
string = ''
string << case [@special.setuid?, @user.execute?]
when [true, true]
@user.to_s[0..1] << 's'
when [true, false]
@user.to_s[0..1] << 'S'
else
@user.to_s
end
string << case [@special.setgid?, @group.execute?]
when [true, true]
@group.to_s[0..1] << 's'
when [true, false]
@group.to_s[0..1] << 'S'
else
@group.to_s
end
string << case [@special.sticky?, @other.execute?]
when [true, true]
@other.to_s[0..1] << 't'
when [true, false]
@other.to_s[0..1] << 'T'
else
@other.to_s
end
string
end
# Converts the ComplexMode to a four-digit octal representation
#
# @return [Integer] four-digit octal representation
def to_i
result = 0
[@special, @user, @group, @other].each do |mode|
result = (result << 3) | mode.to_i
end
result
end
# @!method setuid
# @attribute setuid [readonly]
# @return (see GodObject::FilePermissions::SpecialMode#setuid)
#
# @!method setuid?
# @attribute setuid? [readonly]
# @return (see GodObject::FilePermissions::SpecialMode#setuid?)
#
# @!method setgid
# @attribute setgid [readonly]
# @return (see GodObject::FilePermissions::SpecialMode#setgid)
#
# @!method setgid?
# @attribute setgid? [readonly]
# @return (see GodObject::FilePermissions::SpecialMode#setgid?)
#
# @!method sticky
# @attribute sticky [readonly]
# @return (see GodObject::FilePermissions::SpecialMode#sticky)
#
# @!method sticky?
# @attribute sticky? [readonly]
# @return (see GodObject::FilePermissions::SpecialMode#sticky?)
def_delegators :@special, :setuid?, :setgid?, :sticky?
end
end
end