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