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 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
|
#######################################################################
# proctable.rb
#
# A pure Ruby version of sys-proctable for AIX 5.3 or later.
########################################################################
require 'sys/proctable/version'
# The Sys module serves as a namespace only.
module Sys
# The ProcTable class encapsulates process table information.
class ProcTable
class Error < StandardError; end
# There is no constructor
private_class_method :new
private
@fields = [
# --- psinfo_t ---
:flag, # process flags from proc struct p_flag
:flag2, # process flags from proc struct p_flag2
:nlwp, # number of threads in process
#:pad1, # reserved for future use
:uid, # real user id
:euid, # effective user id
:gid, # real group id
:egid, # effective group id
:pid, # unique process id
:ppid, # process id of parent
:pgid, # pid of process group leader
:sid, # session id
:ttydev, # controlling tty device (device #)
:s_ttydev, # controlling tty device name or '-'
:addr, # internal address of proc struct
:size, # process image size in KB (1024) units
:rssize, # resident set size in KB (1024) units
:start, # process start time, time since epoch
:time, # usr+sys cpu time for this process
:cid, # corral id
#:pad2, # reserved for future use
:argc, # initial argument count
:argv, # address of initial argument vector in user process
:envp, # address of initial environment vector in user process
:fname, # last component of exec()ed pathname
:psargs, # initial characters of arg list
#:pad, # reserved for future use
# --- lwpsinfo_t ---
:lwpid, # thread id
#:addr, # internal address of thread
:wchan, # wait addr for sleeping thread
#:flag, # thread flags
:wtype, # type of thread wait
:state, # thread state
:sname, # printable thread state character
:nice, # nice for cpu usage
:pri, # priority, high value = high priority
:policy, # scheduling policy
:clname, # printable scheduling policy string
:onpro, # processor on which thread last ran
:bindpro, # processor to which thread is bound
:ptid, # pthread id
#:pad1, # reserved for future use
#:pad, # reserved for future use
# --- prmap_t ---
:map, # array of prmap_t structures
# --- lwp ---
#:lwp # array of lwp information
# other...
:fd, # array of used file descriptors
:cmd_args, # array of command line arguments
:environ, # hash of environment associated with the process
:cmdline, # joined cmd_args if present, otherwise psargs
:cwd, # current working directory
]
@psinfo_pack_directive = [
'L', # pr_flag
'L', # pr_flag2
'L', # pr_nlwp
'L', # pr__pad1
'Q', # pr_uid
'Q', # pr_euid
'Q', # pr_gid
'Q', # pr_egid
'Q', # pr_pid
'Q', # pr_ppid
'Q', # pr_pgid
'Q', # pr_sid
'Q', # pr_ttydev
'Q', # pr_addr
'Q', # pr_size
'Q', # pr_rssize
'QlL', # pr_start
'QlL', # pr_time
'S', # pr_cid
'S', # pr__pad2
'L', # pr_argc
'Q', # pr_argv
'Q', # pr_envp
'A16', # pr_fname[PRFNSZ]
'A80', # pr_psargs[PRARGSZ]
'Q8', # pr__pad[8]
# --- lwpsinfo_t --- pr_lwp
'Q', # pr_lwpid
'Q', # pr_addr
'Q', # pr_wchan
'L', # pr_flag
'C', # pr_wtype
'c', # pr_state
'A', # pr_sname
'C', # pr_nice
'l', # pr_pri
'L', # pr_policy
'A8', # pr_clname
'l', # pr_onpro
'l', # pr_bindpro
'L', # pr_ptid
'L', # pr__pad1
'Q7' # pr__pad[7]
].join
# --- prmap_t ---
@map_fields = [
:size,
:vaddr,
:mapname,
:off,
:mflags,
:s_mflags,
:pathoff,
:alias,
:gp,
#:pad,
:path,
]
@prmap_pack_directive = [
'Q', # pr_size
'Q', # pr_vaddr
'A64', # pr_mapname[PRMAPSZ]
'Q', # pr_off
'L', # pr_mflags
'L', # pr_pathoff
'Q', # pr_alias
'Q', # pr_gp
'Q8', # pr__pad[8]
].join
# prmap_t pr_mflags
PR_MFLAGS =
[
[ 0x80000000, 'main' ], # MA_MAINEXEC - main executable
[ 0x40000000, 'kernel' ], # MA_KERNTEXT - kernel text
[ 0x00000004, 'read' ], # MA_READ - readable
[ 0x00000002, 'write' ], # MA_WRITE - writable
[ 0x00000001, 'exec' ], # MA_EXEC - executable
[ 0x00000008, 'shared' ], # MA_SHARED - shared memory region
[ 0x00000010, 'heap' ], # MA_BREAK - heap -- grown by brk
[ 0x00000020, 'stack' ], # MA_STACK - stack -- grows on stack faults
]
@devs = {}
Dir['/dev/**/*'].map do |filename|
begin
rdev = File.stat(filename).rdev
rescue
next
end
@devs[rdev] = filename[5..-1] if rdev.nonzero?
end
public
ProcTableStruct = Struct.new("ProcTableStruct", *@fields) do
alias comm fname
end
ProcTableMapStruct = Struct.new("ProcTableMapStruct", *@map_fields)
# In block form, yields a ProcTableStruct for each process entry that you
# have rights to. This method returns an array of ProcTableStruct's in
# non-block form.
#
# If a +pid+ is provided, then only a single ProcTableStruct is yielded or
# returned, or nil if no process information is found for that +pid+.
#
# Example:
#
# # Iterate over all processes
# ProcTable.ps do |proc_info|
# p proc_info
# end
#
# # Print process table information for only pid 1001
# p ProcTable.ps(pid: 1001)
#
def self.ps(**kwargs)
pid = kwargs[:pid]
raise TypeError unless pid.is_a?(Numeric) if pid
array = block_given? ? nil : []
struct = nil
Dir.foreach("/proc") do |file|
next if file =~ /\D/ # Skip non-numeric entries under /proc
# Only return information for a given pid, if provided
if pid
next unless file.to_i == pid
end
# Skip over any entries we don't have permissions to read
next unless File.readable?("/proc/#{file}/psinfo")
psinfo = IO.read("/proc/#{file}/psinfo") rescue next
psinfo_array = psinfo.unpack(@psinfo_pack_directive)
struct = ProcTableStruct.new
struct.flag = psinfo_array[0] # pr_flag
struct.flag2 = psinfo_array[1] # pr_flag2
struct.nlwp = psinfo_array[2] # pr_nlwp
# pr__pad1
struct.uid = psinfo_array[4] # pr_uid
struct.euid = psinfo_array[5] # pr_euid
struct.gid = psinfo_array[6] # pr_gid
struct.egid = psinfo_array[7] # pr_egid
struct.pid = psinfo_array[8] # pr_pid
struct.ppid = psinfo_array[9] # pr_ppid
struct.pgid = psinfo_array[10] # pr_pgid
struct.sid = psinfo_array[11] # pr_sid
struct.ttydev = psinfo_array[12] # pr_ttydev
# convert from 64-bit dev_t to 32-bit dev_t and then map the device
# number to a name
ttydev = struct.ttydev
ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF))
struct.s_ttydev = @devs.has_key?(ttydev) ? @devs[ttydev] : '-'
struct.addr = psinfo_array[13] # pr_addr
struct.size = psinfo_array[14] * 1024 # pr_size
struct.rssize = psinfo_array[15] * 1024 # pr_rssize
struct.start = Time.at(psinfo_array[16], psinfo_array[17]) # pr_start
# skip pr_start.__pad
struct.time = psinfo_array[19] # pr_time
# skip pr_time.tv_nsec and pr_time.__pad
struct.cid = psinfo_array[22] # pr_cid
# skip pr__pad2
struct.argc = psinfo_array[24] # pr_argc
struct.argv = psinfo_array[25] # pr_argv
struct.envp = psinfo_array[26] # pr_envp
struct.fname = psinfo_array[27] # pr_fname
struct.psargs = psinfo_array[28] # pr_psargs
# skip pr__pad
### lwpsinfo_t info
struct.lwpid = psinfo_array[37] # pr_lwpid
# skip pr_addr
struct.wchan = psinfo_array[39] # pr_wchan
# skip pr_flag
struct.wtype = psinfo_array[41] # pr_wtype
struct.state = psinfo_array[42] # pr_state
struct.sname = psinfo_array[43] # pr_sname
struct.nice = psinfo_array[44] # pr_nice
struct.pri = psinfo_array[45] # pr_pri
struct.policy = psinfo_array[46] # pr_policy
struct.clname = psinfo_array[47] # pr_clname
struct.onpro = psinfo_array[48] # pr_onpro
struct.bindpro = psinfo_array[49] # pr_bindpro
struct.ptid = psinfo_array[50] # pr_ptid
# skip pr__pad1
# skip pr__pad
# Get the full command line out of /proc/<pid>/as.
begin
File.open("/proc/#{file}/as", 'rb') do |fd|
np = fd.sysseek(struct.argv, IO::SEEK_SET)
if np != struct.argv
raise Error, "argv seek to #{struct.argv}, result #{np}", caller
end
argv = fd.sysread(4).unpack('L')[0]
np = fd.sysseek(argv, IO::SEEK_SET)
if np != argv
raise Error, "*argv seek to #{argv}, result #{np}", caller
end
argv = fd.sysread(4 * struct.argc).unpack("L#{struct.argc}")
struct.cmd_args = []
argv.each_with_index do |address, i|
np = fd.sysseek(address, IO::SEEK_SET)
if np != address
raise Error, "argv[#{i}] seek to #{address}, result #{np}",
caller
end
data = fd.sysread(512)[/^[^\0]*/] # Null strip
struct.cmd_args << data
end
# Get the environment hash associated with the process.
struct.environ = {}
# First have to go to the address given by struct.envp. That will
# give us the address of the environment pointer array.
np = fd.sysseek(struct.envp, IO::SEEK_SET)
if np != struct.envp
raise Error, "envp seek to #{struct.envp}, result #{np}", caller
end
envloc = fd.sysread(4).unpack('L')[0]
n = 0
loop do
np = fd.sysseek(envloc, IO::SEEK_SET)
if np != envloc
raise Error, "envp[#{n}] seek to #{envloc}, result #{np}",
caller
end
envp = fd.sysread(4).unpack("L")[0]
break if envp.zero?
np = fd.sysseek(envp, IO::SEEK_SET)
data = fd.sysread(1024)[/^[^\0]*/] # Null strip
key, value = data.split('=')
struct.environ[key] = value
envloc += 4
n += 1
end
end
rescue Errno::EACCES, Errno::EOVERFLOW, EOFError
# Skip this if we don't have proper permissions, if there's
# no associated environment, or if there's a largefile issue.
rescue Errno::ENOENT
next # The process has terminated. Bail out!
end
# Information from /proc/<pid>/fd. This returns an array of
# numeric file descriptors used by the process.
struct.fd = Dir["/proc/#{file}/fd/*"].map { |f| File.basename(f).to_i }
# Use the cmd_args as the cmdline if available. Otherwise use
# the psargs. This struct member is provided to provide a measure
# of consistency with the other platform implementations.
if struct.cmd_args.nil? || struct.cmd_args.empty?
struct.cmdline = struct.psargs
else
struct.cmdline = struct.cmd_args.join(' ')
end
# get current working directory from /proc/<pid>/cwd
struct.cwd = File.readlink("/proc/#{file}/cwd") rescue nil
# get virtual address map from /proc/<pid>/map
begin
struct.map = []
File.open("/proc/#{file}/map", 'rb') do |fd|
loop do
prmap_array = fd.sysread(176).unpack(@prmap_pack_directive)
break if prmap_array[0].zero?
map_struct = ProcTableMapStruct.new
map_struct.size = prmap_array[0] # pr_size
map_struct.vaddr = prmap_array[1] # pr_vaddr
map_struct.mapname = prmap_array[2] # pr_mapname
map_struct.off = prmap_array[3] # pr_off
map_struct.mflags = prmap_array[4] # pr_mflags
# convert pr_mflags value to string sort of like procmap outputs
mflags = map_struct.mflags
map_struct.s_mflags = ''
sep = ''
PR_MFLAGS.each do |flag|
if (mflags & flag[0]).nonzero?
map_struct.s_mflags << sep << flag[1]
sep = '/'
mflags &= ~flag[0]
end
end
if mflags.nonzero?
map_struct.s_mflags << sep << sprintf('%08x', mflags)
end
map_struct.pathoff = prmap_array[5] # pr_pathoff
map_struct.alias = prmap_array[6] # pr_alias
map_struct.gp = prmap_array[7] # pr_gp
struct.map << map_struct
end
struct.map.each do |m|
next if m.pathoff.zero?
fd.sysseek(m.pathoff, IO::SEEK_SET)
buf = fd.sysread(4096)
buf =~ /^([^\0]*)\0([^\0]*)\0/
m.path = $2.empty? ? $1 : "#{$1}(#{$2})"
end
end
struct.map = nil if struct.map.empty?
rescue
struct.map = nil
end
# This is read-only data
struct.freeze
if block_given?
yield struct
else
array << struct
end
end
pid ? struct : array
end
# Returns an array of fields that each ProcTableStruct will contain. This
# may be useful if you want to know in advance what fields are available
# without having to perform at least one read of the /proc table.
#
# Example:
#
# Sys::ProcTable.fields.each do |field|
# puts "Field: #{field}"
# end
#
def self.fields
@fields.map{ |f| f.to_s }
end
end
end
|