Fix get_block_device_list() function:
[bootmanager.git] / source / systeminfo.py
1 #!/usr/bin/python2
2
3 # --------------
4 # THIS file used to be named 'blockdevicescan.py', but has been renamed
5 # systeminfo.py and made more generic (now includes info about memory,
6 # and other hardware info on the machine)
7 # --------------
8
9 # Copyright (c) 2003 Intel Corporation
10 # All rights reserved.
11
12 # Redistribution and use in source and binary forms, with or without
13 # modification, are permitted provided that the following conditions are
14 # met:
15
16 #     * Redistributions of source code must retain the above copyright
17 #       notice, this list of conditions and the following disclaimer.
18
19 #     * Redistributions in binary form must reproduce the above
20 #       copyright notice, this list of conditions and the following
21 #       disclaimer in the documentation and/or other materials provided
22 #       with the distribution.
23
24 #     * Neither the name of the Intel Corporation nor the names of its
25 #       contributors may be used to endorse or promote products derived
26 #       from this software without specific prior written permission.
27
28 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
32 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
35 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
36 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
40 # EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
41 # YOUR JURISDICTION. It is licensee's responsibility to comply with any
42 # export regulations applicable in licensee's jurisdiction. Under
43 # CURRENT (May 2000) U.S. export regulations this software is eligible
44 # for export from the U.S. and can be downloaded by or otherwise
45 # exported or reexported worldwide EXCEPT to U.S. embargoed destinations
46 # which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
47 # Afghanistan and any other country to which the U.S. has embargoed
48 # goods and services.
49
50
51 # expected /proc/partitions format
52 #
53 #----------------------------------------------------
54 #major minor  #blocks  name
55 #
56 #3     0   40017915 hda
57 #3     1     208813 hda1
58 #3     2   20482875 hda2
59 #3     3     522112 hda3
60 #3     4   18804082 hda4
61 #----------------------------------------------------
62
63
64 import string
65 import os
66 import popen2
67 import merge_hw_tables
68 import re
69 import errno
70
71 hwdatapath = "usr/share/hwdata"
72 class systeminfo:
73     """
74     a utility class for finding and returning information about
75     block devices, memory, and other hardware on the system
76     """
77
78     PROC_MEMINFO_PATH= "/proc/meminfo"
79
80
81     PROC_PARTITIONS_PATH= "/proc/partitions"
82
83     # set when the sfdisk -l <dev> trick has been done to make
84     # all devices show up
85     DEVICES_SCANNED_FLAG= "/tmp/devices_scanned"
86         
87     # a /proc/partitions block is 1024 bytes
88     # a GB to a HDD manufacturer is 10^9 bytes
89     BLOCKS_PER_GB = pow(10, 9) / 1024.0;
90
91
92     # -n is numeric ids (no lookup), -m is machine readable
93     LSPCI_CMD= "/sbin/lspci -nm"
94     
95     MODULE_CLASS_NETWORK= "network"
96     MODULE_CLASS_SCSI= "scsi"
97
98     PCI_CLASS_NETWORK_ETHERNET=0x0200L
99     PCI_CLASS_STORAGE_SCSI=0x0100L
100     PCI_CLASS_STORAGE_IDE=0x0101L
101     PCI_CLASS_STORAGE_FLOPPY=0x0102L
102     PCI_CLASS_STORAGE_IPI=0x0103L
103     PCI_CLASS_STORAGE_RAID=0x0104L
104     PCI_CLASS_STORAGE_OTHER=0x0180L
105
106     PCI_ANY=0xffffffffL
107
108     def get_total_phsyical_mem(self):
109         """
110         return the total physical memory of the machine, in kilobytes.
111
112         Return None if /proc/meminfo not readable.
113         """
114         
115         try:
116             meminfo_file= file(self.PROC_MEMINFO_PATH,"r")
117         except IOError, e:
118             return
119
120         total_memory= None
121         
122         for line in meminfo_file:
123
124             try:
125                 (fieldname,value)= string.split(line,":")
126             except ValueError, e:
127                 # this will happen for lines that don't have two values
128                 # (like the first line on 2.4 kernels)
129                 continue
130
131             fieldname= string.strip(fieldname)
132             value= string.strip(value)
133             
134             if fieldname == "MemTotal":
135                 try:
136                     (total_memory,units)= string.split(value)
137                 except ValueError, e:
138                     return
139                 
140                 if total_memory == "" or total_memory == None or \
141                        units == "" or units == None:
142                     return
143
144                 if string.lower(units) != "kb":
145                     return
146
147                 try:
148                     total_memory= int(total_memory)
149                 except ValueError, e:
150                     return
151
152                 break
153
154         meminfo_file.close()
155
156         return total_memory
157
158
159
160     def get_block_device_list(self):
161         """
162         get a list of block devices from this system.
163         return an associative array, where the device name
164         (full /dev/device path) is the key, and the value
165         is a tuple of (major,minor,numblocks,gb_size,readonly)
166         """
167         
168         # make sure we can access to the files/directories in /proc
169         if not os.access(self.PROC_PARTITIONS_PATH, os.F_OK):
170             return None
171
172         # table with valid scsi/sata/ide/raid block device names
173         valid_blk_names = {}
174         # add in valid sd and hd block device names
175         for blk_prefix in ('sd','hd'):
176             for blk_num in map ( \
177                 lambda x: chr(x), range(ord('a'),ord('z')+1)):
178                 devicename="%s%c" % (blk_prefix, blk_num)
179                 valid_blk_names[devicename]=None
180
181         # add in valid scsi raid block device names
182         for M in range(0,1+1):
183             for N in range(0,7+1):
184                 devicename = "cciss/c%dd%d" % (M,N)
185                 valid_blk_names[devicename]=None
186
187         # only do this once every system boot
188         if not os.access(self.DEVICES_SCANNED_FLAG, os.R_OK):
189             
190             # this is ugly. under devfs, device
191             # entries in /dev/scsi/.. and /dev/ide/...
192             # don't show up until you attempt to read
193             # from the associated device at /dev (/dev/sda).
194             # so, lets run sfdisk -l (list partitions) against
195             # most possible block devices, that way they show
196             # up when it comes time to do the install.
197             devicenames = valid_blk_names.keys()
198             devicenames.sort()
199             for devicename in devicenames:
200                 os.system( "sfdisk -l /dev/%s > /dev/null 2>&1" % devicename )
201
202             # touch file
203             fb = open(self.DEVICES_SCANNED_FLAG,"w")
204             fb.close()
205
206         devicelist= {}
207
208         partitions_file= file(self.PROC_PARTITIONS_PATH,"r")
209         line_count= 0
210         for line in partitions_file:
211             line_count= line_count + 1
212
213             # skip the first two lines always
214             if line_count < 2:
215                 continue
216
217             parts= string.split(line)
218
219             if len(parts) < 4:
220                 continue
221             
222             device= parts[3]
223             
224             # skip and ignore any partitions
225             if not valid_blk_names.has_key(device):
226                 continue
227
228             try:
229                 major= int(parts[0])
230                 minor= int(parts[1])
231                 blocks= int(parts[2])
232             except ValueError, err:
233                 continue
234
235             gb_size= blocks/self.BLOCKS_PER_GB
236             
237             # check to see if the blk device is readonly
238             try:
239                 # can we write to it?
240                 dev_name= "/dev/%s" % device
241                 fb = open(dev_name,"w")
242                 fb.close()
243                 readonly=False
244             except IOError, e:
245                 # check if EROFS errno
246                 if errno.errorcode.get(e.errno,None) == 'EROFS':
247                     readonly=True
248                 else:
249                     # got some other errno, pretend device is readonly
250                     readonly=True
251
252             devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
253
254         return devicelist
255
256
257
258     def get_system_modules( self, install_root, kernel_version= None ):
259         """
260         Return a list of kernel modules that this system requires.
261         This requires access to the installed system's root
262         directory, as the following files must exist and are used:
263         <install_root>/usr/share/hwdata/pcitable
264         <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
265         <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
266
267         If there are more than one kernels installed, and the kernel
268         version is not specified, then only the first one in
269         /lib/modules is used.
270
271         Returns a dictionary, keys being the type of module:
272          - scsi       MODULE_CLASS_SCSI
273          - network    MODULE_CLASS_NETWORK
274         The value being the kernel module name to load.
275
276         Some sata devices show up under an IDE device class,
277         hence the reason for checking for ide devices as well.
278         If there actually is a match in the pci -> module lookup
279         table, and its an ide device, its most likely sata,
280         as ide modules are built in to the kernel.
281         """
282
283         # get the kernel version we are assuming
284         if kernel_version is None:
285             try:
286                 kernel_version= os.listdir( "%s/lib/modules/" % install_root )
287             except OSError, e:
288                 return
289
290             if len(kernel_version) == 0:
291                 return
292
293             if len(kernel_version) > 1:
294                 print( "WARNING: We may be returning modules for the wrong kernel." )
295
296             kernel_version= kernel_version[0]
297
298         print( "Using kernel version %s" % kernel_version )
299
300         # test to make sure the three files we need are present
301         pcitable_path = "%s/%s/pcitable" % (install_root,hwdatapath)
302         modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
303                               (install_root,kernel_version)
304         modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
305                            (install_root,kernel_version)
306
307         for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
308             if not os.access(path,os.R_OK):
309                 print( "Unable to read %s" % path )
310                 return
311
312         # now, with those three files, merge them all into one easy to
313         # use lookup table
314         (all_pci_ids, all_modules) = merge_hw_tables.merge_files( modules_dep_path,
315                                                                   modules_pcimap_path,
316                                                                   pcitable_path )
317         if all_modules is None:
318             print( "Unable to merge pci id tables." )
319             return
320
321         # this is the actual data structure we return
322         system_mods= {}
323
324         # these are the lists that will be in system_mods
325         network_mods= []
326         scsi_mods= []
327
328
329         # get all the system devices from lspci
330         lspci_prog= popen2.Popen3( self.LSPCI_CMD, 1 )
331         if lspci_prog is None:
332             print( "Unable to run %s with popen2.Popen3" % self.LSPCI_CMD )
333             return
334         
335         returncode= lspci_prog.wait()
336         if returncode != 0:
337             print( "Running %s failed" % self.LSPCI_CMD )
338             return
339         else:
340             print( "Successfully ran %s" % self.LSPCI_CMD )
341             
342         # for every lspci line, parse in the four tuple PCI id and the
343         # search for the corresponding driver from the dictionary
344         # generated by merge_hw_tables
345         for line in lspci_prog.fromchild:
346             # A sample line:
347             #
348             # 00:1f.1 "Class 0101" "8086" "2411" -r02 -p80 "8086" "2411"
349             #
350             # Remove '"', 'Class ', and anything beginning with '-'
351             # (usually revisions and prog-if flags) so that we can
352             # split on whitespace:
353             #
354             # 00:1f.1 0101 8086 2411 8086 2411
355             #
356             line = line.strip()
357             line = line.replace('"', '')
358             line = line.replace('Class ', '')
359             line = re.sub('-[^ ]*', '', line)
360
361             parts = line.split()
362             try:
363                 if len(parts) < 4:
364                     raise
365                 classid = long(parts[1], 16)
366                 vendorid = long(parts[2], 16)
367                 deviceid = long(parts[3], 16)
368             except:
369                 print "Invalid line:", line
370                 continue
371
372             if classid not in (self.PCI_CLASS_NETWORK_ETHERNET,
373                                self.PCI_CLASS_STORAGE_SCSI,
374                                self.PCI_CLASS_STORAGE_RAID,
375                                self.PCI_CLASS_STORAGE_OTHER,
376                                self.PCI_CLASS_STORAGE_IDE):
377                 continue
378
379             # Device may have a subvendorid and subdeviceid
380             try:
381                 subvendorid = long(parts[4], 16)
382                 subdeviceid = long(parts[5], 16)
383             except:
384                 subvendorid = self.PCI_ANY
385                 subdeviceid = self.PCI_ANY
386
387             # search for driver that most closely matches the full_id
388             # to drivers that can handle any subvendor/subdevice
389             # version of the hardware.
390             full_ids = ((vendorid,deviceid,subvendorid,subdeviceid),
391                         (vendorid,deviceid,subvendorid,self.PCI_ANY),
392                         (vendorid,deviceid,self.PCI_ANY,self.PCI_ANY))
393
394             for full_id in full_ids:
395                 module = all_pci_ids.get(full_id, None)
396                 if module is not None:
397                     if classid == self.PCI_CLASS_NETWORK_ETHERNET:
398                         network_mods.append(module[0])
399                     elif classid in (self.PCI_CLASS_STORAGE_SCSI,
400                                      self.PCI_CLASS_STORAGE_RAID,
401                                      self.PCI_CLASS_STORAGE_OTHER,
402                                      self.PCI_CLASS_STORAGE_IDE):
403                         scsi_mods.append(module[0])
404                     else:
405                         print "not network or scsi: 0x%x" % classid
406                     break
407
408         system_mods[self.MODULE_CLASS_SCSI]= scsi_mods
409         system_mods[self.MODULE_CLASS_NETWORK]= network_mods
410         
411         return system_mods
412
413
414     def remove_quotes( self, str ):
415         """
416         remove double quotes off of a string if they exist
417         """
418         if str == "" or str == None:
419             return str
420         if str[:1] == '"':
421             str= str[1:]
422         if str[len(str)-1] == '"':
423             str= str[:len(str)-1]
424         return str
425
426
427
428 if __name__ == "__main__":
429     info= systeminfo()
430     
431     devices= info.get_block_device_list()
432     print "block devices detected:"
433     if not devices:
434         print "no devices found!"
435     else:
436         for dev in devices.keys():
437             print "%s %s" % (dev, repr(devices[dev]))
438             
439
440     print ""
441     memory= info.get_total_phsyical_mem()
442     if not memory:
443         print "unable to read /proc/meminfo for memory"
444     else:
445         print "total physical memory: %d kb" % memory
446         
447
448     print ""
449
450     import sys
451     kernel_version = None
452     if len(sys.argv) > 2:
453         kernel_version = sys.argv[1]
454         
455     modules= info.get_system_modules("/",kernel_version)
456     if not modules:
457         print "unable to list system modules"
458     else:
459         for type in modules:
460             if type == info.MODULE_CLASS_SCSI:
461                 print( "all scsi modules:" )
462                 for a_mod in modules[type]:
463                     print a_mod
464             elif type == info.MODULE_CLASS_NETWORK:
465                 print( "all network modules:" )
466                 for a_mod in modules[type]:
467                     print a_mod
468