check in all bootmanager sources
[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
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 ):
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)/modules.pcimap
299         <install_root>/lib/modules/(first entry)/modules.dep
300
301         Note, that this assumes there is only one kernel
302         that is installed. If there are more than one, then
303         only the first one in a directory listing 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
311         # get the kernel version we are assuming
312         try:
313             kernel_version= os.listdir( "%s/lib/modules/" % install_root )
314         except OSError, e:
315             return
316
317         if len(kernel_version) == 0:
318             return
319
320         if len(kernel_version) > 1:
321             print( "WARNING: We may be returning modules for the wrong kernel." )
322
323         kernel_version= kernel_version[0]
324         print( "Using kernel version %s" % kernel_version )
325
326         # test to make sure the three files we need are present
327         pcitable_path = "%s/usr/share/hwdata/pcitable" % install_root
328         modules_pcimap_path = "%s/lib/modules/%s/modules.pcimap" % \
329                               (install_root,kernel_version)
330         modules_dep_path = "%s/lib/modules/%s/modules.dep" % \
331                            (install_root,kernel_version)
332
333         for path in (pcitable_path,modules_pcimap_path,modules_dep_path):
334             if not os.access(path,os.R_OK):
335                 print( "Unable to read %s" % path )
336                 return
337
338         # now, with those three files, merge them all into one easy to
339         # use lookup table
340         all_modules= merge_hw_tables().merge_files( modules_dep_path,
341                                                     modules_pcimap_path,
342                                                     pcitable_path )
343
344         if all_modules is None:
345             print( "Unable to merge pci id tables." )
346             return
347
348
349         # this is the actual data structure we return
350         system_mods= {}
351
352         # these are the lists that will be in system_mods
353         network_mods= []
354         scsi_mods= []
355
356
357         # get all the system devices from lspci
358         lspci_prog= popen2.Popen3( self.LSPCI_CMD, 1 )
359         if lspci_prog is None:
360             print( "Unable to run %s with popen2.Popen3" % self.LSPCI_CMD )
361             return
362         
363         returncode= lspci_prog.wait()
364         if returncode != 0:
365             print( "Running %s failed" % self.LSPCI_CMD )
366             return
367         else:
368             print( "Successfully ran %s" % self.LSPCI_CMD )
369             
370         for line in lspci_prog.fromchild:
371             if string.strip(line) == "":
372                 continue
373     
374             parts= string.split(line)
375
376             try:
377                 classid= self.remove_quotes(parts[2])
378                 vendorid= self.remove_quotes(parts[3])
379                 deviceid= self.remove_quotes(parts[4])
380             except IndexError:
381                 print( "Skipping invalid line:", string.strip(line) )
382                 continue
383
384             if classid not in (self.PCI_CLASS_NETWORK,
385                                self.PCI_CLASS_RAID,
386                                self.PCI_CLASS_RAID2):                    
387                 continue
388             
389             full_deviceid= "%s:%s" % (vendorid,deviceid)
390
391             for module in all_modules.keys():
392                 if full_deviceid in all_modules[module]:
393                     if classid == self.PCI_CLASS_NETWORK:
394                         network_mods.append(module)
395                     elif classid in (self.PCI_CLASS_RAID,
396                                      self.PCI_CLASS_RAID2):
397                         scsi_mods.append(module)
398                         
399         system_mods[self.MODULE_CLASS_SCSI]= scsi_mods
400         system_mods[self.MODULE_CLASS_NETWORK]= network_mods
401         
402         return system_mods
403
404
405     def remove_quotes( self, str ):
406         """
407         remove double quotes off of a string if they exist
408         """
409         if str == "" or str == None:
410             return str
411         if str[:1] == '"':
412             str= str[1:]
413         if str[len(str)-1] == '"':
414             str= str[:len(str)-1]
415         return str
416
417
418
419 if __name__ == "__main__":
420     info= systeminfo()
421     
422     devices= info.get_block_device_list()
423     print "block devices detected:"
424     if not devices:
425         print "no devices found!"
426     else:
427         for dev in devices.keys():
428             print "%s %s" % (dev, repr(devices[dev]))
429             
430
431     print ""
432     memory= info.get_total_phsyical_mem()
433     if not memory:
434         print "unable to read /proc/meminfo for memory"
435     else:
436         print "total physical memory: %d kb" % memory
437         
438
439     print ""
440     modules= info.get_system_modules("/")
441     if not modules:
442         print "unable to list system modules"
443     else:
444         for type in modules:
445             if type == info.MODULE_CLASS_SCSI:
446                 print( "all scsi modules:" )
447                 for a_mod in modules[type]:
448                     print a_mod
449             elif type == info.MODULE_CLASS_NETWORK:
450                 print( "all network modules:" )
451                 for a_mod in modules[type]:
452                     print a_mod
453