- get_system_modules: allow kernel_version to be passed in
[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 from merge_hw_tables import merge_hw_tables
68
69
70 class systeminfo:
71     """
72     a utility class for finding and returning information about
73     block devices, memory, and other hardware on the system
74     """
75
76     PROC_MEMINFO_PATH= "/proc/meminfo"
77
78
79     PROC_PARTITIONS_PATH= "/proc/partitions"
80
81     # set when the sfdisk -l <dev> trick has been done to make
82     # all devices show up
83     DEVICES_SCANNED_FLAG= "/tmp/devices_scanned"
84         
85     # a /proc/partitions block is 1024 bytes
86     # a GB to a HDD manufacturer is 10^9 bytes
87     BLOCKS_PER_GB = pow(10, 9) / 1024.0;
88
89
90     # -n is numeric ids (no lookup), -m is machine readable
91     LSPCI_CMD= "/sbin/lspci -nm"
92     
93     MODULE_CLASS_NETWORK= "network"
94     MODULE_CLASS_SCSI= "scsi"
95
96     PCI_CLASS_NETWORK= "0200"
97     PCI_CLASS_RAID= "0104"
98     PCI_CLASS_RAID2= "0100"
99     PCI_CLASS_IDE= "0101"
100
101     def get_total_phsyical_mem(self):
102         """
103         return the total physical memory of the machine, in kilobytes.
104
105         Return None if /proc/meminfo not readable.
106         """
107         
108         try:
109             meminfo_file= file(self.PROC_MEMINFO_PATH,"r")
110         except IOError, e:
111             return
112
113         total_memory= None
114         
115         for line in meminfo_file:
116
117             try:
118                 (fieldname,value)= string.split(line,":")
119             except ValueError, e:
120                 # this will happen for lines that don't have two values
121                 # (like the first line on 2.4 kernels)
122                 continue
123
124             fieldname= string.strip(fieldname)
125             value= string.strip(value)
126             
127             if fieldname == "MemTotal":
128                 try:
129                     (total_memory,units)= string.split(value)
130                 except ValueError, e:
131                     return
132                 
133                 if total_memory == "" or total_memory == None or \
134                        units == "" or units == None:
135                     return
136
137                 if string.lower(units) != "kb":
138                     return
139
140                 try:
141                     total_memory= int(total_memory)
142                 except ValueError, e:
143                     return
144
145                 break
146
147         meminfo_file.close()
148
149         return total_memory
150
151
152
153     def get_block_device_list(self):
154         """
155         get a list of block devices from this system.
156         return an associative array, where the device name
157         (full /dev/device path) is the key, and the value
158         is a tuple of (major,minor,numblocks,gb_size,readonly)
159         """
160         
161         # make sure we can access to the files/directories in /proc
162         if not os.access(self.PROC_PARTITIONS_PATH, os.F_OK):
163             return None
164
165
166         # only do this once every system boot
167         if not os.access(self.DEVICES_SCANNED_FLAG, os.R_OK):
168             
169             # this is ugly. under devfs, device
170             # entries in /dev/scsi/.. and /dev/ide/...
171             # don't show up until you attempt to read
172             # from the associated device at /dev (/dev/sda).
173             # so, lets run sfdisk -l (list partitions) against
174             # most possible block devices, that way they show
175             # up when it comes time to do the install.
176             for dev_prefix in ('sd','hd'):
177                 block_dev_num= 0
178                 while block_dev_num < 10:
179                     if block_dev_num < 26:
180                         devicename= "/dev/%s%c" % \
181                                     (dev_prefix, chr(ord('a')+block_dev_num))
182                     else:
183                         devicename= "/dev/%s%c%c" % \
184                                     ( dev_prefix,
185                                       chr(ord('a')+((block_dev_num/26)-1)),
186                                       chr(ord('a')+(block_dev_num%26)) )
187
188                     os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename )
189                     block_dev_num = block_dev_num + 1
190
191             additional_scan_devices= ("/dev/cciss/c0d0p", "/dev/cciss/c0d1p",
192                                       "/dev/cciss/c0d2p", "/dev/cciss/c0d3p",
193                                       "/dev/cciss/c0d4p", "/dev/cciss/c0d5p",
194                                       "/dev/cciss/c0d6p", "/dev/cciss/c0d7p",
195                                       "/dev/cciss/c1d0p", "/dev/cciss/c1d1p",
196                                       "/dev/cciss/c1d2p", "/dev/cciss/c1d3p",
197                                       "/dev/cciss/c1d4p", "/dev/cciss/c1d5p",
198                                       "/dev/cciss/c1d6p", "/dev/cciss/c1d7p",)
199             
200             for devicename in additional_scan_devices:
201                 os.system( "sfdisk -l %s > /dev/null 2>&1" % devicename )
202
203             os.system( "touch %s" % self.DEVICES_SCANNED_FLAG )
204             
205
206         devicelist= {}
207
208         partitions_file= file(self.PROC_PARTITIONS_PATH,"r")
209         line_count= 0
210         
211         for line in partitions_file:
212             line_count= line_count + 1
213
214             # skip the first two lines always
215             if line_count < 2:
216                 continue
217
218             parts= string.split(line)
219
220             if len(parts) < 4:
221                 continue
222             
223             device= parts[3]
224
225             # if the last char in device is a number, its
226             # a partition, and we ignore it
227             
228             if device[len(device)-1].isdigit():
229                 continue
230
231             dev_name= "/dev/%s" % device
232             
233             try:
234                 major= int(parts[0])
235                 minor= int(parts[1])
236                 blocks= int(parts[2])
237             except ValueError, err:
238                 continue
239
240             gb_size= blocks/self.BLOCKS_PER_GB
241             
242             # parse the output of hdparm <disk> to get the readonly flag;
243             # if this fails, assume it isn't read only
244             readonly= 0
245             
246             hdparm_cmd = popen2.Popen3("hdparm %s 2> /dev/null" % dev_name)
247             status= hdparm_cmd.wait()
248
249             if os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0:
250                 try:
251                     hdparm_output= hdparm_cmd.fromchild.read()
252                     hdparm_cmd= None
253                     
254                     # parse the output of hdparm, the lines we are interested
255                     # in look
256                     # like:
257                     #
258                     #  readonly     =  0 (off)
259                     #
260                     
261                     for line in string.split(hdparm_output,"\n"):
262                         
263                         line= string.strip(line)
264                         if line == "":
265                             continue
266                         
267                         line_parts= string.split(line,"=")
268                         if len(line_parts) < 2:
269                             continue
270
271                         name= string.strip(line_parts[0])
272
273                         if name == "readonly":
274                             value= string.strip(line_parts[1])
275                             if len(value) == 0:
276                                 break
277
278                             if value[0] == "1":
279                                 readonly= 1
280                                 break
281
282                 except IOError:
283                     pass
284
285             devicelist[dev_name]= (major,minor,blocks,gb_size,readonly)
286
287             
288         return devicelist
289
290
291
292     def get_system_modules( self, install_root, kernel_version= None ):
293         """
294         Return a list of kernel modules that this system requires.
295         This requires access to the installed system's root
296         directory, as the following files must exist and are used:
297         <install_root>/usr/share/hwdata/pcitable
298         <install_root>/lib/modules/(first entry if kernel_version unspecified)/modules.pcimap
299         <install_root>/lib/modules/(first entry if kernel version unspecified)/modules.dep
300
301         If there are more than one kernels installed, and the kernel
302         version is not specified, then only the first one in
303         /lib/modules is used.
304
305         Returns a dictionary, keys being the type of module:
306          - scsi       MODULE_CLASS_SCSI
307          - network    MODULE_CLASS_NETWORK
308         The value being the kernel module name to load.
309
310         Some sata devices show up under an IDE device class,
311         hence the reason for checking for ide devices as well.
312         If there actually is a match in the pci -> module lookup
313         table, and its an ide device, its most likely sata,
314         as ide modules are built in to the kernel.
315         """
316
317         # get the kernel version we are assuming
318         if kernel_version is None:
319             try:
320                 kernel_version= os.listdir( "%s/lib/modules/" % install_root )
321             except OSError, e:
322                 return
323
324             if len(kernel_version) == 0:
325                 return
326
327             if len(kernel_version) > 1:
328                 print( "WARNING: We may be returning modules for the wrong kernel." )
329
330             kernel_version= kernel_version[0]
331
332         print( "Using kernel version %s" % kernel_version )
333
334         # test to make sure the three files we need are present
335         pcitable_path = "%s/usr/share/hwdata/pcitable" % install_root
336         modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
337                               (install_root,kernel_version)
338         modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
339                            (install_root,kernel_version)
340
341         for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
342             if not os.access(path,os.R_OK):
343                 print( "Unable to read %s" % path )
344                 return
345
346         # now, with those three files, merge them all into one easy to
347         # use lookup table
348         all_modules= merge_hw_tables().merge_files( modules_dep_path,
349                                                     modules_pcimap_path,
350                                                     pcitable_path )
351
352         if all_modules is None:
353             print( "Unable to merge pci id tables." )
354             return
355
356
357         # this is the actual data structure we return
358         system_mods= {}
359
360         # these are the lists that will be in system_mods
361         network_mods= []
362         scsi_mods= []
363
364
365         # get all the system devices from lspci
366         lspci_prog= popen2.Popen3( self.LSPCI_CMD, 1 )
367         if lspci_prog is None:
368             print( "Unable to run %s with popen2.Popen3" % self.LSPCI_CMD )
369             return
370         
371         returncode= lspci_prog.wait()
372         if returncode != 0:
373             print( "Running %s failed" % self.LSPCI_CMD )
374             return
375         else:
376             print( "Successfully ran %s" % self.LSPCI_CMD )
377             
378         for line in lspci_prog.fromchild:
379             if string.strip(line) == "":
380                 continue
381     
382             parts= string.split(line)
383
384             try:
385                 classid= self.remove_quotes(parts[2])
386                 vendorid= self.remove_quotes(parts[3])
387                 deviceid= self.remove_quotes(parts[4])
388             except IndexError:
389                 print( "Skipping invalid line:", string.strip(line) )
390                 continue
391
392             if classid not in (self.PCI_CLASS_NETWORK,
393                                self.PCI_CLASS_RAID,
394                                self.PCI_CLASS_RAID2,
395                                self.PCI_CLASS_IDE):
396                 continue
397             
398             full_deviceid= "%s:%s" % (vendorid,deviceid)
399
400             for module in all_modules.keys():
401                 if full_deviceid in all_modules[module]:
402                     if classid == self.PCI_CLASS_NETWORK:
403                         network_mods.append(module)
404                     elif classid in (self.PCI_CLASS_RAID,
405                                      self.PCI_CLASS_RAID2,
406                                      self.PCI_CLASS_IDE):
407                         scsi_mods.append(module)
408                         
409         system_mods[self.MODULE_CLASS_SCSI]= scsi_mods
410         system_mods[self.MODULE_CLASS_NETWORK]= network_mods
411         
412         return system_mods
413
414
415     def remove_quotes( self, str ):
416         """
417         remove double quotes off of a string if they exist
418         """
419         if str == "" or str == None:
420             return str
421         if str[:1] == '"':
422             str= str[1:]
423         if str[len(str)-1] == '"':
424             str= str[:len(str)-1]
425         return str
426
427
428
429 if __name__ == "__main__":
430     info= systeminfo()
431     
432     devices= info.get_block_device_list()
433     print "block devices detected:"
434     if not devices:
435         print "no devices found!"
436     else:
437         for dev in devices.keys():
438             print "%s %s" % (dev, repr(devices[dev]))
439             
440
441     print ""
442     memory= info.get_total_phsyical_mem()
443     if not memory:
444         print "unable to read /proc/meminfo for memory"
445     else:
446         print "total physical memory: %d kb" % memory
447         
448
449     print ""
450     modules= info.get_system_modules("/")
451     if not modules:
452         print "unable to list system modules"
453     else:
454         for type in modules:
455             if type == info.MODULE_CLASS_SCSI:
456                 print( "all scsi modules:" )
457                 for a_mod in modules[type]:
458                     print a_mod
459             elif type == info.MODULE_CLASS_NETWORK:
460                 print( "all network modules:" )
461                 for a_mod in modules[type]:
462                     print a_mod
463