- greatly simplify (and fix in the process) parsing of lspci output
[bootmanager.git] / source / systeminfo.py
1 #!/usr/bin/python2
2
3 # --------------
4 # THIS file used to be named 'blockdevicescan.py', but has been renamed
5 # systeminfo.py and made more generic (now includes info about memory,
6 # and other hardware info on the machine)
7 # --------------
8
9 # Copyright (c) 2003 Intel Corporation
10 # All rights reserved.
11
12 # Redistribution and use in source and binary forms, with or without
13 # modification, are permitted provided that the following conditions are
14 # met:
15
16 #     * Redistributions of source code must retain the above copyright
17 #       notice, this list of conditions and the following disclaimer.
18
19 #     * Redistributions in binary form must reproduce the above
20 #       copyright notice, this list of conditions and the following
21 #       disclaimer in the documentation and/or other materials provided
22 #       with the distribution.
23
24 #     * Neither the name of the Intel Corporation nor the names of its
25 #       contributors may be used to endorse or promote products derived
26 #       from this software without specific prior written permission.
27
28 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
32 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
35 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
40 # EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
41 # YOUR JURISDICTION. It is licensee's responsibility to comply with any
42 # export regulations applicable in licensee's jurisdiction. Under
43 # CURRENT (May 2000) U.S. export regulations this software is eligible
44 # for export from the U.S. and can be downloaded by or otherwise
45 # exported or reexported worldwide EXCEPT to U.S. embargoed destinations
46 # which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
47 # Afghanistan and any other country to which the U.S. has embargoed
48 # goods and services.
49
50
51 # expected /proc/partitions format
52 #
53 #----------------------------------------------------
54 #major minor  #blocks  name
55 #
56 #3     0   40017915 hda
57 #3     1     208813 hda1
58 #3     2   20482875 hda2
59 #3     3     522112 hda3
60 #3     4   18804082 hda4
61 #----------------------------------------------------
62
63
64 import string
65 import os
66 import popen2
67 import merge_hw_tables
68 import re
69
70 hwdatapath = "usr/share/hwdata"
71 class systeminfo:
72     """
73     a utility class for finding and returning information about
74     block devices, memory, and other hardware on the system
75     """
76
77     PROC_MEMINFO_PATH= "/proc/meminfo"
78
79
80     PROC_PARTITIONS_PATH= "/proc/partitions"
81
82     # set when the sfdisk -l <dev> trick has been done to make
83     # all devices show up
84     DEVICES_SCANNED_FLAG= "/tmp/devices_scanned"
85         
86     # a /proc/partitions block is 1024 bytes
87     # a GB to a HDD manufacturer is 10^9 bytes
88     BLOCKS_PER_GB = pow(10, 9) / 1024.0;
89
90
91     # -n is numeric ids (no lookup), -m is machine readable
92     LSPCI_CMD= "/sbin/lspci -nm"
93     
94     MODULE_CLASS_NETWORK= "network"
95     MODULE_CLASS_SCSI= "scsi"
96
97     PCI_CLASS_NETWORK_ETHERNET=0x0200L
98     PCI_CLASS_STORAGE_SCSI=0x0100L
99     PCI_CLASS_STORAGE_IDE=0x0101L
100     PCI_CLASS_STORAGE_FLOPPY=0x0102L
101     PCI_CLASS_STORAGE_IPI=0x0103L
102     PCI_CLASS_STORAGE_RAID=0x0104L
103     PCI_CLASS_STORAGE_OTHER=0x0180L
104
105     PCI_ANY=0xffffffffL
106
107     def get_total_phsyical_mem(self):
108         """
109         return the total physical memory of the machine, in kilobytes.
110
111         Return None if /proc/meminfo not readable.
112         """
113         
114         try:
115             meminfo_file= file(self.PROC_MEMINFO_PATH,"r")
116         except IOError, e:
117             return
118
119         total_memory= None
120         
121         for line in meminfo_file:
122
123             try:
124                 (fieldname,value)= string.split(line,":")
125             except ValueError, e:
126                 # this will happen for lines that don't have two values
127                 # (like the first line on 2.4 kernels)
128                 continue
129
130             fieldname= string.strip(fieldname)
131             value= string.strip(value)
132             
133             if fieldname == "MemTotal":
134                 try:
135                     (total_memory,units)= string.split(value)
136                 except ValueError, e:
137                     return
138                 
139                 if total_memory == "" or total_memory == None or \
140                        units == "" or units == None:
141                     return
142
143                 if string.lower(units) != "kb":
144                     return
145
146                 try:
147                     total_memory= int(total_memory)
148                 except ValueError, e:
149                     return
150
151                 break
152
153         meminfo_file.close()
154
155         return total_memory
156
157
158
159     def get_block_device_list(self):
160         """
161         get a list of block devices from this system.
162         return an associative array, where the device name
163         (full /dev/device path) is the key, and the value
164         is a tuple of (major,minor,numblocks,gb_size,readonly)
165         """
166         
167         # make sure we can access to the files/directories in /proc
168         if not os.access(self.PROC_PARTITIONS_PATH, os.F_OK):
169             return None
170
171
172         # only do this once every system boot
173         if not os.access(self.DEVICES_SCANNED_FLAG, os.R_OK):
174             
175             # this is ugly. under devfs, device
176             # entries in /dev/scsi/.. and /dev/ide/...
177             # don't show up until you attempt to read
178             # from the associated device at /dev (/dev/sda).
179             # so, lets run sfdisk -l (list partitions) against
180             # most possible block devices, that way they show
181             # up when it comes time to do the install.
182             for dev_prefix in ('sd','hd'):
183                 block_dev_num= 0
184                 while block_dev_num < 10:
185                     if block_dev_num < 26:
186                         devicename= "/dev/%s%c" % \
187                                     (dev_prefix, chr(ord('a')+block_dev_num))
188                     else:
189                         devicename= "/dev/%s%c%c" % \
190                                     ( dev_prefix,
191                                       chr(ord('a')+((block_dev_num/26)-1)),
192                                       chr(ord('a')+(block_dev_num%26)) )
193
194                     os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename )
195                     block_dev_num = block_dev_num + 1
196
197             additional_scan_devices= ("/dev/cciss/c0d0p", "/dev/cciss/c0d1p",
198                                       "/dev/cciss/c0d2p", "/dev/cciss/c0d3p",
199                                       "/dev/cciss/c0d4p", "/dev/cciss/c0d5p",
200                                       "/dev/cciss/c0d6p", "/dev/cciss/c0d7p",
201                                       "/dev/cciss/c1d0p", "/dev/cciss/c1d1p",
202                                       "/dev/cciss/c1d2p", "/dev/cciss/c1d3p",
203                                       "/dev/cciss/c1d4p", "/dev/cciss/c1d5p",
204                                       "/dev/cciss/c1d6p", "/dev/cciss/c1d7p",)
205             
206             for devicename in additional_scan_devices:
207                 os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename )
208
209             os.system( "touch %s" % self.DEVICES_SCANNED_FLAG )
210             
211
212         devicelist= {}
213
214         partitions_file= file(self.PROC_PARTITIONS_PATH,"r")
215         line_count= 0
216         
217         for line in partitions_file:
218             line_count= line_count + 1
219
220             # skip the first two lines always
221             if line_count < 2:
222                 continue
223
224             parts= string.split(line)
225
226             if len(parts) < 4:
227                 continue
228             
229             device= parts[3]
230
231             dev_name= "/dev/%s" % device
232             
233             try:
234                 major= int(parts[0])
235                 minor= int(parts[1])
236                 blocks= int(parts[2])
237             except ValueError, err:
238                 continue
239
240             # skip and ignore any partitions
241             if minor != 0:
242                 continue
243                 
244             gb_size= blocks/self.BLOCKS_PER_GB
245             
246             # parse the output of hdparm <disk> to get the readonly flag;
247             # if this fails, assume it isn't read only
248             readonly= 0
249             
250             hdparm_cmd = popen2.Popen3("hdparm %s 2> /dev/null" % dev_name)
251             status= hdparm_cmd.wait()
252
253             if os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0:
254                 try:
255                     hdparm_output= hdparm_cmd.fromchild.read()
256                     hdparm_cmd= None
257                     
258                     # parse the output of hdparm, the lines we are interested
259                     # in look
260                     # like:
261                     #
262                     #  readonly     =  0 (off)
263                     #
264                     
265                     for line in string.split(hdparm_output,"\n"):
266                         
267                         line= string.strip(line)
268                         if line == "":
269                             continue
270                         
271                         line_parts= string.split(line,"=")
272                         if len(line_parts) < 2:
273                             continue
274
275                         name= string.strip(line_parts[0])
276
277                         if name == "readonly":
278                             value= string.strip(line_parts[1])
279                             if len(value) == 0:
280                                 break
281
282                             if value[0] == "1":
283                                 readonly= 1
284                                 break
285
286                 except IOError:
287                     pass
288
289             devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
290
291             
292         return devicelist
293
294
295
296     def get_system_modules( self, install_root, kernel_version= None ):
297         """
298         Return a list of kernel modules that this system requires.
299         This requires access to the installed system's root
300         directory, as the following files must exist and are used:
301         <install_root>/usr/share/hwdata/pcitable
302         <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
303         <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
304
305         If there are more than one kernels installed, and the kernel
306         version is not specified, then only the first one in
307         /lib/modules is used.
308
309         Returns a dictionary, keys being the type of module:
310          - scsi       MODULE_CLASS_SCSI
311          - network    MODULE_CLASS_NETWORK
312         The value being the kernel module name to load.
313
314         Some sata devices show up under an IDE device class,
315         hence the reason for checking for ide devices as well.
316         If there actually is a match in the pci -> module lookup
317         table, and its an ide device, its most likely sata,
318         as ide modules are built in to the kernel.
319         """
320
321         # get the kernel version we are assuming
322         if kernel_version is None:
323             try:
324                 kernel_version= os.listdir( "%s/lib/modules/" % install_root )
325             except OSError, e:
326                 return
327
328             if len(kernel_version) == 0:
329                 return
330
331             if len(kernel_version) > 1:
332                 print( "WARNING: We may be returning modules for the wrong kernel." )
333
334             kernel_version= kernel_version[0]
335
336         print( "Using kernel version %s" % kernel_version )
337
338         # test to make sure the three files we need are present
339         pcitable_path = "%s/%s/pcitable" % (install_root,hwdatapath)
340         modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
341                               (install_root,kernel_version)
342         modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
343                            (install_root,kernel_version)
344
345         for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
346             if not os.access(path,os.R_OK):
347                 print( "Unable to read %s" % path )
348                 return
349
350         # now, with those three files, merge them all into one easy to
351         # use lookup table
352         (all_pci_ids, all_modules) = merge_hw_tables.merge_files( modules_dep_path,
353                                                                   modules_pcimap_path,
354                                                                   pcitable_path )
355         if all_modules is None:
356             print( "Unable to merge pci id tables." )
357             return
358
359         # this is the actual data structure we return
360         system_mods= {}
361
362         # these are the lists that will be in system_mods
363         network_mods= []
364         scsi_mods= []
365
366
367         # get all the system devices from lspci
368         lspci_prog= popen2.Popen3( self.LSPCI_CMD, 1 )
369         if lspci_prog is None:
370             print( "Unable to run %s with popen2.Popen3" % self.LSPCI_CMD )
371             return
372         
373         returncode= lspci_prog.wait()
374         if returncode != 0:
375             print( "Running %s failed" % self.LSPCI_CMD )
376             return
377         else:
378             print( "Successfully ran %s" % self.LSPCI_CMD )
379             
380         # for every lspci line, parse in the four tuple PCI id and the
381         # search for the corresponding driver from the dictionary
382         # generated by merge_hw_tables
383         for line in lspci_prog.fromchild:
384             # A sample line:
385             #
386             # 00:1f.1 "Class 0101" "8086" "2411" -r02 -p80 "8086" "2411"
387             #
388             # Remove '"', 'Class ', and anything beginning with '-'
389             # (usually revisions and prog-if flags) so that we can
390             # split on whitespace:
391             #
392             # 00:1f.1 0101 8086 2411 8086 2411
393             #
394             line = line.strip()
395             line = line.replace('"', '')
396             line = line.replace('Class ', '')
397             line = re.sub('-[^ ]*', '', line)
398
399             parts = line.split()
400             try:
401                 if len(parts) < 4:
402                     raise
403                 classid = long(parts[1], 16)
404                 vendorid = long(parts[2], 16)
405                 deviceid = long(parts[3], 16)
406             except:
407                 print "Invalid line:", line
408                 continue
409
410             if classid not in (self.PCI_CLASS_NETWORK_ETHERNET,
411                                self.PCI_CLASS_STORAGE_SCSI,
412                                self.PCI_CLASS_STORAGE_RAID,
413                                self.PCI_CLASS_STORAGE_OTHER,
414                                self.PCI_CLASS_STORAGE_IDE):
415                 continue
416
417             # Device may have a subvendorid and subdeviceid
418             try:
419                 subvendorid = long(parts[4], 16)
420                 subdeviceid = long(parts[5], 16)
421             except:
422                 subvendorid = self.PCI_ANY
423                 subdeviceid = self.PCI_ANY
424
425             # search for driver that most closely matches the full_id
426             # to drivers that can handle any subvendor/subdevice
427             # version of the hardware.
428             full_ids = ((vendorid,deviceid,subvendorid,subdeviceid),
429                         (vendorid,deviceid,subvendorid,self.PCI_ANY),
430                         (vendorid,deviceid,self.PCI_ANY,self.PCI_ANY))
431
432             for full_id in full_ids:
433                 module = all_pci_ids.get(full_id, None)
434                 if module is not None:
435                     if classid == self.PCI_CLASS_NETWORK_ETHERNET:
436                         network_mods.append(module[0])
437                     elif classid in (self.PCI_CLASS_STORAGE_SCSI,
438                                      self.PCI_CLASS_STORAGE_RAID,
439                                      self.PCI_CLASS_STORAGE_OTHER,
440                                      self.PCI_CLASS_STORAGE_IDE):
441                         scsi_mods.append(module[0])
442                     else:
443                         print "not network or scsi: 0x%x" % classid
444                     break
445
446         system_mods[self.MODULE_CLASS_SCSI]= scsi_mods
447         system_mods[self.MODULE_CLASS_NETWORK]= network_mods
448         
449         return system_mods
450
451
452     def remove_quotes( self, str ):
453         """
454         remove double quotes off of a string if they exist
455         """
456         if str == "" or str == None:
457             return str
458         if str[:1] == '"':
459             str= str[1:]
460         if str[len(str)-1] == '"':
461             str= str[:len(str)-1]
462         return str
463
464
465
466 if __name__ == "__main__":
467     info= systeminfo()
468     
469     devices= info.get_block_device_list()
470     print "block devices detected:"
471     if not devices:
472         print "no devices found!"
473     else:
474         for dev in devices.keys():
475             print "%s %s" % (dev, repr(devices[dev]))
476             
477
478     print ""
479     memory= info.get_total_phsyical_mem()
480     if not memory:
481         print "unable to read /proc/meminfo for memory"
482     else:
483         print "total physical memory: %d kb" % memory
484         
485
486     print ""
487
488     import sys
489     kernel_version = None
490     if len(sys.argv) > 2:
491         kernel_version = sys.argv[1]
492         
493     modules= info.get_system_modules("/",kernel_version)
494     if not modules:
495         print "unable to list system modules"
496     else:
497         for type in modules:
498             if type == info.MODULE_CLASS_SCSI:
499                 print( "all scsi modules:" )
500                 for a_mod in modules[type]:
501                     print a_mod
502             elif type == info.MODULE_CLASS_NETWORK:
503                 print( "all network modules:" )
504                 for a_mod in modules[type]:
505                     print a_mod
506