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