]> git.openstreetmap.org Git - chef.git/blob - cookbooks/hardware/templates/default/ohai.rb.erb
5854de82dac04c0cc6b50d4692e30baf0d161588
[chef.git] / cookbooks / hardware / templates / default / ohai.rb.erb
1 require "pathname"
2
3 Ohai.plugin(:Hardware) do
4   provides "hardware"
5
6   def read_sysctl_link(file)
7     File.basename(File.readlink(file))
8   rescue Errno::ENOENT
9   end
10
11   def read_sysctl_file(file)
12     IO.read(file).strip
13   rescue Errno::ENOENT, Errno::EINVAL
14   end
15
16   def parse_memory_size(size)
17     if size =~ /^(\d+(?:\.\d+)?)\s*TB/i
18       Regexp.last_match(1).to_f * 2**30
19     elsif size =~ /^(\d+(?:\.\d+)?)\s*GB/i
20       Regexp.last_match(1).to_f * 2**20
21     elsif size =~ /^(\d+(?:\.\d+)?)\s*MB/i
22       Regexp.last_match(1).to_f * 2**10
23     end
24   end
25
26   def format_disk_size(kb)
27     if kb == 0
28       ""
29     else
30       kblog10 = Math.log10(kb).floor
31
32       kb = kb.to_f * 2 / 10**kblog10
33       kb = kb.round.to_f / 2
34
35       if kblog10 >= 9
36         format "%gTB", kb * 10**(kblog10 - 9)
37       elsif kblog10 >= 6
38         format "%dGB", kb * 10**(kblog10 - 6)
39       else
40         format "%dMB", kb * 10**(kblog10 - 3)
41       end
42     end
43   end
44
45   def memory_to_disk_size(size)
46     format_disk_size(parse_memory_size(size))
47   end
48
49   def find_sas_device(address)
50     file = Dir.glob("/sys/class/scsi_generic/sg*/device/sas_address").find do |file|
51       read_sysctl_file(file) == "0x#{address}"
52     end
53
54     if file
55       dir = File.dirname(file)
56       device = Dir.glob("#{dir}/block/*").first ||
57                Dir.glob("#{dir}/scsi_generic/*").first
58
59       "/dev/#{File.basename(device)}"
60     end
61   end
62
63   def pci_devices
64     device = nil
65
66     IO.popen(["lspci", "-Dkvmm"]).each_with_object(Mash.new) do |line, devices|
67       if line =~ /^Slot:\s+((\h{4}):(\h{2}):(\h{2}).(\h))\s*$/
68         device = {
69           :slot => Regexp.last_match(1),
70           :domain => Regexp.last_match(2),
71           :bus => Regexp.last_match(3),
72           :device => Regexp.last_match(4),
73           :function => Regexp.last_match(5)
74         }
75       elsif device && line =~ /^([A-Z]+):\s+(.*)\s*$/i
76         case Regexp.last_match(1)
77         when "Class" then device[:class_name] = Regexp.last_match(2)
78         when "Vendor" then device[:vendor_name] = Regexp.last_match(2)
79         when "Device" then device[:device_name] = Regexp.last_match(2)
80         when "SVendor" then device[:subsystem_vendor_name] = Regexp.last_match(2)
81         when "SDevice" then device[:subsystem_device_name] = Regexp.last_match(2)
82         when "PhySlot" then device[:physical_slot] = Regexp.last_match(2)
83         when "Rev" then device[:revision] = Regexp.last_match(2)
84         when "ProgIf" then device[:programming_interface] = Regexp.last_match(2)
85         when "Driver" then device[:driver] = Regexp.last_match(2)
86         when "Module" then device[:modules] = Array(device[:modules]) << Regexp.last_match(2)
87         end
88       elsif device && line =~ /^\s*$/
89         devices[device[:slot]] = device
90         device = nil
91       end
92     end
93   end
94
95   def network_devices
96     Dir.glob("/sys/class/net/*").each_with_object(Mash.new) do |device, devices|
97       name = File.basename(device)
98
99       devices[name] = {
100         :device => read_sysctl_link("#{device}/device"),
101         :duplex => read_sysctl_file("#{device}/duplex"),
102         :speed => read_sysctl_file("#{device}/speed")
103       }.delete_if { |_, v| v.nil? }
104     end
105   end
106
107   def memory_devices
108     device = nil
109
110     IO.popen(["dmidecode", "-t", "memory"]).each_with_object([]) do |line, devices|
111       if line =~ /^Memory Device\s*$/
112         device = {}
113       elsif device && line =~ /^\s+([A-Z ]+):\s+(.*)\s*$/i
114         device[Regexp.last_match(1).tr(" ", "_").downcase.to_sym] = Regexp.last_match(2).strip
115       elsif device && line =~ /^\s*$/
116         devices << device
117         device = nil
118       end
119     end
120   end
121
122   def disk_devices
123     disk = Mash.new
124
125     disk[:controllers] = []
126     disk[:arrays] = []
127     disk[:disks] = []
128
129     find_direct_disks(disk)
130     find_nvme_disks(disk)
131
132     find_hp_disks(disk) if File.exist?("/usr/sbin/hpssacli")
133     find_megaraid_disks(disk) if File.exist?("/usr/sbin/megacli")
134     find_mpt_disks(disk) if File.exist?("/usr/sbin/sas2ircu")
135     find_adaptec_disks(disk) if File.exist?("/usr/sbin/arcconf")
136     find_areca_disks(disk) if File.exist?("/opt/areca/x86_64/cli64")
137
138     find_md_arrays(disk)
139
140     disk
141   end
142
143   def find_direct_disks(devices)
144     Dir.glob("/sys/class/scsi_host/host*") do |host|
145       driver = read_sysctl_file("#{host}/proc_name")
146
147       if driver == "ahci" || driver == "mptsas"
148         bus = host.sub("/sys/class/scsi_host/host", "")
149
150         Dir.glob("/sys/bus/scsi/devices/#{bus}:0:*").each do |device|
151           next unless File.exist?("#{device}/scsi_disk")
152
153           block = Dir.glob("#{device}/block/*").first
154           vendor = read_sysctl_file("#{device}/vendor")
155           model = read_sysctl_file("#{device}/model")
156           size = read_sysctl_file("#{block}/size").to_f / 2
157
158           if vendor == "ATA" && model =~ /^(\S+)\s+(.*)$/
159             vendor = Regexp.last_match(1)
160             model = Regexp.last_match(2)
161           end
162
163           devices[:disks] << {
164             :id => devices[:disks].count,
165             :device => "/dev/#{File.basename(block)}",
166             :vendor => vendor,
167             :model => model,
168             :firmware_version => read_sysctl_file("#{device}/rev"),
169             :size => format_disk_size(size),
170             :arrays => []
171           }
172         end
173       end
174     end
175   end
176
177   def find_nvme_disks(devices)
178     Dir.glob("/sys/class/misc/nvme*") do |device|
179       controller = {
180         :id => devices[:controllers].count,
181         :pci_slot => File.basename(Pathname.new("#{device}/device").realpath),
182         :arrays => [],
183         :disks => []
184       }
185
186       devices[:controllers] << controller
187
188       IO.popen(["lspci", "-Dkvmm", "-s", controller[:pci_slot]]).each do |line|
189         if line =~ /^SVendor:\s+(\S.*\S)\s*$/
190           controller[:vendor] = Regexp.last_match(1)
191         elsif line =~ /^SDevice:\s+(\S.*\S)\s*$/
192           controller[:model] = Regexp.last_match(1)
193         end
194       end
195
196       Dir.glob("#{device}/device/block/*").each do |block|
197         size = read_sysctl_file("#{block}/size").to_f / 2
198
199         disk = {
200           :id => devices[:disks].count,
201           :controller => controller[:id],
202           :device => "/dev/#{File.basename(block)}",
203           :vendor => controller[:vendor],
204           :model => controller[:model],
205           :size => format_disk_size(size),
206           :arrays => []
207         }
208
209         devices[:disks] << disk
210         controller[:disks] << disk[:id]
211       end
212     end
213   end
214
215   def find_md_arrays(devices)
216     array = nil
217
218     File.new("/proc/mdstat", "r").each do |line|
219       if line =~ /^(md\d+) : active raid(\d+)((?: (?:sd[a-z]|nvme\d+n\d+)\d*\[\d+\](?:\([A-Z]\))*)+)$/
220         array = {
221           :id => devices[:arrays].count,
222           :device => "/dev/#{Regexp.last_match(1)}",
223           :raid_level => Regexp.last_match(2),
224           :disks => []
225         }
226
227         Regexp.last_match(3).scan(/ (sd[a-z]+|nvme\d+n\d+)\d*\[\d+\](?:\([A-Z]\))*/).flatten.each do |device|
228           if disk = devices[:disks].find { |d| d[:device] == "/dev/#{device}" }
229             disk[:arrays] << array[:id]
230             array[:disks] << disk[:id]
231           end
232         end
233
234         devices[:arrays] << array
235       elsif array && line =~ /^\s+(\d+) blocks/
236         array[:size] = format_disk_size(Regexp.last_match(1).to_i)
237       end
238     end
239   end
240
241   def find_hp_disks(devices)
242     controllers = []
243     disks = []
244
245     controller = nil
246     array = nil
247     disk = nil
248
249     IO.popen(%w(hpssacli controller all show config detail)).each do |line|
250       if line =~ /^Smart Array (\S+) /
251         controller = {
252           :id => devices[:controllers].count,
253           :model => Regexp.last_match(1),
254           :arrays => [],
255           :disks => []
256         }
257
258         devices[:controllers] << controller
259
260         controllers << controller
261
262         array = nil
263         disk = nil
264       elsif controller && line =~ /^   (\S.*):\s+(.*)$/
265         case Regexp.last_match(1)
266         when "Serial Number" then controller[:serial_number] = Regexp.last_match(2)
267         when "Hardware Revision" then controller[:hardware_version] = Regexp.last_match(2)
268         when "Firmware Version" then controller[:firmware_version] = Regexp.last_match(2)
269         when "PCI Address (Domain:Bus:Device.Function)" then controller[:pci_slot] = Regexp.last_match(2)
270         end
271       elsif controller && line =~ /^      Logical Drive: (\d+)$/
272         array = {
273           :id => devices[:arrays].count,
274           :controller => controller[:id],
275           :number => Regexp.last_match(1),
276           :disks => []
277         }
278
279         devices[:arrays] << array
280         controller[:arrays] << array[:id]
281
282         disk = nil
283       elsif controller && line =~ /^      physicaldrive (\S+) /
284         disks << Regexp.last_match(1)
285       elsif array && line =~ /^      physicaldrive (\S+)$/
286         disk = {
287           :id => devices[:disks].count,
288           :controller => controller[:id],
289           :arrays => [array[:id]],
290           :location => Regexp.last_match(1),
291           :smart_device => "cciss,#{disks.find_index(Regexp.last_match(1))}"
292         }
293
294         devices[:disks] << disk
295         controller[:disks] << disk[:id]
296         array[:disks] << disk[:id]
297       elsif disk && line =~ /^         (\S[^:]+):\s+(.*)$/
298         case Regexp.last_match(1)
299         when "Interface Type" then disk[:interface] = Regexp.last_match(2)
300         when "Size" then disk[:size] = Regexp.last_match(2)
301         when "Rotational Speed" then disk[:rpm] = Regexp.last_match(2)
302         when "Firmware Revision" then disk[:firmware_version] = Regexp.last_match(2)
303         when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
304         when "Model" then disk[:vendor], disk[:model] = Regexp.last_match(2).squeeze(" ").strip.sub(/^ATA /, "").split
305         end
306       elsif array && line =~ /^         (\S[^:]+):\s+(.*)$/
307         case Regexp.last_match(1)
308         when "Size" then array[:size] = Regexp.last_match(2)
309         when "Fault Tolerance" then array[:raid_level] = Regexp.last_match(2)
310         when "Disk Name" then array[:device] = Regexp.last_match(2).strip
311         when "Mount Points" then array[:mount_point] = Regexp.last_match(2).split.first
312         when "Unique Identifier" then array[:wwn] = Regexp.last_match(2)
313         end
314       end
315     end
316
317     controllers.each do |controller|
318       if device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/cciss*").first
319         controller[:device] = File.basename(device).sub(/^cciss(\d+)$/, "/dev/cciss/c\\1d0")
320       elsif device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/host*/target0:0:0/0:0:0:0/scsi_generic/sg*").first
321         controller[:device] = "/dev/#{File.basename(device)}"
322       end
323     end
324   end
325
326   def find_megaraid_disks(devices)
327     controllers = []
328     arrays = []
329
330     controller = nil
331     array = nil
332     disk = nil
333
334     IO.popen(%w(megacli -AdpGetPciInfo -aAll -NoLog)).each do |line|
335       if line =~ /^PCI information for Controller (\d+)$/
336         controller = {
337           :id => devices[:controllers].count,
338           :arrays => [],
339           :disks => []
340         }
341
342         devices[:controllers] << controller
343
344         controllers << controller
345       elsif line =~ /^Bus Number\s+:\s+(\d+)$/
346         controller[:pci_slot] = format "0000:%02x", Integer("0x#{Regexp.last_match(1)}")
347       elsif line =~ /^Device Number\s+:\s+(\d+)$/
348         controller[:pci_slot] = format "%s:%02x", controller[:pci_slot], Integer("0x#{Regexp.last_match(1)}")
349       elsif line =~ /^Function Number\s+:\s+(\d+)$/
350         controller[:pci_slot] = format "%s.%01x", controller[:pci_slot], Integer("0x#{Regexp.last_match(1)}")
351       end
352     end
353
354     IO.popen(%w(megacli -AdpAllInfo -aAll -NoLog)).each do |line|
355       if line =~ /^Adapter #(\d+)$/
356         controller = controllers[Regexp.last_match(1).to_i]
357       elsif line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
358         case Regexp.last_match(1)
359         when "Product Name" then controller[:model] = Regexp.last_match(2)
360         when "Serial No" then controller[:serial_number] = Regexp.last_match(2)
361         when "FW Package Build" then controller[:firmware_version] = Regexp.last_match(2)
362         end
363       end
364     end
365
366     IO.popen(%w(megacli -LdPdInfo -aAll -NoLog)).each do |line|
367       if line =~ /^Adapter #(\d+)$/
368         controller = controllers[Regexp.last_match(1).to_i]
369       elsif controller && line =~ /^Virtual Drive: (\d+) \(Target Id: (\d+)\)$/
370         pci_slot = controller[:pci_slot]
371         target = Regexp.last_match(2)
372         device = Dir.glob("/sys/bus/pci/devices/#{pci_slot}/host*/target*:2:#{target}/*:2:#{target}:0/block/*").first
373
374         array = {
375           :id => devices[:arrays].count,
376           :controller => controller[:id],
377           :number => Regexp.last_match(1),
378           :device => "/dev/#{File.basename(device)}",
379           :disks => []
380         }
381
382         devices[:arrays] << array
383         controller[:arrays] << array[:id]
384
385         arrays << array
386
387         disk = nil
388       elsif array && line =~ /^PD: (\d+) Information$/
389         disk = {
390           :id => devices[:disks].count,
391           :controller => controller[:id],
392           :arrays => [array[:id]]
393         }
394
395         devices[:disks] << disk
396         controller[:disks] << disk[:id]
397         array[:disks] << disk[:id]
398       elsif disk && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
399         case Regexp.last_match(1)
400         when "Device Id" then disk[:smart_devlce] = "megaraid,#{Regexp.last_match(2)}"
401         when "WWN" then disk[:wwn] = Regexp.last_match(2)
402         when "PD Type" then disk[:interface] = Regexp.last_match(2)
403         when "Raw Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2).sub(/\s*\[.*\]$/, ""))
404         when "Inquiry Data" then disk[:vendor], disk[:model], disk[:serial] = Regexp.last_match(2).split
405         end
406       elsif array && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
407         case Regexp.last_match(1)
408         when "RAID Level" then array[:raid_level] = Regexp.last_match(2).scan(/Primary-(\d+)/).first.first
409         when "Size" then array[:size] = Regexp.last_match(2)
410         end
411       end
412     end
413
414     IO.popen(%w(megacli -PDList -aAll -NoLog)).each do |line|
415       if line =~ /^Adapter #(\d+)$/
416         controller = controllers[Regexp.last_match(1).to_i]
417       elsif controller && line =~ /^Enclosure Device ID: \d+$/
418         disk = {
419           :controller => controller[:id]
420         }
421       elsif disk && line =~ /^WWN:\s+(\S+)$/
422         unless devices[:disks].find { |d| d[:wwn] == Regexp.last_match(1) }
423           disk[:id] = devices[:disks].count
424           disk[:wwn] = Regexp.last_match(1)
425
426           devices[:disks] << disk
427         end
428       elsif disk && line =~ /^(\S.*\S)\s*:\s+(\S.*)$/
429         case Regexp.last_match(1)
430         when "Device Id" then disk[:smart_devlce] = "megaraid,#{Regexp.last_match(2)}"
431         when "WWN" then disk[:wwn] = Regexp.last_match(2)
432         when "PD Type" then disk[:interface] = Regexp.last_match(2)
433         when "Raw Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2).sub(/\s*\[.*\]$/, ""))
434         when "Inquiry Data" then disk[:vendor], disk[:model], disk[:serial] = Regexp.last_match(2).split
435         end
436       end
437     end
438
439     controllers.each do |controller|
440       if device = Dir.glob("/sys/bus/pci/devices/#{controller[:pci_slot]}/host*/target*:2:0/*/scsi_generic/sg*").first
441         controller[:device] = "/dev/#{File.basename(device)}"
442       end
443     end
444   end
445
446   def find_mpt_disks(devices)
447     controllers = []
448
449     IO.popen(%w(sas2ircu list)).each do |line|
450       next unless line =~ /^\s+(\d+)\s+(\S+)\s+\h+h\s+\h+h\s+(\S+)\s+\h+h\s+\h+h\s*$/
451       controllers[Regexp.last_match(1).to_i] = {
452         :id => devices[:controllers].count,
453         :model => Regexp.last_match(2),
454         :pci_slot => Regexp.last_match(3).sub(/^(\h{2})h:(\h{2})h:(\h{2})h:0(\h)h$/, "00\\1:\\2:\\3.\\4"),
455         :arrays => [],
456         :disks => []
457       }
458
459       devices[:controllers] << controllers[Regexp.last_match(1).to_i]
460     end
461
462     controllers.each_with_index do |controller, index|
463       arrays = []
464       disks = []
465
466       array = nil
467       disk = nil
468
469       IO.popen(["sas2ircu", index.to_s, "display"]).each do |line|
470         if line =~ /^IR volume (\d+)$/
471           array = {
472             :id => devices[:arrays].count,
473             :controller => controller[:id],
474             :number => Regexp.last_match(1),
475             :disks => []
476           }
477
478           devices[:arrays] << array
479           controller[:arrays] << array[:id]
480
481           arrays << array
482         elsif line =~ /^Device is a Hard disk$/
483           disk = {
484             :id => devices[:disks].count,
485             :controller => controller[:id],
486             :arrays => []
487           }
488
489           devices[:disks] << disk
490           controller[:disks] << disk[:id]
491
492           disks << disk
493         elsif disk && line =~ /^  (\S.*\S)\s+:\s+(.*\S)\s*$/
494           case Regexp.last_match(1)
495           when "Enclosure #" then disk[:location] = Regexp.last_match(2)
496           when "Slot #" then disk[:location] = "#{disk[:location]}:#{Regexp.last_match(2)}"
497           when "SAS Address" then disk[:device] = find_sas_device(Regexp.last_match(2).tr("-", ""))
498           when "Size (in MB)/(in sectors)" then disk[:size] = memory_to_disk_size("#{Regexp.last_match(2).split('/').first} MB")
499           when "Manufacturer" then disk[:vendor] = Regexp.last_match(2)
500           when "Model Number" then disk[:model] = Regexp.last_match(2)
501           when "Firmware Revision" then disk[:firmware_version] = Regexp.last_match(2)
502           when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
503           when "Protocol" then disk[:interface] = Regexp.last_match(2)
504           end
505         elsif array && line =~ /^  PHY\[\d+\] Enclosure#\/Slot#\s+:\s+(\d+:\d+)\s*$/
506           array[:disks] << Regexp.last_match(1)
507         elsif array && line =~ /^  (\S.*\S)\s+:\s+(.*\S)\s*$/
508           case Regexp.last_match(1)
509           when "Volume wwid" then array[:device] = find_sas_device(Regexp.last_match(2))
510           when "RAID level" then array[:raid_level] = Regexp.last_match(2).sub(/^RAID/, "")
511           when "Size (in MB)" then array[:size] = "#{Regexp.last_match(2)} MB"
512           end
513         elsif line =~ /^  (\S.*\S)\s+:\s+(.*\S)\s*$/
514           case Regexp.last_match(1)
515           when "BIOS version" then controller[:bios_version] = Regexp.last_match(2)
516           when "Firmware version" then controller[:firmware_version] = Regexp.last_match(2)
517           end
518         end
519       end
520
521       arrays.each do |array|
522         array[:disks].map! do |location|
523           disk = disks.find { |disk| disk[:location] == location }
524
525           disk[:arrays] << array[:id]
526           disk[:id]
527         end
528       end
529     end
530   end
531
532   def find_adaptec_disks(devices)
533     controller_count = IO.popen(%w(arcconf getconfig 0)).first.scan(/^Controllers found: (\d+)$/).first.first.to_i
534
535     1.upto(controller_count).each do |controller_number|
536       controller = {
537         :id => devices[:controllers].count,
538         :number => controller_number,
539         :arrays => [],
540         :disks => []
541       }
542
543       devices[:controllers] << controller
544
545       arrays = []
546       disks = []
547
548       array = nil
549       disk = nil
550
551       IO.popen(["arcconf", "getconfig", controller_number.to_s]).each do |line|
552         if line =~ /^Logical device number (\d+)$/
553           array = {
554             :id => devices[:arrays].count,
555             :controller => controller[:id],
556             :number => Regexp.last_match(1).to_i,
557             :disks => []
558           }
559
560           devices[:arrays] << array
561           controller[:arrays] << array[:id]
562
563           arrays << array
564         elsif line =~ /^      Device #(\d+)$/
565           disk = nil
566         elsif line =~ /^         Device is a Hard drive$/
567           disk = {
568             :id => devices[:disks].count,
569             :controller => controller[:id],
570             :arrays => []
571           }
572
573           devices[:disks] << disk
574           controller[:disks] << disk[:id]
575
576           disks << disk
577         elsif disk && line =~ /^         Reported Channel,Device\(T:L\)\s*:\s+(\d+),(\d+)\(\d+:0\)\s*$/
578           disk[:channel_number] = Regexp.last_match(1)
579           disk[:device_number] = Regexp.last_match(1)
580         elsif disk && line =~ /^         (\S.*\S)\s*:\s+(\S.*\S)\s*$/
581           case Regexp.last_match(1)
582           when "Reported Location" then disk[:location] = Regexp.last_match(2)
583           when "Vendor" then disk[:vendor] = Regexp.last_match(2)
584           when "Model" then disk[:model] = Regexp.last_match(2)
585           when "Firmware" then disk[:firmware_version] = Regexp.last_match(2)
586           when "Serial" then disk[:serial_number] = Regexp.last_match(2)
587           when "World-wide name" then disk[:wwn] = Regexp.last_match(2)
588           when "Total Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2))
589           when "Size" then disk[:size] = memory_to_disk_size(Regexp.last_match(2))
590           end
591         elsif array && line =~ / Present \(Controller:\d+,((?:Connector|Enclosure):\d+,(?:Device|Slot):\d+)\) /
592           array[:disks] << Regexp.last_match(1).tr(":", " ").gsub(",", ", ")
593         elsif array && line =~ /^   (\S.*\S)\s*:\s+(\S.*\S)\s*$/
594           case Regexp.last_match(1)
595           when "RAID level" then array[:raid_level] = Regexp.last_match(2)
596           when "Size" then array[:size] = memory_to_disk_size(Regexp.last_match(2))
597           end
598         elsif line =~ /^   (\S.*\S)\s*:\s+(\S.*\S)\s*$/
599           case Regexp.last_match(1)
600           when "Controller Model" then controller[:model] = Regexp.last_match(2)
601           when "Controller Serial Number" then controller[:serial_number] = Regexp.last_match(2)
602           when "Controller World Wide Name" then controller[:wwn] = Regexp.last_match(2)
603           when "BIOS" then controller[:bios_version] = Regexp.last_match(2)
604           when "Firmware" then controller[:firmware_version] = Regexp.last_match(2)
605           end
606         end
607       end
608
609       host = Dir.glob("/sys/class/scsi_host/host*").find do |host|
610         read_sysctl_file("#{host}/serial_number") == controller[:serial_number]
611       end
612
613       arrays.each do |array|
614         array_number = array[:number]
615         device = Dir.glob("#{host}/device/target*:0:#{array_number}/*:0:#{array_number}:0/block/*").first
616
617         array[:device] = "/dev/#{File.basename(device)}"
618
619         array[:disks].map! do |location|
620           disk = disks.find { |disk| disk[:location] == location }
621
622           device_number = disk[:device_number]
623           device = Dir.glob("#{host}/device/target*:1:#{device_number}/*:1:#{device_number}:0/scsi_generic/*").first
624
625           disk[:device] = "/dev/#{File.basename(device)}"
626
627           disk[:arrays] << array[:id]
628           disk[:id]
629         end
630       end
631     end
632   end
633
634   def find_areca_disks(devices)
635     controller = {
636       :id => devices[:controllers].count,
637       :arrays => [],
638       :disks => []
639     }
640
641     devices[:controllers] << controller
642
643     IO.popen(%w(/opt/areca/x86_64/cli64 sys info)).each do |line|
644       next unless line =~ /^(\S.*\S)\s+:\s+(.*\S)\s*$/
645
646       case Regexp.last_match(1)
647       when "Firmware Version" then controller[:firmware_version] = Regexp.last_match(2)
648       when "BOOT ROM Version" then controller[:bios_version] = Regexp.last_match(2)
649       when "Serial Number" then controller[:serial_number] = Regexp.last_match(2)
650       when "Controller Name" then controller[:model] = Regexp.last_match(2)
651       end
652     end
653
654     path = Dir.glob("/sys/bus/pci/devices/*/host*/scsi_host/host*/host_fw_model").find do |file|
655       read_sysctl_file(file) == controller[:model]
656     end
657
658     controller[:pci_slot] = File.basename(File.expand_path("#{path}/../../../.."))
659     controller[:device] = File.basename(Dir.glob(File.expand_path("#{path}/../../../target0:0:16/0:0:16:0/scsi_generic/*")).first)
660
661     arrays = []
662
663     IO.popen(%w(/opt/areca/x86_64/cli64 vsf info)).each do |line|
664       next unless line =~ /^\s+(\d+)\s+/
665       array = {
666         :id => devices[:arrays].count,
667         :number => Regexp.last_match(1),
668         :controller => controller[:id],
669         :disks => []
670       }
671
672       devices[:arrays] << array
673       controller[:arrays] << array[:id]
674
675       arrays << array
676     end
677
678     arrays.each do |array|
679       IO.popen(["/opt/areca/x86_64/cli64", "vsf", "info", "vol=#{array[:number]}"]).each do |line|
680         if line =~ /^SCSI Ch\/Id\/Lun\s+:\s+(\d+)\/(\d+)\/(\d+)\s*$/
681           pci_slot = controller[:pci_slot]
682           channel = Regexp.last_match(1).to_i
683           id = Regexp.last_match(2).to_i
684           lun = Regexp.last_match(3).to_i
685
686           device = Dir.glob("/sys/bus/pci/devices/#{pci_slot}/host*/target*:0:0/0:#{channel}:#{id}:#{lun}/block/*").first
687
688           array[:device] = "/dev/#{File.basename(device)}"
689         elsif line =~ /^(\S.*\S)\s+:\s+(.*\S)\s*$/
690           case Regexp.last_match(1)
691           when "Volume Set Name" then array[:volume_set] = Regexp.last_match(2)
692           when "Raid Set Name" then array[:raid_set] = Regexp.last_match(2)
693           when "Volume Capacity" then array[:size] = format_disk_size(Regexp.last_match(2).to_f * 1000 * 1000)
694           when "Raid Level" then array[:raid_level] = Regexp.last_match(2).sub(/^Raid/, "")
695           end
696         end
697       end
698     end
699
700     disks = []
701
702     IO.popen(%w(/opt/areca/x86_64/cli64 disk info)).each do |line|
703       next unless line =~ /^\s+(\d+)\s+.*\s+\d+\.\d+GB\s+(\S.*\S)\s*$/
704       next if Regexp.last_match(2) == "N.A."
705
706       disk = {
707         :id => devices[:disks].count,
708         :number => Regexp.last_match(1),
709         :controller => controller[:id],
710         :arrays => []
711       }
712
713       devices[:disks] << disk
714       controller[:disks] << disk[:id]
715
716       if array = arrays.find { |array| array[:raid_set] == Regexp.last_match(2) }
717         disk[:arrays] << array[:id]
718         array[:disks] << disk[:id]
719       end
720
721       disks << disk
722     end
723
724     disks.each do |disk|
725       IO.popen(["/opt/areca/x86_64/cli64", "disk", "info", "drv=#{disk[:number]}"]).each do |line|
726         if line =~ /^IDE Channel\s+:\s+(\d+)\s*$/i
727           disk[:smart_device] = "areca,#{Regexp.last_match(1)}"
728         elsif line =~ /^Device Location\s+:\s+Enclosure#(\d+) Slot#?\s*0*(\d+)\s*$/i
729           disk[:smart_device] = "areca,#{Regexp.last_match(2)}/#{Regexp.last_match(1)}"
730         elsif line =~ /^(\S.*\S)\s+:\s+(.*\S)\s*$/
731           case Regexp.last_match(1)
732           when "Model Name" then disk[:vendor], disk[:model] = Regexp.last_match(2).split
733           when "Serial Number" then disk[:serial_number] = Regexp.last_match(2)
734           when "Disk Capacity" then disk[:size] = format_disk_size(Regexp.last_match(2).to_f * 1000 * 1000)
735           end
736         end
737       end
738     end
739   end
740
741   collect_data(:default) do
742     hardware Mash.new
743
744     hardware[:pci] = pci_devices
745     hardware[:network] = network_devices
746     hardware[:memory] = memory_devices
747     hardware[:disk] = disk_devices
748   end
749 end