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