linux_stat 1.0.3 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -119,6 +119,15 @@ static VALUE getEUID(VALUE obj) {
119
119
  return INT2FIX(geteuid()) ;
120
120
  }
121
121
 
122
+ static VALUE getHostname(VALUE obj) {
123
+ int h_max = sysconf(_SC_HOST_NAME_MAX) + 1 ;
124
+ char hostname[h_max] ;
125
+
126
+ short int status = gethostname(hostname, h_max) ;
127
+
128
+ return (status < 0) ? rb_str_new_cstr("") : rb_str_new_cstr(hostname) ;
129
+ }
130
+
122
131
  void Init_sysconf() {
123
132
  VALUE _linux_stat = rb_define_module("LinuxStat") ;
124
133
  VALUE _sysconf = rb_define_module_under(_linux_stat, "Sysconf") ;
@@ -144,4 +153,6 @@ void Init_sysconf() {
144
153
 
145
154
  rb_define_module_function(_sysconf, "get_user", getUser, 0) ;
146
155
  rb_define_module_function(_sysconf, "get_login", getUser, 0) ;
156
+
157
+ rb_define_module_function(_sysconf, "hostname", getHostname, 0) ;
147
158
  }
@@ -15,8 +15,8 @@
15
15
 
16
16
  # Miscellaneous Modules
17
17
  # Independed and LinuxStat's miscellaneous modules
18
- require "linux_stat/version"
19
18
  require 'linux_stat/prettify_bytes'
19
+ require "linux_stat/version"
20
20
 
21
21
  # Independed Modules
22
22
  # Modules that doesn't have any dependency on its own
@@ -25,6 +25,7 @@ require "linux_stat/battery"
25
25
  require "linux_stat/bios"
26
26
  require "linux_stat/memory"
27
27
  require "linux_stat/net"
28
+ require "linux_stat/pci"
28
29
  require "linux_stat/process"
29
30
  require "linux_stat/swap"
30
31
  require "linux_stat/usb"
@@ -51,14 +51,46 @@ module LinuxStat
51
51
  v[:DISTRIB_DESCRIPTION]
52
52
  elsif v.key?(:DISTRIB_ID)
53
53
  v[:DISTRIB_ID]
54
- elsif File.readable?('/etc/issue')
55
- IO.read('/etc/issue').strip
54
+ elsif File.readable?('/etc/issue'.freeze)
55
+ IO.read('/etc/issue'.freeze, encoding: 'ASCII-8bit').strip
56
56
  else
57
57
  'Unknown'.freeze
58
58
  end
59
59
  end
60
60
  end
61
61
 
62
+ ##
63
+ # Gives you the version of the OS you are using.
64
+ #
65
+ # On ArchLinux for example:
66
+ # LinuxStat::OS.version
67
+ #
68
+ # => "rolling"
69
+ #
70
+ # On Ubuntu 20.04:
71
+ # LinuxStat::OS.version
72
+ #
73
+ # => "20.04"
74
+ #
75
+ # On CentOS 26:
76
+ # LinuxStat::OS.version
77
+ #
78
+ # => "26"
79
+ #
80
+ # The return type is String.
81
+ # But if the information isn't available, it will return an empty frozen String.
82
+ def version
83
+ @@distrib_version ||= if os_release.key?(:VERSION_ID)
84
+ os_release[:VERSION_ID]
85
+ elsif lsb_release.key?(:DISTRIB_RELEASE)
86
+ lsb_release[:DISTRIB_RELEASE]
87
+ elsif os_release.key?(:VERSION)
88
+ os_release[:VERSION]
89
+ else
90
+ ''.freeze
91
+ end
92
+ end
93
+
62
94
  ##
63
95
  # Uses utsname.h to determine the machine
64
96
  #
@@ -70,22 +102,18 @@ module LinuxStat
70
102
  ##
71
103
  # Uses utsname.h to determine the system nodename
72
104
  #
73
- # It returns String but if the info isn't available, it will return an empty String
105
+ # It returns String but if the info isn't available, it will return an empty String.
74
106
  def nodename
75
- @@nodename ||= LinuxStat::Uname.nodename
107
+ LinuxStat::Uname.nodename
76
108
  end
77
109
 
78
110
  ##
79
- # Reads /etc/hostname and returns the hostname.
111
+ # Returns the hostname from LinuxStat::Sysconf.hostname.
80
112
  #
81
113
  # The return type is String.
82
- # If the info info isn't available, it will return 'localhost'.
114
+ # If the info info isn't available, it will return an empty String.
83
115
  def hostname
84
- @@hostname ||= if File.exist?('/etc/hostname')
85
- IO.read('/etc/hostname').strip
86
- else
87
- 'localhost'
88
- end
116
+ LinuxStat::Sysconf.hostname
89
117
  end
90
118
 
91
119
  ##
@@ -114,7 +142,7 @@ module LinuxStat
114
142
  def uptime
115
143
  return {} unless uptime_readable?
116
144
 
117
- uptime = IO.read('/proc/uptime').to_f
145
+ uptime = IO.read('/proc/uptime'.freeze).to_f
118
146
  uptime_i = uptime.to_i
119
147
 
120
148
  h = uptime_i / 3600
@@ -128,16 +156,21 @@ module LinuxStat
128
156
  }.freeze
