File: mode.rb

package info (click to toggle)
puppet-agent 8.10.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,404 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (192 lines) | stat: -rw-r--r-- 7,523 bytes parent folder | download | duplicates (2)
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
# frozen_string_literal: true

# Manage file modes.  This state should support different formats
# for specification (e.g., u+rwx, or -0011), but for now only supports
# specifying the full mode.

module Puppet
  Puppet::Type.type(:file).newproperty(:mode) do
    require_relative '../../../puppet/util/symbolic_file_mode'
    include Puppet::Util::SymbolicFileMode

    desc <<-'EOT'
      The desired permissions mode for the file, in symbolic or numeric
      notation. This value **must** be specified as a string; do not use
      un-quoted numbers to represent file modes.

      If the mode is omitted (or explicitly set to `undef`), Puppet does not
      enforce permissions on existing files and creates new files with
      permissions of `0644`.

      The `file` type uses traditional Unix permission schemes and translates
      them to equivalent permissions for systems which represent permissions
      differently, including Windows. For detailed ACL controls on Windows,
      you can leave `mode` unmanaged and use
      [the puppetlabs/acl module.](https://forge.puppetlabs.com/puppetlabs/acl)

      Numeric modes should use the standard octal notation of
      `<SETUID/SETGID/STICKY><OWNER><GROUP><OTHER>` (for example, "0644").

      * Each of the "owner," "group," and "other" digits should be a sum of the
        permissions for that class of users, where read = 4, write = 2, and
        execute/search = 1.
      * The setuid/setgid/sticky digit is also a sum, where setuid = 4, setgid = 2,
        and sticky = 1.
      * The setuid/setgid/sticky digit is optional. If it is absent, Puppet will
        clear any existing setuid/setgid/sticky permissions. (So to make your intent
        clear, you should use at least four digits for numeric modes.)
      * When specifying numeric permissions for directories, Puppet sets the search
        permission wherever the read permission is set.

      Symbolic modes should be represented as a string of comma-separated
      permission clauses, in the form `<WHO><OP><PERM>`:

      * "Who" should be any combination of u (user), g (group), and o (other), or a (all)
      * "Op" should be = (set exact permissions), + (add select permissions),
        or - (remove select permissions)
      * "Perm" should be one or more of:
          * r (read)
          * w (write)
          * x (execute/search)
          * t (sticky)
          * s (setuid/setgid)
          * X (execute/search if directory or if any one user can execute)
          * u (user's current permissions)
          * g (group's current permissions)
          * o (other's current permissions)

      Thus, mode `"0664"` could be represented symbolically as either `a=r,ug+w`
      or `ug=rw,o=r`.  However, symbolic modes are more expressive than numeric
      modes: a mode only affects the specified bits, so `mode => 'ug+w'` will
      set the user and group write bits, without affecting any other bits.

      See the manual page for GNU or BSD `chmod` for more details
      on numeric and symbolic modes.

      On Windows, permissions are translated as follows:

      * Owner and group names are mapped to Windows SIDs
      * The "other" class of users maps to the "Everyone" SID
      * The read/write/execute permissions map to the `FILE_GENERIC_READ`,
        `FILE_GENERIC_WRITE`, and `FILE_GENERIC_EXECUTE` access rights; a
        file's owner always has the `FULL_CONTROL` right
      * "Other" users can't have any permissions a file's group lacks,
        and its group can't have any permissions its owner lacks; that is, "0644"
        is an acceptable mode, but "0464" is not.
    EOT

    validate do |value|
      unless value.is_a?(String)
        raise Puppet::Error, "The file mode specification must be a string, not '#{value.class.name}'"
      end
      unless value.nil? or valid_symbolic_mode?(value)
        raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}"
      end
    end

    munge do |value|
      return nil if value.nil?

      unless valid_symbolic_mode?(value)
        raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}"
      end

      # normalizes to symbolic form, e.g. u+a, an octal string without leading 0
      normalize_symbolic_mode(value)
    end

    unmunge do |value|
      # return symbolic form or octal string *with* leading 0's
      display_mode(value) if value
    end

    def desired_mode_from_current(desired, current)
      current = current.to_i(8) if current.is_a? String
      is_a_directory = @resource.stat && @resource.stat.directory?
      symbolic_mode_to_int(desired, current, is_a_directory)
    end

    # If we're a directory, we need to be executable for all cases
    # that are readable.  This should probably be selectable, but eh.
    def dirmask(value)
      if FileTest.directory?(resource[:path]) and value =~ /^\d+$/ then
        value = value.to_i(8)
        value |= 0o100 if value & 0o400 != 0
        value |= 0o10 if value & 0o40 != 0
        value |= 0o1 if value & 0o4 != 0
        value = value.to_s(8)
      end

      value
    end

    # If we're not following links and we're a link, then we just turn
    # off mode management entirely.
    def insync?(currentvalue)
      if provider.respond_to?(:munge_windows_system_group)
        munged_mode = provider.munge_windows_system_group(currentvalue, @should)
        return false if munged_mode.nil?

        currentvalue = munged_mode
      end
      stat = @resource.stat
      if stat && stat.ftype == "link" && @resource[:links] != :follow
        debug _("Not managing symlink mode")
        true
      else
        super(currentvalue)
      end
    end

    def property_matches?(current, desired)
      return false unless current

      current_bits = normalize_symbolic_mode(current)
      desired_bits = desired_mode_from_current(desired, current).to_s(8)
      current_bits == desired_bits
    end

    # Ideally, dirmask'ing could be done at munge time, but we don't know if 'ensure'
    # will eventually be a directory or something else. And unfortunately, that logic
    # depends on the ensure, source, and target properties. So rather than duplicate
    # that logic, and get it wrong, we do dirmask during retrieve, after 'ensure' has
    # been synced.
    def retrieve
      if @resource.stat
        @should &&= @should.collect { |s| dirmask(s) }
      end

      super
    end

    # Finally, when we sync the mode out we need to transform it; since we
    # don't have access to the calculated "desired" value here, or the
    # "current" value, only the "should" value we need to retrieve again.
    def sync
      current = @resource.stat ? @resource.stat.mode : 0o644
      set(desired_mode_from_current(@should[0], current).to_s(8))
    end

    def change_to_s(old_value, desired)
      return super if desired =~ /^\d+$/

      old_bits = normalize_symbolic_mode(old_value)
      new_bits = normalize_symbolic_mode(desired_mode_from_current(desired, old_bits))
      super(old_bits, new_bits) + " (#{desired})"
    end

    def should_to_s(should_value)
      "'#{should_value.rjust(4, '0')}'"
    end

    def is_to_s(currentvalue) # rubocop:disable Naming/PredicateName
      if currentvalue == :absent
        # This can occur during audits---if a file is transitioning from
        # present to absent the mode will have a value of `:absent`.
        super
      else
        "'#{currentvalue.rjust(4, '0')}'"
      end
    end
  end
end