68023861d007ee6bf99dff1110eeead11c773b66
[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 merge_hw_tables
27 import re
28 import errno
29 import ModelOptions
30 from Exceptions import *
31
32 hwdatapath = "usr/share/hwdata"
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_CLASS_NETWORK_ETHERNET=0x0200L
57 PCI_CLASS_STORAGE_SCSI=0x0100L
58 PCI_CLASS_STORAGE_SATA=0x0106L
59 PCI_CLASS_STORAGE_IDE=0x0101L
60 PCI_CLASS_STORAGE_FLOPPY=0x0102L
61 PCI_CLASS_STORAGE_IPI=0x0103L
62 PCI_CLASS_STORAGE_RAID=0x0104L
63 PCI_CLASS_STORAGE_OTHER=0x0180L
64
65 PCI_ANY=0xffffffffL
66
67 def get_total_phsyical_mem(vars = {}, log = sys.stderr):
68     """
69     return the total physical memory of the machine, in kilobytes.
70
71     Return None if /proc/meminfo not readable.
72     """
73
74     try:
75         meminfo_file= file(PROC_MEMINFO_PATH,"r")
76     except IOError, e:
77         return
78
79     total_memory= None
80
81     for line in meminfo_file:
82
83         try:
84             (fieldname,value)= string.split(line,":")
85         except ValueError, e:
86             # this will happen for lines that don't have two values
87             # (like the first line on 2.4 kernels)
88             continue
89
90         fieldname= string.strip(fieldname)
91         value= string.strip(value)
92         
93         if fieldname == "MemTotal":
94             try:
95                 (total_memory,units)= string.split(value)
96             except ValueError, e:
97                 return
98
99             if total_memory == "" or total_memory == None or \
100                    units == "" or units == None:
101                 return
102
103             if string.lower(units) != "kb":
104                 return
105
106             try:
107                 total_memory= int(total_memory)
108             except ValueError, e:
109                 return
110
111             break
112
113     meminfo_file.close()
114     return total_memory
115
116 def get_block_device_list(vars = {}, log = sys.stderr):
117     """
118     get a list of block devices from this system.
119     return an associative array, where the device name
120     (full /dev/device path) is the key, and the value
121     is a tuple of (major,minor,numblocks,gb_size,readonly)
122     """
123
124     # make sure we can access to the files/directories in /proc
125     if not os.access(PROC_PARTITIONS_PATH, os.F_OK):
126         return None
127
128     # table with valid scsi/sata/ide/raid block device names
129     valid_blk_names = {}
130     # add in valid sd and hd block device names
131     for blk_prefix in ('sd','hd'):
132         for blk_num in map ( \
133             lambda x: chr(x), range(ord('a'),ord('z')+1)):
134             devicename="%s%c" % (blk_prefix, blk_num)
135             valid_blk_names[devicename]=None
136
137     # add in valid scsi raid block device names
138     for M in range(0,1+1):
139         for N in range(0,7+1):
140             devicename = "cciss/c%dd%d" % (M,N)
141             valid_blk_names[devicename]=None
142
143     for devicename in valid_blk_names.keys():
144         # devfs under 2.4 (old boot cds) used to list partitions
145         # in a format such as scsi/host0/bus0/target0/lun0/disc
146         # and /dev/sda, etc. were just symlinks
147         try:
148             devfsname= os.readlink( "/dev/%s" % devicename )
149             valid_blk_names[devfsname]= devicename
150         except OSError:
151             pass
152
153     # only do this once every system boot
154     if not os.access(DEVICES_SCANNED_FLAG, os.R_OK):
155
156         # this is ugly. under devfs, device
157         # entries in /dev/scsi/.. and /dev/ide/...
158         # don't show up until you attempt to read
159         # from the associated device at /dev (/dev/sda).
160         # so, lets run sfdisk -l (list partitions) against
161         # most possible block devices, that way they show
162         # up when it comes time to do the install.
163         devicenames = valid_blk_names.keys()
164         devicenames.sort()
165         for devicename in devicenames:
166             os.system( "sfdisk -l /dev/%s > /dev/null 2>&1" % devicename )
167
168         # touch file
169         fb = open(DEVICES_SCANNED_FLAG,"w")
170         fb.close()
171
172     devicelist= {}
173
174     partitions_file= file(PROC_PARTITIONS_PATH,"r")
175     line_count= 0
176     for line in partitions_file:
177         line_count= line_count + 1
178
179         # skip the first two lines always
180         if line_count < 2:
181             continue
182
183         parts= string.split(line)
184
185         if len(parts) < 4:
186             continue
187
188         device= parts[3]
189
190         # skip and ignore any partitions
191         if not valid_blk_names.has_key(device):
192             continue
193         elif valid_blk_names[device] is not None:
194             device= valid_blk_names[device]
195
196         try:
197             major= int(parts[0])
198             minor= int(parts[1])
199             blocks= int(parts[2])
200         except ValueError, err:
201             continue
202
203         gb_size= blocks/BLOCKS_PER_GB
204
205         # check to see if the blk device is readonly
206         try:
207             # can we write to it?
208             dev_name= "/dev/%s" % device
209             fb = open(dev_name,"w")
210             fb.close()
211             readonly=False
212         except IOError, e:
213             # check if EROFS errno
214             if errno.errorcode.get(e.errno,None) == 'EROFS':
215                 readonly=True
216             else:
217                 # got some other errno, pretend device is readonly
218                 readonly=True
219
220         devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
221
222     return devicelist
223
224
225 def get_system_modules( vars = {}, log = sys.stderr):
226     """
227     Return a list of kernel modules that this system requires.
228     This requires access to the installed system's root
229     directory, as the following files must exist and are used:
230     <install_root>/usr/share/hwdata/pcitable
231     <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
232     <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
233
234     If there are more than one kernels installed, and the kernel
235     version is not specified, then only the first one in
236     /lib/modules is used.
237
238     Returns a dictionary, keys being the type of module:
239         - scsi       MODULE_CLASS_SCSI
240         - network    MODULE_CLASS_NETWORK
241     The value being the kernel module name to load.
242
243     Some sata devices show up under an IDE device class,
244     hence the reason for checking for ide devices as well.
245     If there actually is a match in the pci -> module lookup
246     table, and its an ide device, its most likely sata,
247     as ide modules are built in to the kernel.
248     """
249
250     if not vars.has_key("SYSIMG_PATH"):
251         vars["SYSIMG_PATH"]="/"
252     SYSIMG_PATH=vars["SYSIMG_PATH"]
253
254     if not vars.has_key("NODE_MODEL_OPTIONS"):
255         vars["NODE_MODEL_OPTIONS"] = 0;
256
257     initrd, kernel_version = getKernelVersion(vars, log)
258
259     # get the kernel version we are assuming
260     if kernel_version is None:
261         try:
262             kernel_version= os.listdir( "%s/lib/modules/" % SYSIMG_PATH )
263         except OSError, e:
264             return
265
266         if len(kernel_version) == 0:
267             return
268
269         if len(kernel_version) > 1:
270             print( "WARNING: We may be returning modules for the wrong kernel." )
271
272         kernel_version= kernel_version[0]
273
274     print( "Using kernel version %s" % kernel_version )
275
276     # test to make sure the three files we need are present
277     pcitable_path = "%s/%s/pcitable" % (SYSIMG_PATH,hwdatapath)
278     modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
279                           (SYSIMG_PATH,kernel_version)
280     modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
281                        (SYSIMG_PATH,kernel_version)
282
283     for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
284         if not os.access(path,os.R_OK):
285             print( "Unable to read %s" % path )
286             return
287
288     # now, with those three files, merge them all into one easy to
289     # use lookup table
290     (all_pci_ids, all_modules) = merge_hw_tables.merge_files( modules_dep_path,
291                                                               modules_pcimap_path,
292                                                               pcitable_path )
293     if all_modules is None:
294         print( "Unable to merge pci id tables." )
295         return
296
297     # this is the actual data structure we return
298     system_mods= {}
299
300     # these are the lists that will be in system_mods
301     network_mods= []
302     scsi_mods= []
303
304
305     # get all the system devices from lspci
306     lspci_prog= popen2.Popen3( LSPCI_CMD, 1 )
307     if lspci_prog is None:
308         print( "Unable to run %s with popen2.Popen3" % LSPCI_CMD )
309         return
310
311     returncode= lspci_prog.wait()
312     if returncode != 0:
313         print( "Running %s failed" % LSPCI_CMD )
314         return
315     else:
316         print( "Successfully ran %s" % LSPCI_CMD )
317
318     # for every lspci line, parse in the four tuple PCI id and the
319     # search for the corresponding driver from the dictionary
320     # generated by merge_hw_tables
321     for line in lspci_prog.fromchild:
322         # A sample line:
323         #
324         # 00:1f.1 "Class 0101" "8086" "2411" -r02 -p80 "8086" "2411"
325         #
326         # Remove '"', 'Class ', and anything beginning with '-'
327         # (usually revisions and prog-if flags) so that we can
328         # split on whitespace:
329         #
330         # 00:1f.1 0101 8086 2411 8086 2411
331         #
332         line = line.strip()
333         line = line.replace('"', '')
334         line = line.replace('Class ', '')
335         line = re.sub('-[^ ]*', '', line)
336
337         parts = line.split()
338         try:
339             if len(parts) < 4:
340                 raise
341             classid = long(parts[1], 16)
342             vendorid = long(parts[2], 16)
343             deviceid = long(parts[3], 16)
344         except:
345             print "Invalid line:", line
346             continue
347
348         if classid not in (PCI_CLASS_NETWORK_ETHERNET,
349                            PCI_CLASS_STORAGE_SCSI,
350                            PCI_CLASS_STORAGE_SATA,
351                            PCI_CLASS_STORAGE_RAID,
352                            PCI_CLASS_STORAGE_OTHER,
353                            PCI_CLASS_STORAGE_IDE):
354             continue
355
356         # Device may have a subvendorid and subdeviceid
357         try:
358             subvendorid = long(parts[4], 16)
359             subdeviceid = long(parts[5], 16)
360         except:
361             subvendorid = PCI_ANY
362             subdeviceid = PCI_ANY
363
364         # search for driver that most closely matches the full_id
365         # to drivers that can handle any subvendor/subdevice
366         # version of the hardware.
367         full_ids = ((vendorid,deviceid,subvendorid,subdeviceid),
368                     (vendorid,deviceid,subvendorid,PCI_ANY),
369                     (vendorid,deviceid,PCI_ANY,PCI_ANY))
370
371         for full_id in full_ids:
372             module = all_pci_ids.get(full_id, None)
373             if module is not None:
374                 if classid == PCI_CLASS_NETWORK_ETHERNET:
375                     network_mods.append(module[0])
376                 elif classid in (PCI_CLASS_STORAGE_SCSI,
377                                  PCI_CLASS_STORAGE_SATA,
378                                  PCI_CLASS_STORAGE_RAID,
379                                  PCI_CLASS_STORAGE_OTHER,
380                                  PCI_CLASS_STORAGE_IDE):
381                     scsi_mods.append(module[0])
382
383                     # XXX ata_piix and ahci both claim 8086:2652 and 8086:2653,
384                     # and it is usually a non-visible BIOS option that decides
385                     # which is appropriate. Just load both.
386                     if vendorid == 0x8086 and (deviceid == 0x2652 or deviceid == 0x2653):
387                         if module[0] == "ahci":
388                             scsi_mods.append("ata_piix")
389                         elif module[0] == "ata_piix":
390                             scsi_mods.append("ahci")
391                 else:
392                     print "not network or scsi: 0x%x" % classid
393                 break
394
395     system_mods[MODULE_CLASS_SCSI]= scsi_mods
396     system_mods[MODULE_CLASS_NETWORK]= network_mods
397
398     return system_mods
399
400
401 def getKernelVersion( vars = {} , log = sys.stderr):
402     # make sure we have the variables we need
403     try:
404         SYSIMG_PATH= vars["SYSIMG_PATH"]
405         if SYSIMG_PATH == "":
406             raise ValueError, "SYSIMG_PATH"
407
408         NODE_MODEL_OPTIONS=vars["NODE_MODEL_OPTIONS"]
409     except KeyError, var:
410         raise BootManagerException, "Missing variable in vars: %s\n" % var
411     except ValueError, var:
412         raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
413
414     option = ''
415     if NODE_MODEL_OPTIONS & ModelOptions.SMP:
416         option = 'smp'
417         try:
418             os.stat("%s/boot/kernel-boot%s" % (SYSIMG_PATH,option))
419             os.stat("%s/boot/initrd-boot%s" % (SYSIMG_PATH,option))
420         except OSError, e:
421             # smp kernel is not there; remove option from modeloptions
422             # such that the rest of the code base thinks we are just
423             # using the base kernel.
424             NODE_MODEL_OPTIONS = NODE_MODEL_OPTIONS & ~ModelOptions.SMP
425             vars["NODE_MODEL_OPTIONS"] = NODE_MODEL_OPTIONS
426             log.write( "WARNING: Couldn't locate smp kernel.\n")
427             option = ''
428     try:
429         initrd= os.readlink( "%s/boot/initrd-boot%s" % (SYSIMG_PATH,option) )
430         kernel_version= initrd.replace("initrd-", "").replace(".img", "")    
431     except OSError, e:
432         initrd = None
433         kernel_version = None
434         
435     return (initrd, kernel_version)
436
437
438 if __name__ == "__main__":
439     devices= get_block_device_list()
440     print "block devices detected:"
441     if not devices:
442         print "no devices found!"
443     else:
444         for dev in devices.keys():
445             print "%s %s" % (dev, repr(devices[dev]))
446             
447
448     print ""
449     memory= get_total_phsyical_mem()
450     if not memory:
451         print "unable to read /proc/meminfo for memory"
452     else:
453         print "total physical memory: %d kb" % memory
454         
455
456     print ""
457
458     kernel_version = None
459     if len(sys.argv) > 2:
460         kernel_version = sys.argv[1]
461         
462     modules= get_system_modules()
463     if not modules:
464         print "unable to list system modules"
465     else:
466         for type in modules:
467             if type == MODULE_CLASS_SCSI:
468                 print( "all scsi modules:" )
469                 for a_mod in modules[type]:
470                     print a_mod
471             elif type == MODULE_CLASS_NETWORK:
472                 print( "all network modules:" )
473                 for a_mod in modules[type]:
474                     print a_mod
475