966c149456ebec2273b0b43a84f8e3526ebb233e
[bootmanager.git] / source / systeminfo.py
1 #!/usr/bin/python2
2
3 # Copyright (c) 2003 Intel Corporation
4 # All rights reserved.
5 #
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
7 # All rights reserved.
8 # expected /proc/partitions format
9
10
11 #----------------------------------------------------
12 #major minor  #blocks  name
13 #
14 #3     0   40017915 hda
15 #3     1     208813 hda1
16 #3     2   20482875 hda2
17 #3     3     522112 hda3
18 #3     4   18804082 hda4
19 #----------------------------------------------------
20
21
22 import string
23 import sys
24 import os
25 import popen2
26 import merge_hw_tables
27 import re
28 import errno
29 import ModelOptions
30 from Exceptions import *
31
32 hwdatapath = "usr/share/hwdata"
33 """
34 a utility class for finding and returning information about
35 block devices, memory, and other hardware on the system
36 """
37
38 PROC_MEMINFO_PATH= "/proc/meminfo"
39 PROC_PARTITIONS_PATH= "/proc/partitions"
40
41 # set when the sfdisk -l <dev> trick has been done to make
42 # all devices show up
43 DEVICES_SCANNED_FLAG= "/tmp/devices_scanned"
44
45 # a /proc/partitions block is 1024 bytes
46 # a GB to a HDD manufacturer is 10^9 bytes
47 BLOCKS_PER_GB = pow(10, 9) / 1024.0;
48
49
50 # -n is numeric ids (no lookup), -m is machine readable
51 LSPCI_CMD= "/sbin/lspci -nm"
52
53 MODULE_CLASS_NETWORK= "network"
54 MODULE_CLASS_SCSI= "scsi"
55
56 PCI_CLASS_NETWORK_ETHERNET=0x0200L
57 PCI_CLASS_STORAGE_SCSI=0x0100L
58 PCI_CLASS_STORAGE_IDE=0x0101L
59 PCI_CLASS_STORAGE_FLOPPY=0x0102L
60 PCI_CLASS_STORAGE_IPI=0x0103L
61 PCI_CLASS_STORAGE_RAID=0x0104L
62 PCI_CLASS_STORAGE_OTHER=0x0180L
63
64 PCI_ANY=0xffffffffL
65
66 def get_total_phsyical_mem(vars = {}, log = sys.stderr):
67     """
68     return the total physical memory of the machine, in kilobytes.
69
70     Return None if /proc/meminfo not readable.
71     """
72
73     try:
74         meminfo_file= file(PROC_MEMINFO_PATH,"r")
75     except IOError, e:
76         return
77
78     total_memory= None
79
80     for line in meminfo_file:
81
82         try:
83             (fieldname,value)= string.split(line,":")
84         except ValueError, e:
85             # this will happen for lines that don't have two values
86             # (like the first line on 2.4 kernels)
87             continue
88
89         fieldname= string.strip(fieldname)
90         value= string.strip(value)
91         
92         if fieldname == "MemTotal":
93             try:
94                 (total_memory,units)= string.split(value)
95             except ValueError, e:
96                 return
97
98             if total_memory == "" or total_memory == None or \
99                    units == "" or units == None:
100                 return
101
102             if string.lower(units) != "kb":
103                 return
104
105             try:
106                 total_memory= int(total_memory)
107             except ValueError, e:
108                 return
109
110             break
111
112     meminfo_file.close()
113     return total_memory
114
115 def get_block_device_list(vars = {}, log = sys.stderr):
116     """
117     get a list of block devices from this system.
118     return an associative array, where the device name
119     (full /dev/device path) is the key, and the value
120     is a tuple of (major,minor,numblocks,gb_size,readonly)
121     """
122
123     # make sure we can access to the files/directories in /proc
124     if not os.access(PROC_PARTITIONS_PATH, os.F_OK):
125         return None
126
127     # table with valid scsi/sata/ide/raid block device names
128     valid_blk_names = {}
129     # add in valid sd and hd block device names
130     for blk_prefix in ('sd','hd'):
131         for blk_num in map ( \
132             lambda x: chr(x), range(ord('a'),ord('z')+1)):
133             devicename="%s%c" % (blk_prefix, blk_num)
134             valid_blk_names[devicename]=None
135
136     # add in valid scsi raid block device names
137     for M in range(0,1+1):
138         for N in range(0,7+1):
139             devicename = "cciss/c%dd%d" % (M,N)
140             valid_blk_names[devicename]=None
141
142     # only do this once every system boot
143     if not os.access(DEVICES_SCANNED_FLAG, os.R_OK):
144
145         # this is ugly. under devfs, device
146         # entries in /dev/scsi/.. and /dev/ide/...
147         # don't show up until you attempt to read
148         # from the associated device at /dev (/dev/sda).
149         # so, lets run sfdisk -l (list partitions) against
150         # most possible block devices, that way they show
151         # up when it comes time to do the install.
152         devicenames = valid_blk_names.keys()
153         devicenames.sort()
154         for devicename in devicenames:
155             os.system( "sfdisk -l /dev/%s > /dev/null 2>&1" % devicename )
156
157         # touch file
158         fb = open(DEVICES_SCANNED_FLAG,"w")
159         fb.close()
160
161     devicelist= {}
162
163     partitions_file= file(PROC_PARTITIONS_PATH,"r")
164     line_count= 0
165     for line in partitions_file:
166         line_count= line_count + 1
167
168         # skip the first two lines always
169         if line_count < 2:
170             continue
171
172         parts= string.split(line)
173
174         if len(parts) < 4:
175             continue
176
177         device= parts[3]
178
179         # skip and ignore any partitions
180         if not valid_blk_names.has_key(device):
181             continue
182
183         try:
184             major= int(parts[0])
185             minor= int(parts[1])
186             blocks= int(parts[2])
187         except ValueError, err:
188             continue
189
190         gb_size= blocks/BLOCKS_PER_GB
191
192         # check to see if the blk device is readonly
193         try:
194             # can we write to it?
195             dev_name= "/dev/%s" % device
196             fb = open(dev_name,"w")
197             fb.close()
198             readonly=False
199         except IOError, e:
200             # check if EROFS errno
201             if errno.errorcode.get(e.errno,None) == 'EROFS':
202                 readonly=True
203             else:
204                 # got some other errno, pretend device is readonly
205                 readonly=True
206
207         devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
208
209     return devicelist
210
211
212 def get_system_modules( vars = {}, log = sys.stderr):
213     """
214     Return a list of kernel modules that this system requires.
215     This requires access to the installed system's root
216     directory, as the following files must exist and are used:
217     <install_root>/usr/share/hwdata/pcitable
218     <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
219     <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
220
221     If there are more than one kernels installed, and the kernel
222     version is not specified, then only the first one in
223     /lib/modules is used.
224
225     Returns a dictionary, keys being the type of module:
226         - scsi       MODULE_CLASS_SCSI
227         - network    MODULE_CLASS_NETWORK
228     The value being the kernel module name to load.
229
230     Some sata devices show up under an IDE device class,
231     hence the reason for checking for ide devices as well.
232     If there actually is a match in the pci -> module lookup
233     table, and its an ide device, its most likely sata,
234     as ide modules are built in to the kernel.
235     """
236
237     if not vars.has_key("SYSIMG_PATH"):
238         vars["SYSIMG_PATH"]="/"
239     SYSIMG_PATH=vars["SYSIMG_PATH"]
240
241     if not vars.has_key("NODE_MODEL_OPTIONS"):
242         vars["NODE_MODEL_OPTIONS"] = 0;
243
244     initrd, kernel_version = getKernelVersion(vars, log)
245
246     # get the kernel version we are assuming
247     if kernel_version is None:
248         try:
249             kernel_version= os.listdir( "%s/lib/modules/" % SYSIMG_PATH )
250         except OSError, e:
251             return
252
253         if len(kernel_version) == 0:
254             return
255
256         if len(kernel_version) > 1:
257             print( "WARNING: We may be returning modules for the wrong kernel." )
258
259         kernel_version= kernel_version[0]
260
261     print( "Using kernel version %s" % kernel_version )
262
263     # test to make sure the three files we need are present
264     pcitable_path = "%s/%s/pcitable" % (SYSIMG_PATH,hwdatapath)
265     modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
266                           (SYSIMG_PATH,kernel_version)
267     modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
268                        (SYSIMG_PATH,kernel_version)
269
270     for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
271         if not os.access(path,os.R_OK):
272             print( "Unable to read %s" % path )
273             return
274
275     # now, with those three files, merge them all into one easy to
276     # use lookup table
277     (all_pci_ids, all_modules) = merge_hw_tables.merge_files( modules_dep_path,
278                                                               modules_pcimap_path,
279                                                               pcitable_path )
280     if all_modules is None:
281         print( "Unable to merge pci id tables." )
282         return
283
284     # this is the actual data structure we return
285     system_mods= {}
286
287     # these are the lists that will be in system_mods
288     network_mods= []
289     scsi_mods= []
290
291
292     # get all the system devices from lspci
293     lspci_prog= popen2.Popen3( LSPCI_CMD, 1 )
294     if lspci_prog is None:
295         print( "Unable to run %s with popen2.Popen3" % LSPCI_CMD )
296         return
297
298     returncode= lspci_prog.wait()
299     if returncode != 0:
300         print( "Running %s failed" % LSPCI_CMD )
301         return
302     else:
303         print( "Successfully ran %s" % LSPCI_CMD )
304
305     # for every lspci line, parse in the four tuple PCI id and the
306     # search for the corresponding driver from the dictionary
307     # generated by merge_hw_tables
308     for line in lspci_prog.fromchild:
309         # A sample line:
310         #
311         # 00:1f.1 "Class 0101" "8086" "2411" -r02 -p80 "8086" "2411"
312         #
313         # Remove '"', 'Class ', and anything beginning with '-'
314         # (usually revisions and prog-if flags) so that we can
315         # split on whitespace:
316         #
317         # 00:1f.1 0101 8086 2411 8086 2411
318         #
319         line = line.strip()
320         line = line.replace('"', '')
321         line = line.replace('Class ', '')
322         line = re.sub('-[^ ]*', '', line)
323
324         parts = line.split()
325         try:
326             if len(parts) < 4:
327                 raise
328             classid = long(parts[1], 16)
329             vendorid = long(parts[2], 16)
330             deviceid = long(parts[3], 16)
331         except:
332             print "Invalid line:", line
333             continue
334
335         if classid not in (PCI_CLASS_NETWORK_ETHERNET,
336                            PCI_CLASS_STORAGE_SCSI,
337                            PCI_CLASS_STORAGE_RAID,
338                            PCI_CLASS_STORAGE_OTHER,
339                            PCI_CLASS_STORAGE_IDE):
340             continue
341
342         # Device may have a subvendorid and subdeviceid
343         try:
344             subvendorid = long(parts[4], 16)
345             subdeviceid = long(parts[5], 16)
346         except:
347             subvendorid = PCI_ANY
348             subdeviceid = PCI_ANY
349
350         # search for driver that most closely matches the full_id
351         # to drivers that can handle any subvendor/subdevice
352         # version of the hardware.
353         full_ids = ((vendorid,deviceid,subvendorid,subdeviceid),
354                     (vendorid,deviceid,subvendorid,PCI_ANY),
355                     (vendorid,deviceid,PCI_ANY,PCI_ANY))
356
357         for full_id in full_ids:
358             module = all_pci_ids.get(full_id, None)
359             if module is not None:
360                 if classid == PCI_CLASS_NETWORK_ETHERNET:
361                     network_mods.append(module[0])
362                 elif classid in (PCI_CLASS_STORAGE_SCSI,
363                                  PCI_CLASS_STORAGE_RAID,
364                                  PCI_CLASS_STORAGE_OTHER,
365                                  PCI_CLASS_STORAGE_IDE):
366                     scsi_mods.append(module[0])
367                 else:
368                     print "not network or scsi: 0x%x" % classid
369                 break
370
371     system_mods[MODULE_CLASS_SCSI]= scsi_mods
372     system_mods[MODULE_CLASS_NETWORK]= network_mods
373
374     return system_mods
375
376
377 def getKernelVersion( vars = {} , log = sys.stderr):
378     # make sure we have the variables we need
379     try:
380         SYSIMG_PATH= vars["SYSIMG_PATH"]
381         if SYSIMG_PATH == "":
382             raise ValueError, "SYSIMG_PATH"
383
384         NODE_MODEL_OPTIONS=vars["NODE_MODEL_OPTIONS"]
385     except KeyError, var:
386         raise BootManagerException, "Missing variable in vars: %s\n" % var
387     except ValueError, var:
388         raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
389
390     option = ''
391     if NODE_MODEL_OPTIONS & ModelOptions.SMP:
392         option = 'smp'
393         try:
394             os.stat("%s/boot/kernel-boot%s" % (SYSIMG_PATH,option))
395             os.stat("%s/boot/initrd-boot%s" % (SYSIMG_PATH,option))
396         except OSError, e:
397             # smp kernel is not there; remove option from modeloptions
398             # such that the rest of the code base thinks we are just
399             # using the base kernel.
400             NODE_MODEL_OPTIONS = NODE_MODEL_OPTIONS & ~ModelOptions.SMP
401             vars["NODE_MODEL_OPTIONS"] = NODE_MODEL_OPTIONS
402             log.write( "WARNING: Couldn't locate smp kernel.\n")
403             option = ''
404     try:
405         initrd= os.readlink( "%s/boot/initrd-boot%s" % (SYSIMG_PATH,option) )
406         kernel_version= initrd.replace("initrd-", "").replace(".img", "")    
407     except OSError, e:
408         initrd = None
409         kernel_version = None
410         
411     return (initrd, kernel_version)
412
413
414 if __name__ == "__main__":
415     devices= get_block_device_list()
416     print "block devices detected:"
417     if not devices:
418         print "no devices found!"
419     else:
420         for dev in devices.keys():
421             print "%s %s" % (dev, repr(devices[dev]))
422             
423
424     print ""
425     memory= get_total_phsyical_mem()
426     if not memory:
427         print "unable to read /proc/meminfo for memory"
428     else:
429         print "total physical memory: %d kb" % memory
430         
431
432     print ""
433
434     kernel_version = None
435     if len(sys.argv) > 2:
436         kernel_version = sys.argv[1]
437         
438     modules= get_system_modules()
439     if not modules:
440         print "unable to list system modules"
441     else:
442         for type in modules:
443             if type == MODULE_CLASS_SCSI:
444                 print( "all scsi modules:" )
445                 for a_mod in modules[type]:
446                     print a_mod
447             elif type == MODULE_CLASS_NETWORK:
448                 print( "all network modules:" )
449                 for a_mod in modules[type]:
450                     print a_mod
451