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