-
Notifications
You must be signed in to change notification settings - Fork 7
/
parser.rb
168 lines (145 loc) · 4.53 KB
/
parser.rb
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
require "fosl/namespace"
require "fosl/process"
class FOSL::Parser
# Fields are separated by newlines or null
# Fields start with a character followed by the data
# These are the fields according to the lsof manpage.
# If you want to implement one, you should write a 'parse_<field letter>'
# method. It should return a hash of key => value you want to save.
#
# # The following copied mostly verbatim from the lsof manpage.
# - a file access mode
# - c process command name (all characters from proc or user structure)
# - C file structure share count
# - d file's device character code
# - D file's major/minor device number (0x<hexadecimal>)
# - f file descriptor
# - F file structure address (0x<hexadecimal>)
# - G file flaGs (0x<hexadecimal>; names if +fg follows)
# - i file's inode number
# - k link count
# - l file's lock status
# - L process login name
# - m marker between repeated output
# - n file name, comment, Internet address
# - N node identifier (ox<hexadecimal>
# - o file's offset (decimal)
# - p process ID (always selected)
# - g process group ID
# - P protocol name
# - r raw device number (0x<hexadecimal>)
# - R parent process ID
# - s file's size (decimal)
# - S file's stream identification
# - t file's type
# - T TCP/TPI information, identified by prefixes (the
# - u process user ID
# - z Solaris 10 and higher zone name
# - Z SELinux security context (inhibited when SELinux is disabled)
# T is various network/tcp/socket information.
def parse_T(data)
prefix, value = data.split("=")
case prefix
when "ST" ; prefix = :state
when "QR" ; prefix = :read_queue
when "QS" ; prefix = :send_queue
# (sissel) I don't know the values of these fields. Feel free
# to implement them and send me patches.
#when "SO" ; prefix = :socket_options
#when "SS" ; prefix = :socket_State
#when "TF" ; prefix = :tcp_flags
#when "WR" ; prefix = :read_window
#when "WW" ; prefix = :write_window
end
return { prefix => value }
end # def parse_T
# The file's type
def parse_t(data)
return { :type => data }
end
# The protocol name
def parse_P(data)
return { :protocol => data }
end
# the pid
def parse_p(data)
new_pid(data.to_i)
return :new_pid
end
# the file name or identifier
def parse_n(data)
return { :name => data }
end
# file descriptor (or 'cwd' etc...)
def parse_f(data)
new_file
# Convert to int it looks like a number.
if data.to_i != 0 or data == "0"
data = data.to_i
end
return { :fd => data }
end
# The command name
def parse_c(data)
@current_process.command = data
return nil
end
# The login name
def parse_L(data)
@current_process.login = data
return nil
end
# state helper, creates a new process
def new_pid(pid)
new_file # push the last file (if any) onto the last process
@current_process = FOSL::Process.new(pid)
end
# state helper, creates a new file hash
def new_file
if !@current_file.nil? && !@current_file.empty?
@current_process.files << @current_file
end
@current_file = {}
end
# Parse output from an lsof(1) run. You must run
# This output must be from lsof run with this flag '-F Pcfnt0'
def parse(data)
if data[0..0] != "p"
raise "Expected first character to be 'p'. Unexpected data input - #{data[0..30]}..."
end
result = Hash.new { |h,k| h[k] = FOSL::Process.new(k) }
data.split(/[\n\0]/).each do |field|
next if field.empty?
type = field[0 .. 0]
value = field[1 .. -1]
method = "parse_#{type}".to_sym
if self.respond_to?(method)
r = self.send(method, value)
#p field => r
if r.is_a?(Hash)
@current_file.merge!(r)
elsif r == :new_pid
result[@current_process.pid] = @current_process
end
else
$stderr.puts "Unsupported field type '#{type}': #{field.inspect}"
end
end
# push last file
new_file
return result
end # def parse
# Helper for running lsof.
# Returns the same thing as 'parse'
#
# Example:
# lsof("-i :443")
def lsof(args="")
output = `lsof -F PcfntT0L #{args}`
# Should we raise an exception, or just return empty results, on failure?
if $?.exitstatus != 0
raise "lsof exited with status #{$?.exitstatus}"
end
return self.parse(output)
end
end # class FOSL::Parser