- fix last checkin to just store the full path (e.g.,
[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]=None
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
194         try:
195             major= int(parts[0])
196             minor= int(parts[1])
197             blocks= int(parts[2])
198         except ValueError, err:
199             continue
200
201         gb_size= blocks/BLOCKS_PER_GB
202
203         # check to see if the blk device is readonly
204         try:
205             # can we write to it?
206             dev_name= "/dev/%s" % device
207             fb = open(dev_name,"w")
208             fb.close()
209             readonly=False
210         except IOError, e:
211             # check if EROFS errno
212             if errno.errorcode.get(e.errno,None) == 'EROFS':
213                 readonly=True
214             else:
215                 # got some other errno, pretend device is readonly
216                 readonly=True
217
218         devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
219
220     return devicelist
221
222
223 def get_system_modules( vars = {}, log = sys.stderr):
224     """
225     Return a list of kernel modules that this system requires.
226     This requires access to the installed system's root
227     directory, as the following files must exist and are used:
228     <install_root>/usr/share/hwdata/pcitable
229     <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
230     <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
231
232     If there are more than one kernels installed, and the kernel
233     version is not specified, then only the first one in
234     /lib/modules is used.
235
236     Returns a dictionary, keys being the type of module:
237         - scsi       MODULE_CLASS_SCSI
238         - network    MODULE_CLASS_NETWORK
239     The value being the kernel module name to load.
240
241     Some sata devices show up under an IDE device class,
242     hence the reason for checking for ide devices as well.
243     If there actually is a match in the pci -> module lookup
244     table, and its an ide device, its most likely sata,
245     as ide modules are built in to the kernel.
246     """
247
248     if not vars.has_key("SYSIMG_PATH"):
249         vars["SYSIMG_PATH"]="/"
250     SYSIMG_PATH=vars["SYSIMG_PATH"]
251
252     if not vars.has_key("NODE_MODEL_OPTIONS"):
253         vars["NODE_MODEL_OPTIONS"] = 0;
254
255     initrd, kernel_version = getKernelVersion(vars, log)
256
257     # get the kernel version we are assuming
258     if kernel_version is None:
259         try:
260             kernel_version= os.listdir( "%s/lib/modules/" % SYSIMG_PATH )
261         except OSError, e:
262             return
263
264         if len(kernel_version) == 0:
265             return
266
267         if len(kernel_version) > 1:
268             print( "WARNING: We may be returning modules for the wrong kernel." )
269
270         kernel_version= kernel_version[0]
271
272     print( "Using kernel version %s" % kernel_version )
273
274     # test to make sure the three files we need are present
275     pcitable_path = "%s/%s/pcitable" % (SYSIMG_PATH,hwdatapath)
276     modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
277                           (SYSIMG_PATH,kernel_version)
278     modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
279                        (SYSIMG_PATH,kernel_version)
280
281     for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
282         if not os.access(path,os.R_OK):
283             print( "Unable to read %s" % path )
284             return
285
286     # now, with those three files, merge them all into one easy to
287     # use lookup table
288     (all_pci_ids, all_modules) = merge_hw_tables.merge_files( modules_dep_path,
289                                                               modules_pcimap_path,
290                                                               pcitable_path )
291     if all_modules is None:
292         print( "Unable to merge pci id tables." )
293         return
294
295     # this is the actual data structure we return
296     system_mods= {}
297
298     # these are the lists that will be in system_mods
299     network_mods= []
300     scsi_mods= []
301
302
303     # get all the system devices from lspci
304     lspci_prog= popen2.Popen3( LSPCI_CMD, 1 )
305     if lspci_prog is None:
306         print( "Unable to run %s with popen2.Popen3" % LSPCI_CMD )
307         return
308
309     returncode= lspci_prog.wait()
310     if returncode != 0:
311         print( "Running %s failed" % LSPCI_CMD )
312         return
313     else:
314         print( "Successfully ran %s" % LSPCI_CMD )
315
316     # for every lspci line, parse in the four tuple PCI id and the
317     # search for the corresponding driver from the dictionary
318     # generated by merge_hw_tables
319     for line in lspci_prog.fromchild:
320         # A sample line:
321         #
322         # 00:1f.1 "Class 0101" "8086" "2411" -r02 -p80 "8086" "2411"
323         #
324         # Remove '"', 'Class ', and anything beginning with '-'
325         # (usually revisions and prog-if flags) so that we can
326         # split on whitespace:
327         #
328         # 00:1f.1 0101 8086 2411 8086 2411
329         #
330         line = line.strip()
331         line = line.replace('"', '')
332         line = line.replace('Class ', '')
333         line = re.sub('-[^ ]*', '', line)
334
335         parts = line.split()
336         try:
337             if len(parts) < 4:
338                 raise
339             classid = long(parts[1], 16)
340             vendorid = long(parts[2], 16)
341             deviceid = long(parts[3], 16)
342         except:
343             print "Invalid line:", line
344             continue
345
346         if classid not in (PCI_CLASS_NETWORK_ETHERNET,
347                            PCI_CLASS_STORAGE_SCSI,
348                            PCI_CLASS_STORAGE_SATA,
349                            PCI_CLASS_STORAGE_RAID,
350                            PCI_CLASS_STORAGE_OTHER,
351                            PCI_CLASS_STORAGE_IDE):
352             continue
353
354         # Device may have a subvendorid and subdeviceid
355         try:
356             subvendorid = long(parts[4], 16)
357             subdeviceid = long(parts[5], 16)
358         except:
359             subvendorid = PCI_ANY
360             subdeviceid = PCI_ANY
361
362         # search for driver that most closely matches the full_id
363         # to drivers that can handle any subvendor/subdevice
364         # version of the hardware.
365         full_ids = ((vendorid,deviceid,subvendorid,subdeviceid),
366                     (vendorid,deviceid,subvendorid,PCI_ANY),
367                     (vendorid,deviceid,PCI_ANY,PCI_ANY))
368
369         for full_id in full_ids:
370             module = all_pci_ids.get(full_id, None)
371             if module is not None:
372                 if classid == PCI_CLASS_NETWORK_ETHERNET:
373                     network_mods.append(module[0])
374                 elif classid in (PCI_CLASS_STORAGE_SCSI,
375                                  PCI_CLASS_STORAGE_SATA,
376                                  PCI_CLASS_STORAGE_RAID,
377                                  PCI_CLASS_STORAGE_OTHER,
378                                  PCI_CLASS_STORAGE_IDE):
379                     scsi_mods.append(module[0])
380
381                     # XXX ata_piix and ahci both claim 8086:2652 and 8086:2653,
382                     # and it is usually a non-visible BIOS option that decides
383                     # which is appropriate. Just load both.
384                     if vendorid == 0x8086 and (deviceid == 0x2652 or deviceid == 0x2653):
385                         if module[0] == "ahci":
386                             scsi_mods.append("ata_piix")
387                         elif module[0] == "ata_piix":
388                             scsi_mods.append("ahci")
389                 else:
390                     print "not network or scsi: 0x%x" % classid
391                 break
392
393     system_mods[MODULE_CLASS_SCSI]= scsi_mods
394     system_mods[MODULE_CLASS_NETWORK]= network_mods
395
396     return system_mods
397
398
399 def getKernelVersion( vars = {} , log = sys.stderr):
400     # make sure we have the variables we need
401     try:
402         SYSIMG_PATH= vars["SYSIMG_PATH"]
403         if SYSIMG_PATH == "":
404             raise ValueError, "SYSIMG_PATH"
405
406         NODE_MODEL_OPTIONS=vars["NODE_MODEL_OPTIONS"]
407     except KeyError, var:
408         raise BootManagerException, "Missing variable in vars: %s\n" % var
409     except ValueError, var:
410         raise BootManagerException, "Variable in vars, shouldn't be: %s\n" % var
411
412     option = ''
413     if NODE_MODEL_OPTIONS & ModelOptions.SMP:
414         option = 'smp'
415         try:
416             os.stat("%s/boot/kernel-boot%s" % (SYSIMG_PATH,option))
417             os.stat("%s/boot/initrd-boot%s" % (SYSIMG_PATH,option))
418         except OSError, e:
419             # smp kernel is not there; remove option from modeloptions
420             # such that the rest of the code base thinks we are just
421             # using the base kernel.
422             NODE_MODEL_OPTIONS = NODE_MODEL_OPTIONS & ~ModelOptions.SMP
423             vars["NODE_MODEL_OPTIONS"] = NODE_MODEL_OPTIONS
424             log.write( "WARNING: Couldn't locate smp kernel.\n")
425             option = ''
426     try:
427         initrd= os.readlink( "%s/boot/initrd-boot%s" % (SYSIMG_PATH,option) )
428         kernel_version= initrd.replace("initrd-", "").replace(".img", "")    
429     except OSError, e:
430         initrd = None
431         kernel_version = None
432         
433     return (initrd, kernel_version)
434
435
436 if __name__ == "__main__":
437     devices= get_block_device_list()
438     print "block devices detected:"
439     if not devices:
440         print "no devices found!"
441     else:
442         for dev in devices.keys():
443             print "%s %s" % (dev, repr(devices[dev]))
444             
445
446     print ""
447     memory= get_total_phsyical_mem()
448     if not memory:
449         print "unable to read /proc/meminfo for memory"
450     else:
451         print "total physical memory: %d kb" % memory
452         
453
454     print ""
455
456     kernel_version = None
457     if len(sys.argv) > 2:
458         kernel_version = sys.argv[1]
459         
460     modules= get_system_modules()
461     if not modules:
462         print "unable to list system modules"
463     else:
464         for type in modules:
465             if type == MODULE_CLASS_SCSI:
466                 print( "all scsi modules:" )
467                 for a_mod in modules[type]:
468                     print a_mod
469             elif type == MODULE_CLASS_NETWORK:
470                 print( "all network modules:" )
471                 for a_mod in modules[type]:
472                     print a_mod
473