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