129
157
  end
130
158
 
159
+ alias distrib_version version
160
+
131
161
  private
132
162
  def release(file)
133
163
  IO.readlines(file, 4096).reduce({}) { |h, x|
134
164
  x.strip!
135
165
  next h if x.empty?
136
166
 
137
- splitted = x.split(?=)
167
+ splitted = x.split(?=.freeze)
168
+
169
+ key = splitted[0].to_s
170
+ key.strip!
138
171
 
139
- key = splitted[0].to_s.strip
140
- value = splitted[1..-1].join(?=).to_s.strip
172
+ value = splitted[1..-1].join(?=.freeze).to_s
173
+ value.strip!
141
174
 
142
175
  value[0] = ''.freeze if value[0] == ?".freeze
143
176
  value[-1] = ''.freeze if value[-1] == ?".freeze
@@ -0,0 +1,366 @@
1
+ module LinuxStat
2
+ module PCI
3
+ class << self
4
+ ##
5
+ # = devices_info(hwdata: true)
6
+ #
7
+ # [ Not to be confused with devices_stat ]
8
+ #
9
+ # Take a look at LinuxStat::PCI.devices_stat for more info.
10
+ #
11
+ # Returns details about the devices found in /proc/bus/pci/devices file.
12
+ #
13
+ # The details doesn't contain a lot of details, it opens just one file.
14
+ #
15
+ # The return value is an Array of multiple Hashes. If there's no info available,
16
+ # it will rather return an empty Array.
17
+ #
18
+ # On Android Termux for example, it can not list the directories because they are
19
+ # not readable the the regular user.
20
+ #
21
+ # It can have information like:
22
+ #
23
+ # id, vendor, device, irq, and kernel_driver
24
+ #
25
+ # An example of the returned sample from a test machine is:
26
+ # LinuxStat::PCI.devices_info
27
+ #
28
+ # => [{:id=>"8086:1904", :vendor=>"8086", :device=>"1904", :irq=>0, :kernel_driver=>"skl_uncore", :hwdata=>{:vendor=>"Intel Corporation", :product=>"Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Host Bridge/DRAM Registers"}}]
29
+ #
30
+ # Right, it's an Array of Hashes.
31
+ #
32
+ # It also takes one option. The hwdata, which is true by default.
33
+ #
34
+ # Information about usb devices is found inside /usr/share/hwdata/pci.ids
35
+ #
36
+ # The data contains the vendor and the product, the subvendor and the subproduct.
37
+ #
38
+ # If the option is enabled, it will try read /usr/share/hwdata/pci.ids
39
+ #
40
+ # But the file will be read only once. The consecutive calls to this method
41
+ # won't open the hwdata all the times.
42
+ #
43
+ # But if there's no hwdata, the Hash returned by this method will not contain
44
+ # hwdata key.
45
+ #
46
+ # The data is only populated if it's available. For example, if there's no
47
+ # manufacturer available for the product, the returned Hash will not contain the
48
+ # information about the manufacturer.
49
+ #
50
+ # Also note that if there's no info available or no PCI enabled devices, it will return an empty
51
+ # Hash.
52
+ def devices_info(hwdata: true)
53
+ @@proc_pci_readable ||= File.readable?('/proc/bus/pci/devices')
54
+ return {} unless @@proc_pci_readable
55
+
56
+ IO.readlines('/proc/bus/pci/devices'.freeze).map! { |dev|
57
+ x = dev.split
58
+
59
+ vendor = x[1][0..3]
60
+ device = x[1][4..-1]
61
+ irq = x[2].to_i(16)
62
+ kernel_driver = x[-1]
63
+
64
+ query = if hwdata
65
+ query_hwdata(vendor, device)
66
+ else
67
+ {}
68
+ end
69
+
70
+ ret = {
71
+ id: "#{vendor}:#{device}",
72
+ vendor: vendor, device: device,
73
+ irq: irq,
74
+ kernel_driver: kernel_driver
75
+ }
76
+
77
+ ret.merge!(hwdata: query) unless query.empty?
78
+
79
+ ret
80
+ }
81
+ end
82
+
83
+ ##
84
+ # = devices_stat(hwdata: true)
85
+ #
86
+ # Returns details about the devices found in /sys/bus/pci/devices/
87
+ #
88
+ # The return value is an Array of multiple Hashes. If there's no info available,
89
+ # it will rather return an empty Array.
90
+ #
91
+ # On Android Termux for example, it can not list the directories because they are
92
+ # not readable the the regular user.
93
+ #
94
+ # It can have information like:
95
+ #
96
+ # path, id, vendor, device, subvendor, sub_device,
97
+ # kernel_driver, revision, irq, enable, hwdata.
98
+ #
99
+ # An example of the returned sample from a test machine is:
100
+ # LinuxStat::PCI.devices_stat
101
+ #
102
+ # => [{:path=>"/sys/bus/pci/devices/0000:00:00.0/", :id=>"8086:1904", :vendor=>"8086", :device=>"1904", :sub_vendor=>"1028", :sub_device=>"077d", :kernel_driver=>"skl_uncore", :revision=>"0x08", :irq=>0, :enable=>false, :hwdata=>{:vendor=>"Intel Corporation", :product=>"Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Host Bridge/DRAM Registers", :sub_system=>nil}}]
103
+ #
104
+ # Right, it's an Array of Hashes.
105
+ #
106
+ # It also takes one option. The hwdata, which is true by default.
107
+ #
108
+ # Information about usb devices is found inside /usr/share/hwdata/pci.ids
109
+ #
110
+ # The data contains the vendor and the product, the subvendor and the subproduct.
111
+ #
112
+ # If the option is enabled, it will try read /usr/share/hwdata/pci.ids
113
+ #
114
+ # But the file will be read only once. The consecutive calls to this method
115
+ # won't open the hwdata all the times.
116
+ #
117
+ # But if there's no hwdata, the Hash returned by this method will not contain
118
+ # hwdata key.
119
+ #
120
+ # The data is only populated if it's available. For example, if there's no
121
+ # manufacturer available for the product, the returned Hash will not contain the
122
+ # information about the manufacturer.
123
+ #
124
+ # If there's no /sys/bus/pci/devices/, it will call LinuxStat::PCI.devices_info
125
+ #
126
+ # Also note that if there's no info available or no PCI enabled devices, it will return an empty
127
+ # Hash.
128
+ def devices_stat(hwdata: true)
129
+ @@sys_pci_readable ||= File.readable?('/sys/bus/pci/devices/')
130
+ return devices_info(hwdata: hwdata) unless @@sys_pci_readable
131
+
132
+ Dir['/sys/bus/pci/devices/*/'.freeze].sort!.map! { |x|
133
+ begin
134
+ _vendor_file = File.join(x, 'vendor'.freeze)
135
+ next unless File.readable?(_vendor_file)
136
+ vendor = IO.read(_vendor_file).to_i(16).to_s(16)
137
+ prepend_0(vendor)
138
+
139
+ _device_file = File.join(x, 'device'.freeze)
140
+ next unless File.readable?(_device_file)
141
+ device = IO.read(_device_file).to_i(16).to_s(16)
142
+ prepend_0(device)
143
+
144
+ _sub_vendor_file = File.join(x, 'subsystem_vendor'.freeze)
145
+ sub_vendor = File.readable?(_sub_vendor_file) ? IO.read(_sub_vendor_file).to_i(16).to_s(16) : nil
146
+ prepend_0(sub_vendor) if sub_vendor
147
+
148
+ _sub_device_file = File.join(x, 'subsystem_device'.freeze)
149
+ sub_device = File.readable?(_sub_device_file) ? IO.read(_sub_device_file).to_i(16).to_s(16) : nil
150
+ prepend_0(sub_device) if sub_device
151
+
152
+ _uevent = File.join(x, 'uevent'.freeze)
153
+ uevent = File.readable?(_uevent) ? IO.foreach(_uevent) : nil
154
+
155
+ kernel_driver = if uevent
156
+ uevent.find { |_x|
157
+ _x.split(?=.freeze)[0].to_s.tap(&:strip!) == 'DRIVER'.freeze
158
+ } &.split(?=) &.[](1) &.tap(&:strip!)
159
+ else
160
+ nil
161
+ end
162
+
163
+ _revision_file = File.join(x, 'revision'.freeze)
164
+ revision = File.readable?(_revision_file) ? IO.read(_revision_file).tap(&:strip!) : ''.freeze
165
+
166
+ _irq_file = File.join(x, 'irq'.freeze)
167
+ irq = File.readable?(_irq_file) ? IO.read(_irq_file).to_i : nil
168
+
169
+ _enable_file = File.join(x, 'enable'.freeze)
170
+ enable = File.readable?(_enable_file) ? IO.read(_enable_file).to_i == 1 : nil
171
+
172
+ query = if hwdata && sub_vendor && sub_device
173
+ query_hwdata(vendor, device, sub_vendor, sub_device)
174
+ elsif hwdata && sub_vendor
175
+ query_hwdata(vendor, device, sub_vendor)
176
+ elsif hwdata
177
+ query_hwdata(vendor, device)
178
+ else
179
+ {}
180
+ end
181
+
182
+ ret = {
183
+ path: x, id: "#{vendor}:#{device}",
184
+ vendor: vendor, device: device
185
+ }
186
+
187
+ ret.merge!(sub_vendor: sub_vendor) if sub_vendor
188
+ ret.merge!(sub_device: sub_device) if sub_device
189
+
190
+ ret.merge!(kernel_driver: kernel_driver) if kernel_driver
191
+ ret.merge!(revision: revision) unless revision.empty?
192
+ ret.merge!(irq: irq) if irq
193
+ ret.merge!(enable: enable) unless enable.nil?
194
+ ret.merge!(hwdata: query) unless query.empty?
195
+
196
+ ret
197
+ rescue StandardError
198
+ end
199
+ }.tap(&:compact!)
200
+ end
201
+
202
+ ##
203
+ # Reads /proc/bus/pci/devices, counts and returns the total number of lines.
204
+ #
205
+ # But if the information isn't available, it will look into the contents of
206
+ # /sys/bus/pci/devices, and counts the total number of
207
+ # devices connected to the PCI.
208
+ # It checks for devices with vendor and device id files.
209
+ # If there's no such file, it will not count them as a PCI connected device.
210
+ #
211
+ # The return type is an integer.
212
+ #
213
+ # But if the information isn't available, it will return nil.
214
+ def count
215
+ @@proc_pci_readable ||= File.readable?('/proc/bus/pci/devices')
216
+ @@sys_pci_readable ||= File.readable?('/sys/bus/pci/devices/')
217
+
218
+ if @@proc_pci_readable
219
+ IO.readlines('/proc/bus/pci/devices'.freeze).length
220
+
221
+ elsif @@sys_pci_readable
222
+ Dir['/sys/bus/pci/devices/*/'.freeze].count { |x|
223
+ id_vendor_file = File.join(x, 'vendor'.freeze)
224
+ id_product_file = File.join(x, 'device'.freeze)
225
+ File.readable?(id_vendor_file) && File.readable?(id_product_file)
226
+ }
227
+
228
+ else
229
+ nil
230
+ end
231
+ end
232
+
233
+ ##
234
+ # hwdata_file = file
235
+ #
236
+ # Lets you set the hwdata_file about pci.ids.
237
+ #
238
+ # The hwdata file about pci.ids contains vendor name and product name information about
239
+ # devices. This is then mapped by the other methods that utilizes hwdata/pci.ids.
240
+ #
241
+ # Do note that this method is intended to run only once, at the beginning.
242
+ # If you use any other method that utilizes hwdata/pci.ids, before
243
+ # calling this method, this method will not work.
244
+ def hwdata_file=(file)
245
+ @@hwdata_file ||= file.freeze
246
+ end
247
+
248
+ ##
249
+ # Checks if hwdata_file is already initialized or not.
250
+ # Once it's initialized, calling hwdata_file = 'something/pci.ids' is futile.
251
+ def hwdata_file_set?
252
+ @@hwdata_file ||= false
253
+ !!@@hwdata_file
254
+ end
255
+
256
+ ##
257
+ # Returns the hwdata_file as string.
258
+ #
259
+ # If hwdata_file isn't set, it will return an empty frozen string.
260
+ #
261
+ # Once it's set, it can't be changed.
262
+ def hwdata_file
263
+ @@hwdata_file ||= nil
264
+ @@hwdata_file ? @@hwdata_file : ''.freeze
265
+ end
266
+
267
+ ##
268
+ # Initializes hwdata
269
+ #
270
+ # hwdata can take upto 0.1 to 0.2 seconds to get initialized.
271
+ #
272
+ # Calling this method will load hwdata for future use.
273
+ #
274
+ # Once it's initialized, hwdata_file can't be changed.
275
+ #
276
+ # If this method initializes hwdata, it will return true
277
+ # Othewise this method will return false.
278
+ def initialize_hwdata
279
+ @@hwdata ||= nil
280
+ init = !@@hwdata
281
+ hwdata
282
+ init
283
+ end
284
+
285
+ alias count_devices count
286
+
287
+ private
288
+ def hwdata
289
+ @@hwdata_file ||= "/usr/share/hwdata/pci.ids".freeze
290
+
291
+ @@hwdata ||= if File.readable?(@@hwdata_file)
292
+ ret, vendor_id, device_id, device = {}, nil, nil, nil
293
+ i = -1
294
+
295
+ file_data = IO.readlines(@@hwdata_file, encoding: 'ASCII-8BIT')
296
+ file_data_size = file_data.size
297
+
298
+ while (i += 1) < file_data_size
299
+ x = file_data[i]
300
+
301
+ _lstripped = x.lstrip
302
+ next if _lstripped[0] == ?#.freeze || _lstripped.empty?
303
+
304
+ if x[0..1] == "\t\t".freeze
305
+ next unless device_id
306
+
307
+ x.strip!
308
+ sub_device_id = x[/\A.*?\s/].to_s.strip
309
+ sub_device = x[device_id.length..-1].to_s.strip
310
+
311
+ if ret[vendor_id] &.at(1) &.[](device_id) &.[](1)
312
+ sub_system_id = sub_device[/\A.*?\s/].to_s.strip
313
+ sub_system_device = sub_device[sub_system_id.length..-1].to_s.strip
314
+
315
+ ret[vendor_id][1][device_id][1].merge!(sub_device_id => {sub_system_id => sub_system_device})
316
+ end
317
+
318
+ elsif x[0] == ?\t.freeze
319
+ next unless vendor_id
320
+
321
+ x.strip!
322
+ device_id = x[/\A.*?\s/].to_s.strip
323
+ device = x[device_id.length..-1].to_s.strip
324
+ ret[vendor_id][1][device_id] = [device, {}]
325
+
326
+ else
327
+ x.strip!
328
+ vendor_id = x[/\A.*?\s/].to_s.strip
329
+ vendor = x[vendor_id.length..-1].to_s.strip
330
+ ret[vendor_id] = [vendor, {}]
331
+ end
332
+ end
333
+
334
+ ret.freeze
335
+ else
336
+ {}
337
+ end
338
+ end
339
+
340
+ def prepend_0(n)
341
+ n[0, 0] = ?0.*(4 - n.length).freeze if n.length < 4
342
+ n
343
+ end
344
+
345
+ def query_hwdata(vendor_id, product_id, sub_vendor_id = nil, sub_device_id = nil)
346
+ vendor = hwdata[vendor_id]
347
+
348
+ if vendor
349
+ ret = {
350
+ vendor: vendor[0],
351
+ product: vendor.dig(1, product_id, 0),
352
+ }
353
+
354
+ if sub_vendor_id && sub_device_id
355
+ sub_product = vendor.dig(1, product_id, 1, sub_vendor_id, sub_device_id)
356
+ ret.merge!(sub_system: sub_product) if sub_product
357
+ end
358
+
359
+ ret
360
+ else
361
+ {}
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end