File: etc.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 (180 lines) | stat: -rw-r--r-- 7,682 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
require_relative '../puppet/util/character_encoding'
# Wrapper around Ruby Etc module allowing us to manage encoding in a single
# place.
# This represents a subset of Ruby's Etc module, only the methods required by Puppet.

# On Ruby 2.1.0 and later, Etc returns strings in variable encoding depending on
# environment. The string returned will be labeled with the environment's
# encoding (Encoding.default_external), with one exception: If the environment
# encoding is 7-bit ASCII, and any individual character bit representation is
# equal to or greater than 128 - \x80 - 0b10000000 - signifying the smallest
# 8-bit big-endian value, the returned string will be in BINARY encoding instead
# of environment encoding.
#
# Barring that exception, the returned string will be labeled as encoding
# Encoding.default_external, regardless of validity or byte-width. For example,
# ruby will label a string containing a four-byte characters such as "\u{2070E}"
# as EUC_KR even though EUC_KR is a two-byte width encoding.
#
# On Ruby 2.0.x and earlier, Etc will always return string values in BINARY,
# ignoring encoding altogether.
#
# For Puppet we specifically want UTF-8 as our input from the Etc module - which
# is our input for many resource instance 'is' values. The associated 'should'
# value will basically always be coming from Puppet in UTF-8 - and written to
# disk as UTF-8. Etc is defined for Windows but the majority calls to it return
# nil and Puppet does not use it.
#
# That being said, we have cause to retain the original, pre-override string
# values. `puppet resource user`
# (Puppet::Resource::User.indirection.search('User', {})) uses self.instances to
# query for user(s) and then iterates over the results of that query again to
# obtain state for each user. If we've overridden the original user name and not
# retained the original, we've lost the ability to query the system for it
# later. Hence the Puppet::Etc::Passwd and Puppet::Etc::Group structs.
#
# We only use Etc for retrieving existing property values from the system. For
# setting property values, providers leverage system tools (i.e., `useradd`)
#
# @api private
module Puppet::Etc
  class << self

    # Etc::getgrent returns an Etc::Group struct object
    # On first call opens /etc/group and returns parse of first entry. Each subsquent call
    # returns new struct the next entry or nil if EOF. Call ::endgrent to close file.
    def getgrent
      override_field_values_to_utf8(::Etc.getgrent)
    end

    # closes handle to /etc/group file
    def endgrent
      ::Etc.endgrent
    end

    # effectively equivalent to IO#rewind of /etc/group
    def setgrent
      ::Etc.setgrent
    end

    # Etc::getpwent returns an Etc::Passwd struct object
    # On first call opens /etc/passwd and returns parse of first entry. Each subsquent call
    # returns new struct for the next entry or nil if EOF. Call ::endgrent to close file.
    def getpwent
      override_field_values_to_utf8(::Etc.getpwent)
    end

    # closes handle to /etc/passwd file
    def endpwent
      ::Etc.endpwent
    end

    #effectively equivalent to IO#rewind of /etc/passwd
    def setpwent
      ::Etc.setpwent
    end

    # Etc::getpwnam searches /etc/passwd file for an entry corresponding to
    # username.
    # returns an Etc::Passwd struct corresponding to the entry or raises
    # ArgumentError if none
    def getpwnam(username)
      override_field_values_to_utf8(::Etc.getpwnam(username))
    end

    # Etc::getgrnam searches /etc/group file for an entry corresponding to groupname.
    # returns an Etc::Group struct corresponding to the entry or raises
    # ArgumentError if none
    def getgrnam(groupname)
      override_field_values_to_utf8(::Etc.getgrnam(groupname))
    end

    # Etc::getgrid searches /etc/group file for an entry corresponding to id.
    # returns an Etc::Group struct corresponding to the entry or raises
    # ArgumentError if none
    def getgrgid(id)
      override_field_values_to_utf8(::Etc.getgrgid(id))
    end

    # Etc::getpwuid searches /etc/passwd file for an entry corresponding to id.
    # returns an Etc::Passwd struct corresponding to the entry or raises
    # ArgumentError if none
    def getpwuid(id)
      override_field_values_to_utf8(::Etc.getpwuid(id))
    end

    # Etc::group returns a Ruby iterator that executes a block for
    # each entry in the /etc/group file. The code-block is passed
    # a Group struct. See getgrent above for more details.
    def group
      # The implementation here duplicates the logic in https://github.com/ruby/etc/blob/master/ext/etc/etc.c#L523-L537
      # Note that we do not call ::Etc.group directly, because we
      # want to use our wrappers for methods like getgrent, setgrent,
      # endgrent, etc.
      return getgrent unless block_given?

      setgrent
      begin
        while cur_group = getgrent #rubocop:disable Lint/AssignmentInCondition
          yield cur_group
        end
      ensure
        endgrent
      end
    end

    private

    # @api private
    # Defines Puppet::Etc::Passwd struct class. Contains all of the original
    # member fields of Etc::Passwd, and additional "canonical_" versions of
    # these fields as well. API compatible with Etc::Passwd. Because Struct.new
    # defines a new Class object, we memoize to avoid superfluous extra Class
    # instantiations.
    def puppet_etc_passwd_class
      @password_class ||= Struct.new(*Etc::Passwd.members, *Etc::Passwd.members.map { |member| "canonical_#{member}".to_sym })
    end

    # @api private
    # Defines Puppet::Etc::Group struct class. Contains all of the original
    # member fields of Etc::Group, and additional "canonical_" versions of these
    # fields as well. API compatible with Etc::Group. Because Struct.new
    # defines a new Class object, we memoize to avoid superfluous extra Class
    # instantiations.
    def puppet_etc_group_class
      @group_class ||= Struct.new(*Etc::Group.members, *Etc::Group.members.map { |member| "canonical_#{member}".to_sym })
    end

    # Utility method for overriding the String values of a struct returned by
    # the Etc module to UTF-8. Structs returned by the ruby Etc module contain
    # members with fields of type String, Integer, or Array of Strings, so we
    # handle these types. Otherwise ignore fields.
    #
    # @api private
    # @param [Etc::Passwd or Etc::Group struct]
    # @return [Puppet::Etc::Passwd or Puppet::Etc::Group struct] a new struct
    #   object with the original struct values overridden to UTF-8, if valid. For
    #   invalid values originating in UTF-8, invalid characters are replaced with
    #   '?'. For each member the struct also contains a corresponding
    #   :canonical_<member name> struct member.
    def override_field_values_to_utf8(struct)
      return nil if struct.nil?
      new_struct = struct.is_a?(Etc::Passwd) ? puppet_etc_passwd_class.new : puppet_etc_group_class.new
      struct.each_pair do |member, value|
        if value.is_a?(String)
          new_struct["canonical_#{member}".to_sym] = value.dup
          new_struct[member] = Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(value).scrub
        elsif value.is_a?(Array)
          new_struct["canonical_#{member}".to_sym] = value.inject([]) { |acc, elem| acc << elem.dup }
          new_struct[member] = value.inject([]) do |acc, elem|
            acc << Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(elem).scrub
          end
        else
          new_struct["canonical_#{member}".to_sym] = value
          new_struct[member] = value
        end
      end
      new_struct
    end
  end
end