36d0ce60177d024473af6d0cbebd46ab4ea97a90
[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
69 hwdatapath = "usr/share/hwdata"
70 class systeminfo:
71     """
72     a utility class for finding and returning information about
73     block devices, memory, and other hardware on the system
74     """
75
76     PROC_MEMINFO_PATH= "/proc/meminfo"
77
78
79     PROC_PARTITIONS_PATH= "/proc/partitions"
80
81     # set when the sfdisk -l <dev> trick has been done to make
82     # all devices show up
83     DEVICES_SCANNED_FLAG= "/tmp/devices_scanned"
84         
85     # a /proc/partitions block is 1024 bytes
86     # a GB to a HDD manufacturer is 10^9 bytes
87     BLOCKS_PER_GB = pow(10, 9) / 1024.0;
88
89
90     # -n is numeric ids (no lookup), -m is machine readable
91     LSPCI_CMD= "/sbin/lspci -nm"
92     
93     MODULE_CLASS_NETWORK= "network"
94     MODULE_CLASS_SCSI= "scsi"
95
96     PCI_CLASS_NETWORK_ETHERNET=0x0200L
97     PCI_CLASS_STORAGE_SCSI=0x0100L
98     PCI_CLASS_STORAGE_IDE=0x0101L
99     PCI_CLASS_STORAGE_FLOPPY=0x0102L
100     PCI_CLASS_STORAGE_IPI=0x0103L
101     PCI_CLASS_STORAGE_RAID=0x0104L
102     PCI_CLASS_STORAGE_OTHER=0x0180L
103
104     PCI_ANY=0xffffffffL
105
106     def get_total_phsyical_mem(self):
107         """
108         return the total physical memory of the machine, in kilobytes.
109
110         Return None if /proc/meminfo not readable.
111         """
112         
113         try:
114             meminfo_file= file(self.PROC_MEMINFO_PATH,"r")
115         except IOError, e:
116             return
117
118         total_memory= None
119         
120         for line in meminfo_file:
121
122             try:
123                 (fieldname,value)= string.split(line,":")
124             except ValueError, e:
125                 # this will happen for lines that don't have two values
126                 # (like the first line on 2.4 kernels)
127                 continue
128
129             fieldname= string.strip(fieldname)
130             value= string.strip(value)
131             
132             if fieldname == "MemTotal":
133                 try:
134                     (total_memory,units)= string.split(value)
135                 except ValueError, e:
136                     return
137                 
138                 if total_memory == "" or total_memory == None or \
139                        units == "" or units == None:
140                     return
141
142                 if string.lower(units) != "kb":
143                     return
144
145                 try:
146                     total_memory= int(total_memory)
147                 except ValueError, e:
148                     return
149
150                 break
151
152         meminfo_file.close()
153
154         return total_memory
155
156
157
158     def get_block_device_list(self):
159         """
160         get a list of block devices from this system.
161         return an associative array, where the device name
162         (full /dev/device path) is the key, and the value
163         is a tuple of (major,minor,numblocks,gb_size,readonly)
164         """
165         
166         # make sure we can access to the files/directories in /proc
167         if not os.access(self.PROC_PARTITIONS_PATH, os.F_OK):
168             return None
169
170
171         # only do this once every system boot
172         if not os.access(self.DEVICES_SCANNED_FLAG, os.R_OK):
173             
174             # this is ugly. under devfs, device
175             # entries in /dev/scsi/.. and /dev/ide/...
176             # don't show up until you attempt to read
177             # from the associated device at /dev (/dev/sda).
178             # so, lets run sfdisk -l (list partitions) against
179             # most possible block devices, that way they show
180             # up when it comes time to do the install.
181             for dev_prefix in ('sd','hd'):
182                 block_dev_num= 0
183                 while block_dev_num < 10:
184                     if block_dev_num < 26:
185                         devicename= "/dev/%s%c" % \
186                                     (dev_prefix, chr(ord('a')+block_dev_num))
187                     else:
188                         devicename= "/dev/%s%c%c" % \
189                                     ( dev_prefix,
190                                       chr(ord('a')+((block_dev_num/26)-1)),
191                                       chr(ord('a')+(block_dev_num%26)) )
192
193                     os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename )
194                     block_dev_num = block_dev_num + 1
195
196             additional_scan_devices= ("/dev/cciss/c0d0p", "/dev/cciss/c0d1p",
197                                       "/dev/cciss/c0d2p", "/dev/cciss/c0d3p",
198                                       "/dev/cciss/c0d4p", "/dev/cciss/c0d5p",
199                                       "/dev/cciss/c0d6p", "/dev/cciss/c0d7p",
200                                       "/dev/cciss/c1d0p", "/dev/cciss/c1d1p",
201                                       "/dev/cciss/c1d2p", "/dev/cciss/c1d3p",
202                                       "/dev/cciss/c1d4p", "/dev/cciss/c1d5p",
203                                       "/dev/cciss/c1d6p", "/dev/cciss/c1d7p",)
204             
205             for devicename in additional_scan_devices:
206                 os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename )
207
208             os.system( "touch %s" % self.DEVICES_SCANNED_FLAG )
209             
210
211         devicelist= {}
212
213         partitions_file= file(self.PROC_PARTITIONS_PATH,"r")
214         line_count= 0
215         
216         for line in partitions_file:
217             line_count= line_count + 1
218
219             # skip the first two lines always
220             if line_count < 2:
221                 continue
222
223             parts= string.split(line)
224
225             if len(parts) < 4:
226                 continue
227             
228             device= parts[3]
229
230             dev_name= "/dev/%s" % device
231             
232             try:
233                 major= int(parts[0])
234                 minor= int(parts[1])
235                 blocks= int(parts[2])
236             except ValueError, err:
237                 continue
238
239             # skip and ignore any partitions
240             if minor != 0:
241                 continue
242                 
243             gb_size= blocks/self.BLOCKS_PER_GB
244             
245             # parse the output of hdparm <disk> to get the readonly flag;
246             # if this fails, assume it isn't read only
247             readonly= 0
248             
249             hdparm_cmd = popen2.Popen3("hdparm %s 2> /dev/null" % dev_name)
250             status= hdparm_cmd.wait()
251
252             if os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0:
253                 try:
254                     hdparm_output= hdparm_cmd.fromchild.read()
255                     hdparm_cmd= None
256                     
257                     # parse the output of hdparm, the lines we are interested
258                     # in look
259                     # like:
260                     #
261                     #  readonly     =  0 (off)
262                     #
263                     
264                     for line in string.split(hdparm_output,"\n"):
265                         
266                         line= string.strip(line)
267                         if line == "":
268                             continue
269                         
270                         line_parts= string.split(line,"=")
271                         if len(line_parts) < 2:
272                             continue
273
274                         name= string.strip(line_parts[0])
275
276                         if name == "readonly":
277                             value= string.strip(line_parts[1])
278                             if len(value) == 0:
279                                 break
280
281                             if value[0] == "1":
282                                 readonly= 1
283                                 break
284
285                 except IOError:
286                     pass
287
288             devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
289
290             
291         return devicelist
292
293
294
295     def get_system_modules( self, install_root, kernel_version= None ):
296         """
297         Return a list of kernel modules that this system requires.
298         This requires access to the installed system's root
299         directory, as the following files must exist and are used:
300         <install_root>/usr/share/hwdata/pcitable
301         <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
302         <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
303
304         If there are more than one kernels installed, and the kernel
305         version is not specified, then only the first one in
306         /lib/modules is used.
307
308         Returns a dictionary, keys being the type of module:
309          - scsi       MODULE_CLASS_SCSI
310          - network    MODULE_CLASS_NETWORK
311         The value being the kernel module name to load.
312
313         Some sata devices show up under an IDE device class,
314         hence the reason for checking for ide devices as well.
315         If there actually is a match in the pci -> module lookup
316         table, and its an ide device, its most likely sata,
317         as ide modules are built in to the kernel.
318         """
319
320         # get the kernel version we are assuming
321         if kernel_version is None:
322             try:
323                 kernel_version= os.listdir( "%s/lib/modules/" % install_root )
324             except OSError, e:
325                 return
326
327             if len(kernel_version) == 0:
328                 return
329
330             if len(kernel_version) > 1:
331                 print( "WARNING: We may be returning modules for the wrong kernel." )
332
333             kernel_version= kernel_version[0]
334
335         print( "Using kernel version %s" % kernel_version )
336
337         # test to make sure the three files we need are present
338         pcitable_path = "%s/%s/pcitable" % (install_root,hwdatapath)
339         modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
340                               (install_root,kernel_version)
341         modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
342                            (install_root,kernel_version)
343
344         for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
345             if not os.access(path,os.R_OK):
346                 print( "Unable to read %s" % path )
347                 return
348
349         # now, with those three files, merge them all into one easy to
350         # use lookup table
351         (all_pci_ids, all_modules) = merge_hw_tables.merge_files( modules_dep_path,
352                                                                   modules_pcimap_path,
353                                                                   pcitable_path )
354         if all_modules is None:
355             print( "Unable to merge pci id tables." )
356             return
357
358         # this is the actual data structure we return
359         system_mods= {}
360
361         # these are the lists that will be in system_mods
362         network_mods= []
363         scsi_mods= []
364
365
366         # get all the system devices from lspci
367         lspci_prog= popen2.Popen3( self.LSPCI_CMD, 1 )
368         if lspci_prog is None:
369             print( "Unable to run %s with popen2.Popen3" % self.LSPCI_CMD )
370             return
371         
372         returncode= lspci_prog.wait()
373         if returncode != 0:
374             print( "Running %s failed" % self.LSPCI_CMD )
375             return
376         else:
377             print( "Successfully ran %s" % self.LSPCI_CMD )
378             
379         # for every lspci line, parse in the four tuple PCI id and the
380         # search for the corresponding driver from the dictionary
381         # generated by merge_hw_tables
382         for line in lspci_prog.fromchild:
383             if string.strip(line) == "":
384                 continue
385     
386             parts= string.split(line)
387
388             try:
389                 classid= self.remove_quotes(parts[2])
390                 classid= long(classid,16)
391             except IndexError:
392                 print( "Skipping invalid classid:", string.strip(line) )
393                 continue
394             except ValueError, e:
395                 print( "Skipping invalid classid:", string.strip(line) )
396                 continue
397
398
399             if classid not in (self.PCI_CLASS_NETWORK_ETHERNET,
400                                self.PCI_CLASS_STORAGE_SCSI,
401                                self.PCI_CLASS_STORAGE_RAID,
402                                self.PCI_CLASS_STORAGE_OTHER,
403                                self.PCI_CLASS_STORAGE_IDE):
404                 continue
405
406             vendorid    = self.PCI_ANY
407             deviceid    = self.PCI_ANY
408             subvendorid = self.PCI_ANY
409             subdeviceid = self.PCI_ANY
410
411             # parse in vendorid
412             try:
413                 vendorid= self.remove_quotes(parts[3])
414                 vendorid= long(vendorid,16)
415             except IndexError:
416                 print( "Skipping invalid vendorid:", string.strip(line) )
417                 continue
418             except ValueError, e:
419                 print( "Skipping invalid vendorid:", string.strip(line) )
420                 continue
421
422             # parse in deviceid
423             try:
424                 deviceid= self.remove_quotes(parts[4])
425                 deviceid= long(deviceid,16)
426             except IndexError:
427                 print( "Skipping invalid deviceid:", string.strip(line) )
428                 continue
429             except ValueError, e:
430                 print( "Skipping invalid deviceid:", string.strip(line) )
431                 continue
432
433             # Now get the subvendor & subdevice portion by searching
434             # parts[5:] of the lspci output. Note that we have to skip
435             # the portions of the lspci output string that indicate
436             # revision info.
437
438             # parse in subvendorid
439             subvendorindex = -1
440             for i in range(5,len(parts)):
441                 p = self.remove_quotes(parts[i])
442                 if len(p)>0 and p[0] != '-':
443                     subvendorindex = i
444                     break
445
446             if subvendorindex != -1:
447                 try:
448                     subvendorid= self.remove_quotes(parts[subvendorindex])
449                     subvendorid= long(subvendorid,16)
450                 except IndexError:
451                     print( "Skipping invalid line:", string.strip(line) )
452                     continue
453                 except ValueError, e:
454                     print( "Skipping invalid line:", string.strip(line) )
455                     continue
456
457             # parse in subdeviceid
458             subdeviceindex = -1
459             for i in range(subvendorindex+1,len(parts)):
460                 p = self.remove_quotes(parts[i])
461                 if p[0] != '-':
462                     subdeviceindex = i
463                     break
464             if subdeviceindex != -1:
465                 error_msg = "Skipping invalid subdeviceid:"
466                 try:
467                     subdeviceid= self.remove_quotes(parts[subdeviceindex])
468                     subdeviceid= long(subdeviceid,16)
469                 except IndexError:
470                     print( error_msg, string.strip(line) )
471                     continue
472                 except ValueError, e:
473                     print( error_msg, string.strip(line) )
474                     continue
475
476             # search for driver that most closely matches the full_id
477             # to drivers that can handle any subvendor/subdevice
478             # version of the hardware.
479             full_ids = ((vendorid,deviceid,subvendorid,subdeviceid),
480                         (vendorid,deviceid,subvendorid,self.PCI_ANY),
481                         (vendorid,deviceid,self.PCI_ANY,self.PCI_ANY))
482
483             for full_id in full_ids:
484                 module = all_pci_ids.get(full_id, None)
485                 if module is not None:
486                     if classid == self.PCI_CLASS_NETWORK_ETHERNET:
487                         network_mods.append(module[0])
488                     elif classid in (self.PCI_CLASS_STORAGE_SCSI,
489                                      self.PCI_CLASS_STORAGE_RAID,
490                                      self.PCI_CLASS_STORAGE_OTHER,
491                                      self.PCI_CLASS_STORAGE_IDE):
492                         scsi_mods.append(module[0])
493                     break
494
495         system_mods[self.MODULE_CLASS_SCSI]= scsi_mods
496         system_mods[self.MODULE_CLASS_NETWORK]= network_mods
497         
498         return system_mods
499
500
501     def remove_quotes( self, str ):
502         """
503         remove double quotes off of a string if they exist
504         """
505         if str == "" or str == None:
506             return str
507         if str[:1] == '"':
508             str= str[1:]
509         if str[len(str)-1] == '"':
510             str= str[:len(str)-1]
511         return str
512
513
514
515 if __name__ == "__main__":
516     info= systeminfo()
517     
518     devices= info.get_block_device_list()
519     print "block devices detected:"
520     if not devices:
521         print "no devices found!"
522     else:
523         for dev in devices.keys():
524             print "%s %s" % (dev, repr(devices[dev]))
525             
526
527     print ""
528     memory= info.get_total_phsyical_mem()
529     if not memory:
530         print "unable to read /proc/meminfo for memory"
531     else:
532         print "total physical memory: %d kb" % memory
533         
534
535     print ""
536
537     import sys
538     kernel_version = None
539     if len(sys.argv) > 2:
540         kernel_version = sys.argv[1]
541         
542     modules= info.get_system_modules("/",kernel_version)
543     if not modules:
544         print "unable to list system modules"
545     else:
546         for type in modules:
547             if type == info.MODULE_CLASS_SCSI:
548                 print( "all scsi modules:" )
549                 for a_mod in modules[type]:
550                     print a_mod
551             elif type == info.MODULE_CLASS_NETWORK:
552                 print( "all network modules:" )
553                 for a_mod in modules[type]:
554                     print a_mod
555