Minor clean up:
[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 re
27 import errno
28 import ModelOptions
29 from pypci import *
30 from Exceptions import *
31
32 """
33 a utility class for finding and returning information about
34 block devices, memory, and other hardware on the system
35 """
36
37 PROC_MEMINFO_PATH= "/proc/meminfo"
38 PROC_PARTITIONS_PATH= "/proc/partitions"
39
40 # set when the sfdisk -l <dev> trick has been done to make
41 # all devices show up
42 DEVICES_SCANNED_FLAG= "/tmp/devices_scanned"
43
44 # a /proc/partitions block is 1024 bytes
45 # a GB to a HDD manufacturer is 10^9 bytes
46 BLOCKS_PER_GB = pow(10, 9) / 1024.0;
47
48
49 # -n is numeric ids (no lookup), -m is machine readable
50 LSPCI_CMD= "/sbin/lspci -nm"
51
52 MODULE_CLASS_NETWORK= "network"
53 MODULE_CLASS_SCSI= "scsi"
54
55 def get_total_phsyical_mem(vars = {}, log = sys.stderr):
56     """
57     return the total physical memory of the machine, in kilobytes.
58
59     Return None if /proc/meminfo not readable.
60     """
61
62     try:
63         meminfo_file= file(PROC_MEMINFO_PATH,"r")
64     except IOError, e:
65         return
66
67     total_memory= None
68
69     for line in meminfo_file:
70
71         try:
72             (fieldname,value)= string.split(line,":")
73         except ValueError, e:
74             # this will happen for lines that don't have two values
75             # (like the first line on 2.4 kernels)
76             continue
77
78         fieldname= string.strip(fieldname)
79         value= string.strip(value)
80         
81         if fieldname == "MemTotal":
82             try:
83                 (total_memory,units)= string.split(value)
84             except ValueError, e:
85                 return
86
87             if total_memory == "" or total_memory == None or \
88                    units == "" or units == None:
89                 return
90
91             if string.lower(units) != "kb":
92                 return
93
94             try:
95                 total_memory= int(total_memory)
96             except ValueError, e:
97                 return
98
99             break
100
101     meminfo_file.close()
102     return total_memory
103
104 def get_block_device_list(vars = {}, log = sys.stderr):
105     """
106     get a list of block devices from this system.
107     return an associative array, where the device name
108     (full /dev/device path) is the key, and the value
109     is a tuple of (major,minor,numblocks,gb_size,readonly)
110     """
111
112     # make sure we can access to the files/directories in /proc
113     if not os.access(PROC_PARTITIONS_PATH, os.F_OK):
114         return None
115
116     # table with valid scsi/sata/ide/raid block device names
117     valid_blk_names = {}
118     # add in valid sd and hd block device names
119     for blk_prefix in ('sd','hd'):
120         for blk_num in map ( \
121             lambda x: chr(x), range(ord('a'),ord('z')+1)):
122             devicename="%s%c" % (blk_prefix, blk_num)
123             valid_blk_names[devicename]=None
124
125     # add in valid scsi raid block device names
126     for M in range(0,1+1):
127         for N in range(0,7+1):
128             devicename = "cciss/c%dd%d" % (M,N)
129             valid_blk_names[devicename]=None
130
131     for devicename in valid_blk_names.keys():
132         # devfs under 2.4 (old boot cds) used to list partitions
133         # in a format such as scsi/host0/bus0/target0/lun0/disc
134         # and /dev/sda, etc. were just symlinks
135         try:
136             devfsname= os.readlink( "/dev/%s" % devicename )
137             valid_blk_names[devfsname]=None
138         except OSError:
139             pass
140
141     # only do this once every system boot
142     if not os.access(DEVICES_SCANNED_FLAG, os.R_OK):
143
144         # this is ugly. under devfs, device
145         # entries in /dev/scsi/.. and /dev/ide/...
146         # don't show up until you attempt to read
147         # from the associated device at /dev (/dev/sda).
148         # so, lets run sfdisk -l (list partitions) against
149         # most possible block devices, that way they show
150         # up when it comes time to do the install.
151         devicenames = valid_blk_names.keys()
152         devicenames.sort()
153         for devicename in devicenames:
154             os.system( "sfdisk -l /dev/%s > /dev/null 2>&1" % devicename )
155
156         # touch file
157         fb = open(DEVICES_SCANNED_FLAG,"w")
158         fb.close()
159
160     devicelist= {}
161
162     partitions_file= file(PROC_PARTITIONS_PATH,"r")
163     line_count= 0
164     for line in partitions_file:
165         line_count= line_count + 1
166
167         # skip the first two lines always
168         if line_count < 2:
169             continue
170
171         parts= string.split(line)
172
173         if len(parts) < 4:
174             continue
175
176         device= parts[3]
177
178         # skip and ignore any partitions
179         if not valid_blk_names.has_key(device):
180             continue
181
182         try:
183             major= int(parts[0])
184             minor= int(parts[1])
185             blocks= int(parts[2])
186         except ValueError, err:
187             continue
188
189         gb_size= blocks/BLOCKS_PER_GB
190
191         # check to see if the blk device is readonly
192         try:
193             # can we write to it?
194             dev_name= "/dev/%s" % device
195             fb = open(dev_name,"w")
196             fb.close()
197             readonly=False
198         except IOError, e:
199             # check if EROFS errno
200             if errno.errorcode.get(e.errno,None) == 'EROFS':
201                 readonly=True
202             else:
203                 # got some other errno, pretend device is readonly
204                 readonly=True
205
206         devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
207
208     return devicelist
209
210
211 def get_system_modules( vars = {}, log = sys.stderr):
212     """
213     Return a list of kernel modules that this system requires.
214     This requires access to the installed system's root
215     directory, as the following file must exist and is used:
216     <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
217
218     If there are more than one kernels installed, and the kernel
219     version is not specified, then only the first one in
220     /lib/modules is used.
221
222     Returns a dictionary, keys being the type of module:
223         - scsi       MODULE_CLASS_SCSI
224         - network    MODULE_CLASS_NETWORK
225     The value being the kernel module name to load.
226
227     Some sata devices show up under an IDE device class,
228     hence the reason for checking for ide devices as well.
229     If there actually is a match in the pci -> module lookup
230     table, and its an ide device, its most likely sata,
231     as ide modules are built in to the kernel.
232     """
233
234     if not vars.has_key("SYSIMG_PATH"):
235         vars["SYSIMG_PATH"]="/"
236     SYSIMG_PATH=vars["SYSIMG_PATH"]
237
238     if not vars.has_key("NODE_MODEL_OPTIONS"):
239         vars["NODE_MODEL_OPTIONS"] = 0;
240
241     initrd, kernel_version = getKernelVersion(vars, log)
242
243     # get the kernel version we are assuming
244     if kernel_version is None:
245         try:
246             kernel_version= os.listdir( "%s/lib/modules/" % SYSIMG_PATH )
247         except OSError, e:
248             return
249
250         if len(kernel_version) == 0:
251             return
252
253         if len(kernel_version) > 1:
254             print( "WARNING: We may be returning modules for the wrong kernel." )
255
256         kernel_version= kernel_version[0]
257
258     print( "Using kernel version %s" % kernel_version )
259
260     # test to make sure the file we need is present
261     modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
262                           (SYSIMG_PATH,kernel_version)
263     if not os.access(modules_pcimap_path,os.R_OK):
264         print( "WARNING: Unable to read %s" % modules_pcimap_path )
265         return
266
267     pcimap = pypcimap.PCIMap(modules_pcimap_path)
268
269     # this is the actual data structure we return
270     system_mods= {}
271
272     # these are the lists that will be in system_mods
273     network_mods= []
274     scsi_mods= []
275
276     # XXX: this is really similar to what BootCD/conf_files/pl_hwinit does. merge?
277     pcidevs = get_devices()
278
279     for (slot, dev) in pcidevs.iteritems():
280         base = (dev[4] & 0xff0000) >> 16
281         if base not in (PCI_BASE_CLASS_STORAGE,
282                         PCI_BASE_CLASS_NETWORK):
283             continue
284
285         modules = pcimap.get(dev)
286         if len(modules) > 0:
287             if base == PCI_BASE_CLASS_NETWORK:
288                 network_mods += modules
289             elif base == PCI_BASE_CLASS_STORAGE:
290                 scsi_mods += modules
291
292     system_mods[MODULE_CLASS_SCSI]= scsi_mods
293     system_mods[MODULE_CLASS_NETWORK]= network_mods
294
295     return system_mods
296
297
298 def getKernelVersion( vars = {} , log = sys.stderr):
299     # make sure we have the variables we need
300     try:
301         SYSIMG_PATH= vars["SYSIMG_PATH"]
302         if SYSIMG_PATH == "":
303             raise ValueError, "SYSIMG_PATH"
304
305         NODE_MODEL_OPTIONS=vars["NODE_MODEL_OPTIONS"]
306     except KeyError, var:
307         raise BootManagerException, "Missing variable in vars: %s\n" % var
308     except ValueError, var:
309         raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
310
311     option = ''
312     if NODE_MODEL_OPTIONS & ModelOptions.SMP:
313         option = 'smp'
314         try:
315             os.stat("%s/boot/kernel-boot%s" % (SYSIMG_PATH,option))
316             os.stat("%s/boot/initrd-boot%s" % (SYSIMG_PATH,option))
317         except OSError, e:
318             # smp kernel is not there; remove option from modeloptions
319             # such that the rest of the code base thinks we are just
320             # using the base kernel.
321             NODE_MODEL_OPTIONS = NODE_MODEL_OPTIONS & ~ModelOptions.SMP
322             vars["NODE_MODEL_OPTIONS"] = NODE_MODEL_OPTIONS
323             log.write( "WARNING: Couldn't locate smp kernel.\n")
324             option = ''
325     try:
326         initrd= os.readlink( "%s/boot/initrd-boot%s" % (SYSIMG_PATH,option) )
327         kernel_version= initrd.replace("initrd-", "").replace(".img", "")    
328     except OSError, e:
329         initrd = None
330         kernel_version = None
331         
332     return (initrd, kernel_version)
333
334
335 if __name__ == "__main__":
336     devices= get_block_device_list()
337     print "block devices detected:"
338     if not devices:
339         print "no devices found!"
340     else:
341         for dev in devices.keys():
342             print "%s %s" % (dev, repr(devices[dev]))
343             
344
345     print ""
346     memory= get_total_phsyical_mem()
347     if not memory:
348         print "unable to read /proc/meminfo for memory"
349     else:
350         print "total physical memory: %d kb" % memory
351         
352
353     print ""
354
355     kernel_version = None
356     if len(sys.argv) > 2:
357         kernel_version = sys.argv[1]
358         
359     modules= get_system_modules()
360     if not modules:
361         print "unable to list system modules"
362     else:
363         for module_class in (MODULE_CLASS_SCSI,MODULE_CLASS_NETWORK):
364             if len(modules[module_class]) > 0:
365                 module_list = ""
366                 for a_mod in modules[module_class]:
367                     module_list = module_list + "%s " % a_mod
368                 print "all %s modules: %s" % (module_class, module_list)
369