Class: OsCtld::ProcessIdentity
- Inherits:
-
Object
- Object
- OsCtld::ProcessIdentity
- Defined in:
- lib/osctld/process_identity.rb
Overview
Stable process identity with descriptors opened through one /proc entry.
Instance Attribute Summary collapse
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
-
#pidfd ⇒ Object
readonly
Returns the value of attribute pidfd.
-
#proc_dir ⇒ Object
readonly
Returns the value of attribute proc_dir.
-
#root_dir ⇒ Object
readonly
Returns the value of attribute root_dir.
Class Method Summary collapse
Instance Method Summary collapse
- #alive? ⇒ Boolean
-
#authenticate!(cgroup_path:) ⇒ Object
Recheck that every retained descriptor still describes this live process and that the process remains below the expected cgroup.
- #cgroup_entries ⇒ Object
- #cgroup_paths ⇒ Object
- #close ⇒ Object
- #close_io(io) ⇒ Object protected
-
#duplicate ⇒ Object
Duplicate every held descriptor without resolving the numeric PID again.
- #files ⇒ Object
- #in_cgroup_subtree?(path) ⇒ Boolean
-
#initialize(pid, namespaces: [], root: false) ⇒ ProcessIdentity
constructor
A new instance of ProcessIdentity.
- #initialize_duplicate(source) ⇒ Object protected
- #namespace(name) ⇒ Object
- #open_proc_entry(name) ⇒ Object protected
- #open_proc_file(name, mode = 'r') ⇒ Object
- #proc_open_flags(mode) ⇒ Object protected
- #read_proc_file(name) ⇒ Object
- #same_file?(left, right) ⇒ Boolean protected
- #verify_process_descriptors! ⇒ Object protected
- #verify_root_context! ⇒ Object protected
Constructor Details
#initialize(pid, namespaces: [], root: false) ⇒ ProcessIdentity
Returns a new instance of ProcessIdentity.
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 |
# File 'lib/osctld/process_identity.rb', line 19 def initialize(pid, namespaces: [], root: false) @pid = Integer(pid) raise ArgumentError, 'pid has to be positive' if @pid <= 0 @sys = OsCtl::Lib::Sys.new @pidfd = @sys.pidfd_open(@pid) @pidfd_stat = @pidfd.stat @namespaces = {} @proc_dir = nil @proc_dir_stat = nil @root_dir = nil @proc_dir = File.open(File.join('/proc', @pid.to_s)) @proc_dir_stat = @proc_dir.stat unless @proc_dir_stat.directory? && alive? raise Errno::ESRCH, "process #{@pid} exited" end @root_dir = open_proc_entry('root') if root namespaces.each do |name| ns_name = name.to_sym @namespaces[ns_name] = open_proc_entry(File.join('ns', ns_name.to_s)) end raise Errno::ESRCH, "process #{@pid} exited" unless alive? rescue StandardError close raise end |
Instance Attribute Details
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
6 7 8 |
# File 'lib/osctld/process_identity.rb', line 6 def pid @pid end |
#pidfd ⇒ Object (readonly)
Returns the value of attribute pidfd.
6 7 8 |
# File 'lib/osctld/process_identity.rb', line 6 def pidfd @pidfd end |
#proc_dir ⇒ Object (readonly)
Returns the value of attribute proc_dir.
6 7 8 |
# File 'lib/osctld/process_identity.rb', line 6 def proc_dir @proc_dir end |
#root_dir ⇒ Object (readonly)
Returns the value of attribute root_dir.
6 7 8 |
# File 'lib/osctld/process_identity.rb', line 6 def root_dir @root_dir end |
Class Method Details
.open(pid, namespaces: [], root: false) ⇒ Object
8 9 10 11 12 13 14 15 16 17 |
# File 'lib/osctld/process_identity.rb', line 8 def self.open(pid, namespaces: [], root: false) identity = new(pid, namespaces:, root:) return identity unless block_given? begin yield identity ensure identity.close end end |
Instance Method Details
#alive? ⇒ Boolean
51 52 53 54 55 56 57 58 |
# File 'lib/osctld/process_identity.rb', line 51 def alive? fd = pidfd return false if fd.nil? || fd.closed? @sys.pidfd_alive?(fd) rescue SystemCallError, IOError false end |
#authenticate!(cgroup_path:) ⇒ Object
Recheck that every retained descriptor still describes this live process and that the process remains below the expected cgroup. This is used at the last responsible moment before namespace or mount effects.
72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/osctld/process_identity.rb', line 72 def authenticate!(cgroup_path:) verify_process_descriptors! verify_root_context! if root_dir || @namespaces.any? unless in_cgroup_subtree?(cgroup_path) raise Errno::EXDEV, "process #{pid} left cgroup #{cgroup_path}" end verify_process_descriptors! self end |
#cgroup_entries ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/osctld/process_identity.rb', line 89 def cgroup_entries read_proc_file('cgroup').lines.map do |line| hierarchy, controllers, path = line.strip.split(':', 3) if hierarchy.nil? || controllers.nil? || path.nil? || path.empty? raise ArgumentError, "malformed cgroup entry #{line.inspect}" end { hierarchy:, controllers: controllers.split(','), path: } end end |
#cgroup_paths ⇒ Object
104 105 106 |
# File 'lib/osctld/process_identity.rb', line 104 def cgroup_paths cgroup_entries.map { |entry| entry.fetch(:path) } end |
#close ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/osctld/process_identity.rb', line 139 def close @namespaces&.each_value { |io| close_io(io) } close_io(@root_dir) close_io(@proc_dir) close_io(@pidfd) @namespaces = {} @root_dir = nil @proc_dir = nil @proc_dir_stat = nil @pidfd = nil @pidfd_stat = nil end |
#close_io(io) ⇒ Object (protected)
255 256 257 |
# File 'lib/osctld/process_identity.rb', line 255 def close_io(io) io&.close unless io&.closed? end |
#duplicate ⇒ Object
Duplicate every held descriptor without resolving the numeric PID again. The copy can outlive this identity and remains bound to the same process, proc entry, root, and namespaces.
63 64 65 66 67 |
# File 'lib/osctld/process_identity.rb', line 63 def duplicate copy = self.class.allocate copy.send(:initialize_duplicate, self) copy end |
#files ⇒ Object
135 136 137 |
# File 'lib/osctld/process_identity.rb', line 135 def files [pidfd, proc_dir, root_dir, *@namespaces.values].compact end |
#in_cgroup_subtree?(path) ⇒ Boolean
108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/osctld/process_identity.rb', line 108 def in_cgroup_subtree?(path) prefix = File.absolute_path(path, '/') entries = cgroup_entries return false if entries.empty? entries.all? do |entry| current = File.absolute_path(entry.fetch(:path), '/') current == prefix || current.start_with?("#{prefix}/") end end |
#initialize_duplicate(source) ⇒ Object (protected)
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/osctld/process_identity.rb', line 154 def initialize_duplicate(source) @pid = source.pid @sys = source.instance_variable_get(:@sys) @pidfd = nil @pidfd_stat = source.instance_variable_get(:@pidfd_stat) @proc_dir = nil @proc_dir_stat = source.instance_variable_get(:@proc_dir_stat) @root_dir = nil @namespaces = {} raise Errno::ESRCH, "process #{@pid} exited" unless source.alive? @pidfd = source.pidfd.dup @proc_dir = source.proc_dir.dup @root_dir = source.root_dir ? source.root_dir.dup : nil source.instance_variable_get(:@namespaces).each do |name, io| @namespaces[name] = io.dup end raise Errno::ESRCH, "process #{@pid} exited" unless alive? rescue StandardError close raise end |
#namespace(name) ⇒ Object
85 86 87 |
# File 'lib/osctld/process_identity.rb', line 85 def namespace(name) @namespaces.fetch(name.to_sym) end |
#open_proc_entry(name) ⇒ Object (protected)
219 220 221 222 223 224 225 226 227 |
# File 'lib/osctld/process_identity.rb', line 219 def open_proc_entry(name, **) @sys.openat_io(proc_dir, name, **) rescue SystemCallError, IOError => e raise Errno::ESRCH, "process #{pid} exited", e.backtrace unless alive? raise e.class, "unable to open /proc/#{pid}/#{name} through retained proc descriptor: #{e.}", e.backtrace end |
#open_proc_file(name, mode = 'r') ⇒ Object
120 121 122 123 124 125 126 127 128 129 |
# File 'lib/osctld/process_identity.rb', line 120 def open_proc_file(name, mode = 'r', &) io = open_proc_entry(name, flags: proc_open_flags(mode)) return io unless block_given? begin yield io ensure io.close end end |
#proc_open_flags(mode) ⇒ Object (protected)
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/osctld/process_identity.rb', line 236 def proc_open_flags(mode) case mode when 'r' File::RDONLY when 'w' File::WRONLY | File::TRUNC when 'a' File::WRONLY | File::APPEND when 'r+' File::RDWR when 'w+' File::RDWR | File::TRUNC when 'a+' File::RDWR | File::APPEND else Integer(mode) end end |
#read_proc_file(name) ⇒ Object
131 132 133 |
# File 'lib/osctld/process_identity.rb', line 131 def read_proc_file(name) open_proc_file(name, &:read) end |
#same_file?(left, right) ⇒ Boolean (protected)
229 230 231 232 233 234 |
# File 'lib/osctld/process_identity.rb', line 229 def same_file?(left, right) left_stat = left.respond_to?(:stat) ? left.stat : left right_stat = right.respond_to?(:stat) ? right.stat : right left_stat.dev == right_stat.dev && left_stat.ino == right_stat.ino end |
#verify_process_descriptors! ⇒ Object (protected)
205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/osctld/process_identity.rb', line 205 def verify_process_descriptors! raise Errno::ESRCH, "process #{pid} exited" unless alive? unless same_file?(pidfd, @pidfd_stat) raise Errno::ESTALE, "process #{pid} pidfd was substituted" end unless same_file?(proc_dir, @proc_dir_stat) raise Errno::ESTALE, "process #{pid} proc descriptor was substituted" end raise Errno::ESRCH, "process #{pid} exited" unless alive? end |
#verify_root_context! ⇒ Object (protected)
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 |
# File 'lib/osctld/process_identity.rb', line 179 def verify_root_context! verify_process_descriptors! if root_dir current_root = open_proc_entry('root') unless same_file?(current_root, root_dir) raise Errno::ESTALE, "process #{pid} changed root" end end current_namespaces = [] @namespaces.each do |name, held| current = open_proc_entry(File.join('ns', name.to_s)) current_namespaces << current unless same_file?(current, held) display_name = name == :mnt ? 'mount' : name raise Errno::ESTALE, "process #{pid} changed #{display_name} namespace" end end verify_process_descriptors! ensure current_namespaces&.each(&:close) current_root&.close end |