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