1
# (c) 2007 Canonical Ltd.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License along
14
# with this program; if not, write to the Free Software Foundation, Inc.,
15
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
'''Define some common abstract basic handler types.
19
These provide the common functionality for concrete handlers of different
20
classes, like handlers for a kernel module, a driver package, a handler group,
23
Custom concrete handlers need to fulfill the following requirements:
24
- __init__(self, ui) must take exactly one argument (a reference to an
25
AbstractUI instance). All othe properties must be detected by the
26
constructor or changed with methods. These classes are instantiated
27
automatically, which is not possible with constructors which need more
30
- All handler types in this module have some abstract functions which need to
31
be implemented (see the documentation of the particular classes).
34
import subprocess, os.path, sys, logging
35
from gettext import gettext as _
38
from oslib import OSLib
40
#--------------------------------------------------------------------#
43
'''Abstract basic handler.'''
45
def __init__(self, ui, name, description=None, rationale=None,
47
'''Create a handler with given (human readable) name.
49
Every handler should have a human readable name. A custom rationale and
50
a multi-line description can be given, too. Every handler gets a
51
reference to the currently used user interface (subclass of AbstractUI)
52
so that it can make callbacks and query the user.
56
self._description = description
58
self._rationale = rationale
59
self.extra_options = extra_options
62
'''Return one-line name of the handler (for human consumption).'''
66
def description(self):
67
'''Return multi-line description of the handler.'''
69
return self._description
72
'''Return rationale as to why this driver might be enabled.
74
Might return None if no rationale is available.
77
return self._rationale
83
return _('This driver is necessary to support the '
84
'hardware, there is no free/open alternative.\n\n'
85
'If this driver is not enabled, the hardware will not '
89
'''Return if the module has been enabled/disabled at least once.'''
94
# The following methods can be specialized in subclasses
98
'''Check whether we can actually modify settings of this handler.
100
This might not be the case if e. g. the user manually modified a
101
configuration file. Return an explanatory text if settings can not be
102
changed, or None if changing is ok.
107
return '%s([%s, %s] %s)' % (
108
str(self.__class__).split('.')[-1],
109
self.free() and 'free' or 'nonfree',
110
self.enabled() and 'enabled' or 'disabled',
114
# The following methods must be implemented in subclasses
118
'''Return if the handler represents a free software driver.'''
120
raise NotImplementedError, 'subclasses need to implement this'
123
'''Return if the handler is enabled.
125
'Enabled' means that the user agreed to use this driver if it is
128
raise NotImplementedError, 'subclasses need to implement this'
131
'''Return if the handler is currently in use.'''
133
raise NotImplementedError, 'subclasses need to implement this'
136
'''Return if the conditions to use this handler on the system are met.
138
This usually means that the hardware for this driver is available, but
139
there might be hardware independent drivers, too.
141
If this returns True or False, the answer is definitive and no further
142
detection, db querying, etc is performed. If this returns None, then
143
the handler cannot decide availability on its own; in that case it is
144
merely available in the handler pool, and an external driver database
145
(detection.DriverDB) is queried.
147
raise NotImplementedError, 'subclasses need to implement this'
149
def ui_category(self):
150
'''Return handler category (translated, human readable, short).'''
152
raise NotImplementedError, 'subclasses need to implement this'
155
'''Allow the OS to use it if the hardware is available.
157
If possible, the handler should be loaded, too.
162
'''Prevent the OS from using it even if the hardware is available.
164
If possible, the handler should be unloaded, too.
168
#--------------------------------------------------------------------#
170
class HandlerGroup(Handler):
171
'''Perform operations on a group of handlers.
173
A group should be provided if it makes little sense to present several very
174
similar handlers in the UI. For example, the three VMWare or the dozens of
175
commercial OSS drivers should be grouped.
177
def __init__(self, ui, name, description=None, rationale=None, **extra_options):
178
Handler.__init__(self, ui, name, description, rationale, **extra_options)
179
self.subhandlers = []
181
def add(self, handler):
182
'''Add a subhandler.'''
184
self.subhandlers.append(handler)
187
'''Return if all subhandlers represent free software drivers.'''
189
for h in self.subhandlers:
196
'''Return if all subhandlers are enabled.'''
198
for h in self.subhandlers:
205
'''Return if any subhandler is used.'''
207
for h in self.subhandlers:
214
'''Return if the hardware for any subhandler is available.
216
If all subhandlers return False, this returns False. If any subhandler
217
returns True, this returns True. Otherwise this returns None.
221
for h in self.subhandlers:
235
def ui_category(self):
236
'''Return handler category (translated, human readable, short).'''
238
return self.subhandlers[0].ui_category()
241
'''Enable all subhandlers.'''
243
for h in self.subhandlers:
247
'''Disable all subhandlers.'''
249
for h in self.subhandlers:
253
'''Return if at least one subhandler has been enabled/disabled at
256
for h in self.subhandlers:
262
def can_change(self):
263
'''Check whether we can actually modify settings of this handler.'''
265
assert self.subhandlers
267
for h in self.subhandlers:
274
#--------------------------------------------------------------------#
276
class KernelModuleHandler(Handler):
277
'''Handler for a kernel module.
279
This class can be used as a standard handler for kernel modules (and in
280
fact detection.get_handlers() uses this as a default handler if there is no
281
custom one). Subclasses have to implement __init__() at least.
283
_loaded_modules = None
285
def __init__(self, ui, module, name=None, description=None, rationale=None,
287
'''Create handler for a kernel module.
289
If not given explicitly, the name is read from modinfo's 'description'
293
self._modinfo = detection.get_modinfo(self.module)
294
assert self._modinfo, 'kernel module %s exists' % self.module
296
name = '\n'.join(self._modinfo.get('description', [self.module]))
297
Handler.__init__(self, ui, name, description, rationale, **extra_options)
300
return '%s([%s, %s, %s] %s)' % (
301
str(self.__class__).split('.')[-1],
303
self.free() and 'free' or 'nonfree',
304
self.enabled() and 'enabled' or 'disabled',
308
'''Return if the handler represents a free software driver.'''
310
# this function needs to be kept in sync with the kernel function
311
# is_license_gpl_compatible()
313
for l in self._modinfo.get('license', ['unknown']):
314
if l in ('GPL', 'GPL v2', 'GPL and additional rights',
315
'Dual BSD/GPL', 'Dual MIT/GPL', 'Dual MPL/GPL'):
321
'''Return if the handler is enabled.
323
'Enabled' means that the user agreed to use this driver if it is
326
return not OSLib.inst.module_blacklisted(self.module)
329
'''Return if the handler is currently in use.'''
331
return self.module_loaded(self.module)
334
'''Return if the conditions to use this handler on the system are met
335
(e. g. hardware for this driver is available).
337
This defaults to None, because we usually want to delegate this to the
338
driver db. Subclasses are welcome to override this, of course.
342
def ui_category(self):
343
'''Return handler category (translated, human readable, short).'''
345
return self.ui._('Device driver')
348
'''Allow the OS to use it if the hardware is available.
350
This removes the module from the modprobe blacklist.
353
OSLib.inst.blacklist_module(self.module, False)
354
subprocess.call([OSLib.inst.modprobe_path, self.module])
355
self.read_loaded_modules()
356
self.rebind(self.module) # TODO: is this always wanted?
359
'''Prevent the OS from using it even if the hardware is available.
361
This adds the module to the modprobe blacklist.
363
Handler.disable(self)
364
OSLib.inst.blacklist_module(self.module, True)
367
def rebind(klass, module):
368
'''Re-bind all devices using the module.
370
This is necessary for example to reload firmware.
372
drivers_dir = os.path.join(OSLib.inst.sys_dir, 'module', module, 'drivers')
373
if not os.path.isdir(drivers_dir):
374
logging.warning('%s does not exist, cannot rebind %s driver' % (
375
drivers_dir, module))
378
for driver in os.listdir(drivers_dir):
379
driver_path = os.path.join(drivers_dir, driver)
380
for device in os.listdir(driver_path):
381
# only consider subdirs which are not called 'module'
382
if device == 'module' or not os.path.isdir(
383
os.path.join(driver_path, device)):
386
logging.debug('unbind/rebind on driver %s: device %s', driver_path, device)
387
open(os.path.join(driver_path, 'unbind'), 'w').write(device)
388
open(os.path.join(driver_path, 'bind'), 'w').write(device)
390
logging.warning('unbind/rebind for device %s on driver %s failed',
391
device, driver_path, exc_info=True)
394
def read_loaded_modules(klass):
395
'''Get the list of loaded kernel modules.'''
397
klass._loaded_modules = []
399
proc_modules = open(OSLib.inst.proc_modules)
401
for line in proc_modules:
403
line = line[:line.index(' ')]
407
klass._loaded_modules.append(line.strip())
412
def module_loaded(klass, module):
413
'''Return if the handler is currently in use.'''
415
if klass._loaded_modules == None:
416
klass.read_loaded_modules()
418
return module in klass._loaded_modules
420
#--------------------------------------------------------------------#
422
class FirmwareHandler(KernelModuleHandler):
423
'''Handler for an already available kernel module needing firmware.
425
Subclasses need to extend enable() and implement disable() to do something
426
with the downloaded file (unpack it, put into the right directory, etc.).
427
This class' enable() function will deal with downloading it and the UI
428
progress reporting of the download.
430
def __init__(self, ui, module, testfile, name=None, description=None,
431
rationale=None, **extra_options):
432
'''Create handler for a piece of firmware for a kernel module.
434
The required extra option 'url' specifies where the firmware can be
435
downloaded from. The optional 'sha1sum' extra option provides a
436
checksum of the downloaded file. The file will not be installed if it
439
enabled() will return True iff the path in testfile exists.
441
By default this handler assumes that the firmware is not free (since
442
otherwise the distribution could ship it together with the driver). You
443
can provide the extra option 'free' (any nonempty value) for free
446
If not given explicitly, the name is read from modinfo's 'description'
449
self.url = extra_options['url']
450
self.testfile = testfile
452
if 'free' in extra_options:
453
rationale = ui._('The driver itself is already installed, but '
454
'it requires a piece of firmware which is not shipped with '
455
'the operating system.')
457
rationale = ui._('While this driver itself is free software, '
458
'it relies on proprietary firmware which cannot be legally '
459
'shipped with the operating system.')
460
rationale += ' ' + ui._('Your hardware will not work without the firmware.')
462
KernelModuleHandler.__init__(self, ui, module, name, description,
463
rationale, **extra_options)
466
'''Return if the handler represents a free software driver.'''
468
if 'free' in self.extra_options:
470
return KernelModuleHandler.free(self)
473
'''Return if the handler is enabled.
475
'Enabled' means that the user agreed to use this driver if it is
478
return os.path.exists(self.testfile) and KernelModuleHandler.enabled(self)
481
'''Return if the handler is currently in use.'''
483
return self.enabled() and KernelModuleHandler.used(self)
485
def ui_category(self):
486
'''Return handler category (translated, human readable, short).'''
488
return self.ui._('Firmware')
491
'''Allow the OS to use it if the hardware is available.
493
This downloads the url and puts it into self.firmware_file. Subclasses
494
need to provide an actual implementation what to do with the file.
496
self.firmware_file = self.ui.download_url(self.url)[0]
497
if not self.firmware_file:
500
# TODO: sha1sum check
502
KernelModuleHandler.enable(self)
505
'''Prevent the OS from using it even if the hardware is available.
507
Implementation in subclasses need to remove the firmware files and call
508
KernelModuleHandler.disable().
510
raise NotImplementedError, 'subclasses need to implement this'
512
#--------------------------------------------------------------------#
514
class DriverPackageHandler(Handler):
515
'''Handler for a driver package, not tied to a particular kernel module.
517
Subclasses need to implement __init__(), available() and used() at least.
519
def __init__(self, ui, package, name=None, description=None,
520
rationale=None, **extra_options):
521
'''Create handler for of driver package.
523
If not given explicitly, the name and description are taken from the
524
distribution package.
526
self.package = package
527
if not name or not description:
528
(distro_name, distro_desc) = OSLib.inst.package_description(package)
532
description = distro_desc
534
Handler.__init__(self, ui, name, description, rationale, **extra_options)
537
'''Return if the handler represents a free software driver.'''
539
return OSLib.inst.is_package_free(self.package)
542
'''Return if the handler is enabled.
544
'Enabled' means that the user agreed to use this driver if it is
547
return OSLib.inst.package_installed(self.package)
550
'''Return if the conditions to use this handler on the system are met.
552
This usually means that the hardware for this driver is available, but
553
there might be hardware independent drivers, too.
555
This returns False if the package is not available, otherwise None.
556
Subclasses are welcome to add a positive detection, but should return
557
False if this method returns False, unless they require a separate
561
OSLib.inst.package_description(self.package)
566
def ui_category(self):
567
'''Return handler category (translated, human readable, short).'''
569
return self.ui._('Device driver')
572
'''Allow the OS to use it if the hardware is available.
574
If possible, the handler should be loaded, too.
576
self.ui.install_package(self.package)
580
'''Prevent the OS from using it even if the hardware is available.
582
If possible, the handler should be unloaded, too.
584
self.ui.remove_package(self.package)
585
Handler.disable(self)
587
#--------------------------------------------------------------------#
589
class ModulePackageHandler(KernelModuleHandler, DriverPackageHandler):
590
'''Kernel module which needs an additional driver package.
592
Subclasses need to implement __init__(), available() and used() at least.
594
def __init__(self, ui, module, package, name=None, description=None,
595
rationale=None, **extra_options):
596
'''Handler for a kernel module which needs an additional driver package.
598
If not given explicitly, the name is read from modinfo's 'description'
601
DriverPackageHandler.__init__(self, ui, package, name, description,
602
rationale, **extra_options)
603
KernelModuleHandler.__init__(self, ui, module, name, description,
604
rationale, **extra_options)
606
def can_change(self):
607
'''Check whether we can actually modify settings of this handler.
609
This might not be the case if e. g. the user manually modified a
610
configuration file. Return an explanatory text if settings can not be
611
changed, or None if changing is ok.
613
return DriverPackageHandler.can_change(self) or \
614
KernelModuleHandler.can_change(self)
617
'''Return if the handler represents a free software driver.'''
619
return DriverPackageHandler.free(self) and \
620
KernelModuleHandler.free(self)
623
'''Return if the conditions to use this handler on the system are met.
625
This usually means that the hardware for this driver is available, but
626
there might be hardware independent drivers, too.
628
This returns False if the package is not available, otherwise None.
629
Subclasses are welcome to add a positive detection, but should return
630
False if this method returns False.
632
# KernelModuleHandler does not have an interesting implementation, so
633
# only query the package one by default
634
return DriverPackageHandler.available(self)
637
'''Return if the handler is enabled.
639
'Enabled' means that the user agreed to use this driver if it is
642
return DriverPackageHandler.enabled(self) and \
643
KernelModuleHandler.enabled(self)
646
'''Allow the OS to use it if the hardware is available.
648
If possible, the handler should be loaded, too.
650
DriverPackageHandler.enable(self)
651
KernelModuleHandler.enable(self)
654
'''Prevent the OS from using it even if the hardware is available.
656
If possible, the handler should be unloaded, too.
658
DriverPackageHandler.disable(self)
659
KernelModuleHandler.disable(self)