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