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