~ubuntu-branches/debian/squeeze/aptdaemon/squeeze

« back to all changes in this revision

Viewing changes to .pc/03_auth_me_less.patch/aptdaemon/core.py

  • Committer: Bazaar Package Importer
  • Author(s): Julian Andres Klode
  • Date: 2010-06-06 14:30:27 UTC
  • mfrom: (1.1.18 upstream) (18.1.12 maverick)
  • Revision ID: james.westby@ubuntu.com-20100606143027-tyttr56a1y7lk2h6
Tags: 0.31+bzr413-1
* Merge with Ubuntu, remaining differences:
  - debian/copyright uses DEP-5 format.
  - debian/source/format: Set to "3.0 (quilt)".
  - debian/rules: Use debhelper 7 instead of quilt
  - debian/watch: Added watch file.
  - debian/control: Reindent, Vcs, Maintainer changes.
* debian/patches/03_auth_me_less.patch: Change patch level to 1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
"""
 
4
Core components of aptdaemon.
 
5
 
 
6
This module provides the following core classes of the aptdaemon:
 
7
AptDaemon - complete daemon for managing software via DBus interface
 
8
Transaction - represents a software management operation
 
9
TransactionQueue - queue for aptdaemon transactions
 
10
 
 
11
The main function allows to run the daemon as a command.
 
12
"""
 
13
# Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
 
14
#
 
15
# This program is free software; you can redistribute it and/or modify
 
16
# it under the terms of the GNU General Public License as published by
 
17
# the Free Software Foundation; either version 2 of the License, or
 
18
# any later version.
 
19
#
 
20
# This program is distributed in the hope that it will be useful,
 
21
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
22
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
23
# GNU General Public License for more details.
 
24
#
 
25
# You should have received a copy of the GNU General Public License along
 
26
# with this program; if not, write to the Free Software Foundation, Inc.,
 
27
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
28
 
 
29
__author__  = "Sebastian Heinlein <devel@glatzor.de>"
 
30
 
 
31
__all__ = ("Transaction", "TransactionQueue", "AptDaemon",
 
32
           "APTDAEMON_TRANSACTION_DBUS_INTERFACE", "APTDAEMON_DBUS_INTERFACE"
 
33
           "APTDAEMON_DBUS_PATH", "APTDAEMON_DBUS_SERVICE",
 
34
           "APTDAEMON_IDLE_CHECK_INTERVAL", "APTDAEMON_IDLE_TIMEOUT",
 
35
           "TRANSACTION_IDLE_TIMEOUT", "TRANSACTION_DEL_TIMEOUT")
 
36
 
 
37
import collections
 
38
from functools import wraps
 
39
import gettext
 
40
from gettext import gettext as _
 
41
from locale import _parse_localename as parse_localename
 
42
import logging
 
43
import logging.handlers
 
44
from optparse import OptionParser
 
45
import os
 
46
import Queue
 
47
import signal
 
48
import sys
 
49
import tempfile
 
50
import time
 
51
import threading
 
52
import uuid
 
53
 
 
54
import gobject
 
55
import dbus.exceptions
 
56
import dbus.service
 
57
import dbus.mainloop.glib
 
58
import dbus.glib
 
59
from softwareproperties.AptAuth import AptAuth
 
60
from aptsources.sourceslist import SourcesList
 
61
import apt_pkg
 
62
import aptsources
 
63
import aptsources.distro
 
64
 
 
65
import errors
 
66
from enums import *
 
67
from defer import dbus_deferred_method, Deferred, inline_callbacks, return_value
 
68
import policykit1
 
69
from worker import AptWorker, DummyWorker
 
70
from loop import mainloop
 
71
 
 
72
# Setup i18n
 
73
gettext.textdomain("aptdaemon")
 
74
 
 
75
APTDAEMON_DBUS_INTERFACE = 'org.debian.apt'
 
76
APTDAEMON_DBUS_PATH = '/org/debian/apt'
 
77
APTDAEMON_DBUS_SERVICE = 'org.debian.apt'
 
78
 
 
79
APTDAEMON_TRANSACTION_DBUS_INTERFACE = 'org.debian.apt.transaction'
 
80
 
 
81
APTDAEMON_IDLE_CHECK_INTERVAL = 60
 
82
APTDAEMON_IDLE_TIMEOUT = 5 * 60
 
83
 
 
84
# Maximum allowed time between the creation of a transaction and its queuing
 
85
TRANSACTION_IDLE_TIMEOUT = 300
 
86
# Keep the transaction for the given time alive on the bus after it has
 
87
# finished
 
88
TRANSACTION_DEL_TIMEOUT = 5
 
89
 
 
90
# Setup the DBus main loop
 
91
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
92
 
 
93
# Required for daemon mode
 
94
os.putenv("PATH",
 
95
          "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
 
96
 
 
97
# Setup logging to syslog and the console
 
98
log = logging.getLogger("AptDaemon")
 
99
try:
 
100
    _syslog_handler = logging.handlers.SysLogHandler(address="/dev/log",
 
101
                             facility=logging.handlers.SysLogHandler.LOG_DAEMON)
 
102
    _syslog_handler.setLevel(logging.INFO)
 
103
    _syslog_formatter = logging.Formatter("%(name)s: %(levelname)s: "
 
104
                                          "%(message)s")
 
105
    _syslog_handler.setFormatter(_syslog_formatter)
 
106
except:
 
107
    pass
 
108
else:
 
109
    log.addHandler(_syslog_handler)
 
110
_console_handler = logging.StreamHandler()
 
111
_console_formatter = logging.Formatter("%(asctime)s %(name)s [%(levelname)s]: "
 
112
                                       "%(message)s",
 
113
                                       "%T")
 
114
_console_handler.setFormatter(_console_formatter)
 
115
log.addHandler(_console_handler)
 
116
#FIXME: Use LoggerAdapter (requires Python 2.6)
 
117
log_trans = logging.getLogger("AptDaemon.Trans")
 
118
 
 
119
 
 
120
class Transaction(dbus.service.Object):
 
121
 
 
122
    """Represents a transaction on the D-Bus.
 
123
 
 
124
    A transaction represents a single package management task, e.g. installation
 
125
    or removal of packages. This class allows to expose information and to
 
126
    controll the transaction via DBus using policykit for managing
 
127
    privileges.
 
128
    """
 
129
 
 
130
    def __init__(self, role, queue, uid, connect=True, bus=None,
 
131
                 packages=None, kwargs=None):
 
132
        """Initialize a new Transaction instance.
 
133
 
 
134
        Keyword arguments:
 
135
        queue -- TransactionQueue instance of the daemon
 
136
        uid -- the id of the user who created the transaction
 
137
        connect -- if the Transaction should connect to DBus (default is True)
 
138
        bus -- the DBus connection which should be used (defaults to system bus)
 
139
        """
 
140
        id = uuid.uuid4().get_hex()
 
141
        self.tid = "/org/debian/apt/transaction/%s" % id
 
142
        if connect == True:
 
143
            if bus is None:
 
144
                bus = dbus.SystemBus()
 
145
            bus_name = dbus.service.BusName(APTDAEMON_DBUS_SERVICE, bus)
 
146
            dbus_path = self.tid
 
147
        else:
 
148
            bus_name = None
 
149
            dbus_path = None
 
150
        dbus.service.Object.__init__(self, bus_name, dbus_path)
 
151
        if not packages:
 
152
           packages = [[], [], [], [], []]
 
153
        if not kwargs:
 
154
            kwargs = {}
 
155
        self.queue = queue
 
156
        self.uid = uid
 
157
        self.locale = ""
 
158
        self.allow_unauthenticated = False
 
159
        self.remove_obsoleted_depends = False
 
160
        self.http_proxy = ""
 
161
        self.terminal = ""
 
162
        self.debconf = ""
 
163
        self.kwargs = kwargs
 
164
        self._role = role
 
165
        self._progress = 0
 
166
        self._progress_details = (0, 0, 0, 0, 0, 0)
 
167
        self._progress_download = ("", "", "", 0, 0, "")
 
168
        self._exit = EXIT_UNFINISHED
 
169
        self._status = STATUS_SETTING_UP
 
170
        self._status_details = ""
 
171
        self._error = (-1, "")
 
172
        self._cancellable = True
 
173
        self._term_attached = False
 
174
        self._required_medium = ("", "")
 
175
        self._config_file_conflict = ("", "")
 
176
        self._config_file_conflict_resolution = ""
 
177
        self.cancelled = False
 
178
        self.paused = False
 
179
        self._meta_data = dbus.Dictionary(signature="ss")
 
180
        self._dpkg_status = None
 
181
        self._download = 0
 
182
        self._space = 0
 
183
        self._depends = [dbus.Array([], signature=dbus.Signature('s')) \
 
184
                         for i in range(7)]
 
185
        self._packages = [pkgs or dbus.Array([], signature=dbus.Signature('s'))\
 
186
                          for pkgs in packages]
 
187
        # Add a timeout which removes the transaction from the bus if it
 
188
        # hasn't been setup and run for the TRANSACTION_IDLE_TIMEOUT period
 
189
        self._idle_watch = gobject.timeout_add_seconds(
 
190
            TRANSACTION_IDLE_TIMEOUT, self._remove_from_connection_no_raise)
 
191
 
 
192
    def _remove_from_connection_no_raise(self):
 
193
        """Version of remove_from_connection that does not raise if the
 
194
        object isn't exported.
 
195
        """
 
196
        log_trans.debug("Removing transaction")
 
197
        try:
 
198
            self.remove_from_connection()
 
199
        except LookupError, error:
 
200
            log_trans.debug("remove_from_connection() raised LookupError: "
 
201
                            "'%s'" % error)
 
202
 
 
203
    def _set_meta_data(self, data):
 
204
        # Perform some checks
 
205
        if self.status != STATUS_SETTING_UP:
 
206
            raise errors.TransactionAlreadyRunning()
 
207
        if not isinstance(data, dbus.Dictionary):
 
208
            raise errors.InvalidMetaDataError("The data value has to be a "
 
209
                                              "dictionary: %s" % data)
 
210
        for key, value in data.iteritems():
 
211
            if key in self._meta_data:
 
212
                raise errors.InvalidMetaDataError("The key %s already "
 
213
                                                  "exists. It is not allowed "
 
214
                                                  "to overwrite existing "
 
215
                                                  "data." % key)
 
216
            if not len(key.split("_")) > 1:
 
217
                raise errors.InvalidMetaDataError("The key %s has to be of the "
 
218
                                                  "format IDENTIFIER-KEYNAME")
 
219
            if not isinstance(value, dbus.String):
 
220
                raise errors.InvalidMetaDataError("The value has to be a "
 
221
                                                  "string: %s" % value)
 
222
        # Merge new data into existing one:
 
223
        self._meta_data.update(data)
 
224
        self.PropertyChanged("MetaData", self._meta_data)
 
225
 
 
226
    def _get_meta_data(self):
 
227
        return self._meta_data
 
228
 
 
229
    meta_data = property(_get_meta_data, _set_meta_data,
 
230
                         doc="Allows client applications to store meta data "
 
231
                             "for the transaction in a dictionary.")
 
232
 
 
233
    def _set_role(self, enum):
 
234
        if self._role != ROLE_UNSET:
 
235
            raise errors.TransactionRoleAlreadySet()
 
236
        self.PropertyChanged("Role", enum)
 
237
        self._role = enum
 
238
 
 
239
    def _get_role(self):
 
240
        return self._role
 
241
 
 
242
    role = property(_get_role, _set_role, doc="Operation type of transaction.")
 
243
 
 
244
    def _set_progress_details(self, details):
 
245
        items_done, items_total, bytes_done, bytes_total, speed, eta = details
 
246
        self._progress_details = details
 
247
        self.PropertyChanged("ProgressDetails", details)
 
248
 
 
249
    def _get_progress_details(self):
 
250
        return self._progress_details
 
251
 
 
252
    progress_details = property(_get_progress_details, _set_progress_details,
 
253
                                doc = "Tuple containing detailed progress "
 
254
                                      "information: items done, total items, "
 
255
                                      "bytes done, total bytes, speed and "
 
256
                                      "remaining time")
 
257
 
 
258
    def _set_error(self, excep):
 
259
        self._error = excep
 
260
        self.PropertyChanged("Error", (excep.code, excep.details))
 
261
 
 
262
    def _get_error(self):
 
263
        return self._error
 
264
 
 
265
    error = property(_get_error, _set_error, doc="Raised exception.")
 
266
 
 
267
    def _set_exit(self, enum):
 
268
        self.status = STATUS_FINISHED
 
269
        self._exit = enum
 
270
        self.PropertyChanged("ExitState", enum)
 
271
        self.Finished(enum)
 
272
        # Remove the transaction from the Bus after it is complete. A short
 
273
        # timeout helps lazy clients
 
274
        gobject.timeout_add_seconds(TRANSACTION_DEL_TIMEOUT,
 
275
                                    self._remove_from_connection_no_raise)
 
276
 
 
277
    def _get_exit(self):
 
278
        return self._exit
 
279
 
 
280
    exit = property(_get_exit, _set_exit,
 
281
                    doc="The exit state of the transaction.")
 
282
 
 
283
    def _get_download(self):
 
284
        return self._download
 
285
 
 
286
    def _set_download(self, size):
 
287
        self.PropertyChanged("Download", size)
 
288
        self._download = size
 
289
 
 
290
    download = property(_get_download, _set_download,
 
291
                        doc="The download size of the transaction.")
 
292
 
 
293
    def _get_space(self):
 
294
        return self._space
 
295
 
 
296
    def _set_space(self, size):
 
297
        self.PropertyChanged("Space", size)
 
298
        self._space = size
 
299
 
 
300
    space = property(_get_space, _set_space,
 
301
                     doc="The required disk space of the transaction.")
 
302
 
 
303
    def _get_packages(self):
 
304
        return self._packages
 
305
 
 
306
    def _set_packages(self, packages):
 
307
        self._packages = [dbus.Array(pkgs, signature=dbus.Signature('s')) \
 
308
                          for pkgs in packages]
 
309
        self.PropertyChanged("Packages", self._packages)
 
310
 
 
311
    packages = property(_get_packages, _set_packages,
 
312
                        doc="Packages which will be explictly install, "
 
313
                            "upgraded, removed, purged or reinstalled.")
 
314
 
 
315
    def _get_depends(self):
 
316
        return self._depends
 
317
 
 
318
    def _set_depends(self, depends):
 
319
        self._depends = [dbus.Array(deps, signature=dbus.Signature('s')) \
 
320
                         for deps in depends]
 
321
        self.PropertyChanged("Dependencies", self._depends)
 
322
 
 
323
    depends = property(_get_depends, _set_depends,
 
324
                       doc="The additional dependencies: installs, removals, "
 
325
                           "upgrades and downgrades.")
 
326
 
 
327
    def _get_status(self):
 
328
        return self._status
 
329
 
 
330
    def _set_status(self, enum):
 
331
        self.PropertyChanged("Status", enum)
 
332
        self._status = enum
 
333
 
 
334
    status = property(_get_status, _set_status,
 
335
                      doc="The status of the transaction.")
 
336
 
 
337
    def _get_status_details(self):
 
338
        return self._status_details
 
339
 
 
340
    def _set_status_details(self, text):
 
341
        self._status_details = text
 
342
        self.PropertyChanged("StatusDetails", text)
 
343
 
 
344
    status_details = property(_get_status_details, _set_status_details,
 
345
                              doc="The status message from apt.")
 
346
 
 
347
    def _get_progress(self):
 
348
        return self._progress
 
349
 
 
350
    def _set_progress(self, percent):
 
351
        self._progress = percent
 
352
        self.PropertyChanged("Progress", percent)
 
353
 
 
354
    progress = property(_get_progress, _set_progress,
 
355
                      doc="The progress of the transaction in percent.")
 
356
 
 
357
    def _get_progress_download(self):
 
358
        return self._progress_download
 
359
 
 
360
    def _set_progress_download(self, progress_download):
 
361
        self._progress_download = progress_download
 
362
        self.PropertyChanged("ProgressDownload", progress_download)
 
363
 
 
364
    progress_download = property(_get_progress_download,
 
365
                                 _set_progress_download,
 
366
                                 doc="The last progress update of a currently"
 
367
                                     "running download. A tuple of URI, "
 
368
                                     "status, short description, full size, "
 
369
                                     "partially downloaded size and a status "
 
370
                                     "message.")
 
371
 
 
372
    def _get_cancellable(self):
 
373
        return self._cancellable
 
374
 
 
375
    def _set_cancellable(self, cancellable):
 
376
        self._cancellable = cancellable
 
377
        self.PropertyChanged("Cancellable", cancellable)
 
378
 
 
379
    cancellable = property(_get_cancellable, _set_cancellable,
 
380
                           doc="If it's currently allowed to cancel the "
 
381
                               "transaction.")
 
382
 
 
383
    def _get_term_attached(self):
 
384
        return self._term_attached
 
385
 
 
386
    def _set_term_attached(self, attached):
 
387
        self._term_attached = attached
 
388
        self.PropertyChanged("TerminalAttached", attached)
 
389
 
 
390
    term_attached = property(_get_term_attached, _set_term_attached,
 
391
                             doc="If the controlling terminal is currently "
 
392
                                 "attached to the dpkg call of the "
 
393
                                 "transaction.")
 
394
 
 
395
    def _get_required_medium(self):
 
396
        return self._required_medium
 
397
 
 
398
    def _set_required_medium(self, medium):
 
399
        label, drive = medium
 
400
        self._required_medium = medium
 
401
        self.PropertyChanged("RequiredMedium", medium)
 
402
        self.MediumRequired(label, drive)
 
403
 
 
404
    required_medium = property(_get_required_medium, _set_required_medium,
 
405
                               doc="Tuple containing the label and the drive "
 
406
                                   "of a required CD/DVD to install packages "
 
407
                                   "from.")
 
408
 
 
409
    def _get_config_file_conflict(self):
 
410
        return self._config_file_conflict
 
411
 
 
412
    def _set_config_file_conflict(self, prompt):
 
413
        if prompt is None:
 
414
            self._config_file_conflict = None
 
415
            return
 
416
        old, new = prompt
 
417
        self._config_file_conflict = prompt
 
418
        self.PropertyChanged("ConfigFileConflict", prompt)
 
419
        self.ConfigFileConflict(old, new)
 
420
 
 
421
    config_file_conflict = property(_get_config_file_conflict,
 
422
                                    _set_config_file_conflict,
 
423
                                    doc="Tuple containing the old and the new "
 
424
                                      "path of the configuration file")
 
425
 
 
426
    # Signals
 
427
 
 
428
    @dbus.service.signal(dbus_interface=APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
429
                         signature="sv")
 
430
    def PropertyChanged(self, property, value):
 
431
        """Set and emit if a property of the transaction changed.
 
432
 
 
433
        Keyword argument:
 
434
        property -- the name of the property
 
435
        value -- the new value of the property
 
436
        """
 
437
        log_trans.debug("Emitting PropertyChanged: %s, %s" % (property, value))
 
438
 
 
439
    @dbus.service.signal(dbus_interface=APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
440
                         signature="s")
 
441
    def Finished(self, enum):
 
442
        """Mark and emit the transaction as finished.
 
443
 
 
444
        Keyword argument:
 
445
        enum -- the exit state of the transaction, e.g. EXIT_FAILED
 
446
        """
 
447
        log_trans.debug("Emitting Finished: %s" % \
 
448
                        get_exit_string_from_enum(exit))
 
449
 
 
450
    @dbus.service.signal(dbus_interface=APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
451
                         signature="ss")
 
452
    def MediumRequired(self, medium, drive):
 
453
        """Set and emit the required medium change.
 
454
 
 
455
        This method/signal should be used to inform the user to
 
456
        insert the installation CD/DVD:
 
457
 
 
458
        Keyword arguments:
 
459
        medium -- the CD/DVD label
 
460
        drive -- mount point of the drive
 
461
        """
 
462
        log_trans.debug("Emitting MediumRequierd: %s, %s" % (medium, drive))
 
463
 
 
464
    @dbus.service.signal(dbus_interface=APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
465
                         signature="ss")
 
466
    def ConfigFileConflict(self, old, new):
 
467
        """Set and emit the ConfigFileConflict signal.
 
468
 
 
469
        This method/signal should be used to inform the user to
 
470
        answer a config file prompt.
 
471
 
 
472
        Keyword arguments:
 
473
        old -- current version of the configuration prompt
 
474
        new -- new version of the configuration prompt
 
475
        """
 
476
        log_trans.debug("Emitting ConfigFileConflict: %s, %s" % (old, new))
 
477
 
 
478
    # Methods
 
479
 
 
480
    def _set_locale(self, locale):
 
481
        """Set the language and encoding.
 
482
 
 
483
        Keyword arguments:
 
484
        locale -- specifies language, territory and encoding according
 
485
                  to RFC 1766,  e.g. "de_DE.UTF-8"
 
486
        """
 
487
 
 
488
        if self.status != STATUS_SETTING_UP:
 
489
            raise errors.TransactionAlreadyRunning()
 
490
        try:
 
491
            (lang, encoding) = parse_localename(locale)
 
492
        except ValueError:
 
493
            raise
 
494
        else:
 
495
            self.locale = "%s.%s" % (lang, encoding)
 
496
            self.PropertyChanged("locale", self.locale)
 
497
 
 
498
    def _set_http_proxy(self, url):
 
499
        """Set an http network proxy.
 
500
 
 
501
        Keyword arguments:
 
502
        url -- the URL of the proxy server, e.g. http://proxy:8080
 
503
        """
 
504
        if url != "" and (not url.startswith("http://") or not ":" in url):
 
505
            raise errors.InvalidProxyError(proxy)
 
506
        self.http_proxy = url
 
507
        self.PropertyChanged("HttpProxy", self.http_proxy)
 
508
 
 
509
    def _set_remove_obsoleted_depends(self, remove_obsoleted_depends):
 
510
        """Set the handling of the removal of automatically installed
 
511
        dependencies which are now obsoleted.
 
512
 
 
513
        Keyword arguments:
 
514
        remove_obsoleted_depends -- If True also remove automatically installed
 
515
            dependencies of to removed packages
 
516
        """
 
517
        self.remove_obsoleted_depends = remove_obsoleted_depends
 
518
        self.PropertyChanged("RemoveObsoletedDepends",
 
519
                             self.remove_obsoleted_depends)
 
520
 
 
521
    def _set_allow_unauthenticated(self, allow_unauthenticated):
 
522
        """Set the handling of unauthenticated packages 
 
523
 
 
524
        Keyword arguments:
 
525
        allow_unauthenticated -- True to allow packages that come from a 
 
526
                           repository without a valid authentication signature
 
527
        """
 
528
        self.allow_unauthenticated = allow_unauthenticated
 
529
        self.PropertyChanged("AllowUnauthenticated", self.allow_unauthenticated)
 
530
 
 
531
    @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
532
                          in_signature="", out_signature="",
 
533
                          sender_keyword="sender")
 
534
    def Run(self, sender):
 
535
        """Queue the transaction for processing."""
 
536
        log_trans.info("Queuing transaction %s", self.tid)
 
537
        return self._run(sender)
 
538
 
 
539
    @inline_callbacks
 
540
    def _run(self, sender):
 
541
        yield self._check_foreign_user(sender)
 
542
        self.queue.put(self)
 
543
 
 
544
    @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
545
                          in_signature="", out_signature="",
 
546
                          sender_keyword="sender")
 
547
    def Cancel(self, sender):
 
548
        """Cancel the transaction.
 
549
 
 
550
        Keyword argument:
 
551
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
552
        """
 
553
        log_trans.info("Cancelling transaction %s", self.tid)
 
554
        return self._cancel(sender)
 
555
 
 
556
    @inline_callbacks
 
557
    def _cancel(self, sender):
 
558
        try:
 
559
            yield self._check_foreign_user(sender)
 
560
        except errors.ForeignTransaction:
 
561
            action = policykit1.PK_ACTION_CANCEL_FOREIGN
 
562
            yield policykit1.check_authorization_by_name(sender, action)
 
563
        try:
 
564
            self.queue.remove(self)
 
565
            log_trans.debug("Removed transaction from queue")
 
566
        except ValueError:
 
567
            pass
 
568
        else:
 
569
            self.status = STATUS_CANCELLING
 
570
            self.exit = EXIT_CANCELLED
 
571
            return
 
572
        if self.cancellable:
 
573
            log_trans.debug("Setting cancel event")
 
574
            self.cancelled = True
 
575
            self.status = STATUS_CANCELLING
 
576
            self.paused = False
 
577
            return
 
578
        raise errors.AptDaemonError("Could not cancel transaction")
 
579
 
 
580
    @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
581
                          in_signature="", out_signature="",
 
582
                          sender_keyword="sender")
 
583
    def Simulate(self, sender):
 
584
        """Simulate a transaction to update its dependencies, future status,
 
585
        download size and required disk space.
 
586
 
 
587
        Call this method if you want to show changes before queuing the
 
588
        transaction.
 
589
        """
 
590
        log_trans.info("Simulate was called")
 
591
        return self._simulate(sender)
 
592
 
 
593
    @inline_callbacks
 
594
    def _simulate(self, sender):
 
595
        if self.status != STATUS_SETTING_UP:
 
596
            raise errros.TransactionAlreadyRunning()
 
597
        yield self._check_foreign_user(sender)
 
598
        self.depends, self._dpkg_status, self.download, self.space = \
 
599
                self.queue.worker.simulate(self, self.queue.future_status)
 
600
 
 
601
    def _set_terminal(self, ttyname):
 
602
        """Set the controlling terminal.
 
603
 
 
604
        The worker will be attached to the specified slave end of a pty
 
605
        master/slave pair. This allows to interact with the 
 
606
 
 
607
        Can only be changed before the transaction is started.
 
608
 
 
609
        Keyword arguments:
 
610
        ttyname -- file path to the slave file descriptor
 
611
        """
 
612
        if self.status != STATUS_SETTING_UP:
 
613
            raise errros.TransactionAlreadyRunning()
 
614
        if not os.access(ttyname, os.W_OK):
 
615
            raise errors.AptDaemonError("Pty device does not exist: "
 
616
                                        "%s" % ttyname)
 
617
        if not os.stat(ttyname)[4] == self.uid:
 
618
            raise errors.AptDaemonError("Pty device '%s' has to be owned by"
 
619
                                        "the owner of the transaction "
 
620
                                        "(uid %s) " % (ttyname, self.uid))
 
621
        try:
 
622
            slave_fd = os.open(ttyname, os.O_RDWR | os.O_NOCTTY)
 
623
            if os.isatty(slave_fd):
 
624
                self.terminal = ttyname
 
625
                self.PropertyChanged("Terminal", self.terminal)
 
626
            else:
 
627
                raise errors.AptDaemonError("%s isn't a tty" % ttyname)
 
628
        finally:
 
629
            os.close(slave_fd)
 
630
 
 
631
    def _set_debconf(self, debconf_socket):
 
632
        """Set the socket of the debconf proxy.
 
633
 
 
634
        The worker process forwards all debconf commands through this
 
635
        socket by using the passthrough frontend. On the client side
 
636
        debconf-communicate should be connected to the socket.
 
637
 
 
638
        Can only be changed before the transaction is started.
 
639
 
 
640
        Keyword arguments:
 
641
        debconf_socket: absolute path to the socket
 
642
        """
 
643
        if self.status != STATUS_SETTING_UP:
 
644
            raise errors.TransactionAlreadyRunning()
 
645
        if not os.access(debconf_socket, os.W_OK):
 
646
            raise errors.AptDaemonError("socket does not exist: "
 
647
                                        "%s" % debconf_socket)
 
648
        if not os.stat(debconf_socket)[4] == self.uid:
 
649
            raise errors.AptDaemonError("socket '%s' has to be owned by the "
 
650
                                        "owner of the transaction " % ttyname)
 
651
        self.debconf = debconf_socket
 
652
        self.PropertyChanged("DebconfSocket", self.debconf)
 
653
 
 
654
    @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
655
                          in_signature="s", out_signature="",
 
656
                          sender_keyword="sender")
 
657
    def ProvideMedium(self, medium, sender):
 
658
        """Continue paused transaction with the inserted medium.
 
659
 
 
660
        If a media change is required to install packages from CD/DVD
 
661
        the transaction will be paused and could be resumed with this
 
662
        method.
 
663
 
 
664
        Keyword arguments:
 
665
        medium -- the label of the CD/DVD
 
666
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
667
        """
 
668
        log_trans.info("Medium %s was provided", medium)
 
669
        return self._provide_medium(medium, sender)
 
670
 
 
671
    @inline_callbacks
 
672
    def _provide_medium(self, medium, sender):
 
673
        yield self._check_foreign_user(sender)
 
674
        if not self.required_medium:
 
675
            raise errors.AptDaemonError("There isn't any required medium.")
 
676
        if not self.required_medium[0] == medium:
 
677
            raise errros.AptDaemonError("The medium '%s' isn't "
 
678
                                        "requested." % medium)
 
679
        self.paused = False
 
680
 
 
681
    @dbus_deferred_method(APTDAEMON_TRANSACTION_DBUS_INTERFACE,
 
682
                          in_signature="ss", out_signature="",
 
683
                          sender_keyword="sender")
 
684
    def ResolveConfigFileConflict(self, config, answer, sender):
 
685
        """Resolve a configuration file conflict and continue the transaction.
 
686
 
 
687
        If a config file prompt is detected the transaction will be
 
688
        paused and could be resumed with this method.
 
689
 
 
690
        Keyword arguments:
 
691
        config -- the path to the original config file
 
692
        answer -- the answer to the configuration file question, can be
 
693
                  "keep" or "replace"
 
694
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
695
        """
 
696
        log_trans.info("Resolved conflict of %s with %s", config, answer)
 
697
        return self._resolve_config_file_conflict(config, answer, sender)
 
698
 
 
699
    @inline_callbacks
 
700
    def _resolve_config_file_conflict(self, config, answer, sender):
 
701
        yield self._check_foreign_user(sender)
 
702
        if not self.config_file_conflict:
 
703
            raise errors.AptDaemonError("There isn't any config file prompt "
 
704
                                        "required")
 
705
        if answer not in ["keep", "replace"]:
 
706
            # FIXME: should we re-send the config file prompt
 
707
            #        message or assume the client is buggy and
 
708
            #        just use a safe default (like keep)?
 
709
            raise errors.AptDaemonError("Invalid value: %s" % answer)
 
710
        if not self.config_file_conflict[0] == config:
 
711
            raise errors.AptDaemonError("Invalid config file: %s" % config)
 
712
        self.config_file_conflict_resolution = answer
 
713
        self.paused = False
 
714
 
 
715
    @dbus_deferred_method(dbus.PROPERTIES_IFACE,
 
716
                          in_signature="ssv", out_signature="",
 
717
                          sender_keyword="sender")
 
718
    def Set(self, iface, property, value, sender):
 
719
        """Set a property.
 
720
 
 
721
        Only the user who intiaited the transaction is
 
722
        allowed to modify it.
 
723
 
 
724
        Keyword arguments:
 
725
        iface -- the interface which provides the property
 
726
        property -- the property which should be modified
 
727
        value -- the new value of the property
 
728
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
729
        """
 
730
        log.debug("Set() was called: %s, %s" % (property, value))
 
731
        return self._set(iface, property, value, sender)
 
732
 
 
733
    @inline_callbacks
 
734
    def _set(self, iface, property, value, sender):
 
735
        yield self._check_foreign_user(sender)
 
736
        if iface == APTDAEMON_TRANSACTION_DBUS_INTERFACE:
 
737
            if property == "MetaData":
 
738
                self._set_meta_data(value)
 
739
            elif property == "Terminal":
 
740
                self._set_terminal(value)
 
741
            elif property == "DebconfSocket":
 
742
                self._set_debconf(value)
 
743
            elif property == "Locale":
 
744
                self._set_locale(value)
 
745
            elif property == "RemoveObsoletedDepends":
 
746
                self._set_remove_obsoleted_depends(value)
 
747
            elif property == "AllowUnauthenticated":
 
748
                self._set_allow_unauthenticated(value)
 
749
            elif property == "HttpProxy":
 
750
                self._set_http_proxy(value)
 
751
            else:
 
752
                raise dbus.exceptions.DBusException("Unknown or read only "
 
753
                                                    "property: %s" % property)
 
754
        else:
 
755
            raise dbus.exceptions.DBusException("Unknown interface: %s" % iface)
 
756
 
 
757
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
758
                         in_signature="s", out_signature="a{sv}")
 
759
    def GetAll(self, iface):
 
760
        """Get all available properties of the given interface."""
 
761
        log.debug("GetAll() was called: %s" % iface)
 
762
        return self._get_properties(iface)
 
763
 
 
764
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
765
                         in_signature="ss", out_signature="v")
 
766
    def Get(self, iface, property):
 
767
        """Return the value of the given property provided by the given 
 
768
        interface.
 
769
        """
 
770
        log.debug("Get() was called: %s, %s" % (iface, property))
 
771
        return self._get_properties(iface)[property]
 
772
 
 
773
    def _get_properties(self, iface):
 
774
        #FIXME: Provide introspection information by overriding the Introspect
 
775
        #       method of the dbus.service.Object
 
776
        if iface == APTDAEMON_TRANSACTION_DBUS_INTERFACE:
 
777
            return {"Role": self.role,
 
778
                    "Progress": self.progress,
 
779
                    "ProgressDetails": self.progress_details,
 
780
                    "ProgressDownload": self.progress_download,
 
781
                    "Status": self.status,
 
782
                    "StatusDetails": self.status_details,
 
783
                    "Cancellable": self.cancellable,
 
784
                    "TerminalAttached": self.term_attached,
 
785
                    "RequiredMedium": self.required_medium,
 
786
                    "ConfigFileConflict": self.config_file_conflict,
 
787
                    "ExitState": self.exit,
 
788
                    "Error": self.error,
 
789
                    "Locale": self.locale,
 
790
                    "Terminal": self.terminal,
 
791
                    "DebconfSocket": self.debconf,
 
792
                    "Paused": self.paused,
 
793
                    "AllowUnauthenticated": self.allow_unauthenticated,
 
794
                    "RemoveObsoletedDepends": self.remove_obsoleted_depends,
 
795
                    "HttpProxy": self.http_proxy,
 
796
                    "Packages": self.packages,
 
797
                    "MetaData": self.meta_data,
 
798
                    "Dependencies": self.depends,
 
799
                    "Download": self.download,
 
800
                    "Space": self.space
 
801
                    }
 
802
        else:
 
803
            return {}
 
804
 
 
805
    @inline_callbacks
 
806
    def _check_foreign_user(self, dbus_name):
 
807
        """Check if the transaction is owned by the given caller."""
 
808
        uid = yield policykit1.get_uid_from_dbus_name(dbus_name)
 
809
        if self.uid != uid:
 
810
            raise errors.ForeignTransaction()
 
811
 
 
812
    def _set_kwargs(self, kwargs):
 
813
        """Set the kwargs which will be send to the AptWorker."""
 
814
        self.kwargs = kwargs
 
815
 
 
816
 
 
817
class TransactionQueue(gobject.GObject):
 
818
 
 
819
    """Queue for transactions."""
 
820
 
 
821
    __gsignals__ = {"queue-changed":(gobject.SIGNAL_RUN_FIRST,
 
822
                                     gobject.TYPE_NONE,
 
823
                                     ()),
 
824
                    "future-status-changed":(gobject.SIGNAL_RUN_FIRST,
 
825
                                     gobject.TYPE_NONE,
 
826
                                     ())}
 
827
 
 
828
    def __init__(self, dummy):
 
829
        """Intialize a new TransactionQueue instance."""
 
830
        gobject.GObject.__init__(self)
 
831
        self._queue = collections.deque()
 
832
        self._proc_count = 0
 
833
        if dummy:
 
834
            self.worker = DummyWorker()
 
835
        else:
 
836
            self.worker = AptWorker()
 
837
        self.future_status = None
 
838
        self.future_status_fd = None
 
839
        self.worker.connect("transaction-done", self._on_transaction_done)
 
840
 
 
841
    def __len__(self):
 
842
        return len(self._queue)
 
843
 
 
844
    def _emit_queue_changed(self):
 
845
        """Emit the queued-changed signal."""
 
846
        log.debug("emitting queue changed")
 
847
        self.emit("queue-changed")
 
848
 
 
849
    def _emit_future_status_changed(self):
 
850
        """Emit the future-status-changed signal."""
 
851
        log.debug("emitting future-status changed")
 
852
        self.emit("future-status-changed")
 
853
        #FIXME: All not yet queued transactions should listen to this signal
 
854
        #       and update be re-simulated if already done so
 
855
 
 
856
    def put(self, trans):
 
857
        """Add an item to the queue."""
 
858
        #FIXME: Add a timestamp to check if the future status of the trans
 
859
        #       is really the later one
 
860
        # Simulate the new transaction if this has not been done before:
 
861
        if trans._dpkg_status is None:
 
862
            trans.depends, trans._dpkg_status, trans.download, trans.space = \
 
863
                self.worker.simulate(trans, self.future_status)
 
864
        # Replace the old future status with the new one
 
865
        if self.future_status_fd is not None:
 
866
            os.close(self.future_status_fd)
 
867
            os.remove(self.future_status)
 
868
        self.future_status_fd, self.future_status = \
 
869
            tempfile.mkstemp(prefix="future-status-")
 
870
        os.write(self.future_status_fd, trans._dpkg_status)
 
871
        self._emit_future_status_changed()
 
872
 
 
873
        if trans._idle_watch is not None:
 
874
            gobject.source_remove(trans._idle_watch)
 
875
        if self.worker.trans:
 
876
            self._queue.append(trans)
 
877
        else:
 
878
            self.worker.run(trans)
 
879
        self._emit_queue_changed()
 
880
 
 
881
    def _on_transaction_done(self, worker, tid):
 
882
        """Mark the last item as done and request a new item."""
 
883
        #FIXME: Check if the transaction failed because of a broken system or
 
884
        #       if dpkg journal is dirty. If so allready queued transactions
 
885
        #       except the repair transactions should be removed from the queue
 
886
        try:
 
887
            next = self._queue.popleft()
 
888
        except IndexError:
 
889
            log.debug("There isn't any queued transaction")
 
890
            # Reset the future status to the system one
 
891
            if self.future_status_fd is not None:
 
892
                os.close(self.future_status_fd)
 
893
                os.remove(self.future_status)
 
894
            self.future_status_fd = None
 
895
            self.future_status = None
 
896
            self._emit_future_status_changed()
 
897
        else:
 
898
            self.worker.run(next)
 
899
        self._emit_queue_changed()
 
900
 
 
901
    def remove(self, transaction):
 
902
        """Remove the specified item from the queue."""
 
903
        # FIXME: handle future status
 
904
        self._queue.remove(transaction)
 
905
        self._emit_queue_changed()
 
906
 
 
907
    def clear(self):
 
908
        """Remove all items from the queue."""
 
909
        # FIXME: handle future status
 
910
        for transaction in self._queue:
 
911
            transaction._remove_from_connection_no_raise()
 
912
        self._queue.clear()
 
913
 
 
914
    @property
 
915
    def items(self):
 
916
        """Return a list containing all queued items."""
 
917
        return list(self._queue)
 
918
 
 
919
 
 
920
class AptDaemon(dbus.service.Object):
 
921
 
 
922
    """Provides a system daemon to process package management tasks.
 
923
 
 
924
    The daemon is transaction based. Each package management tasks runs
 
925
    in a separate transaction. The transactions can be created,
 
926
    monitored and managed via the D-Bus interface.
 
927
    """
 
928
 
 
929
    def __init__(self, options, connect=True, bus=None):
 
930
        """Initialize a new AptDaemon instance.
 
931
 
 
932
        Keyword arguments:
 
933
        options -- command line options of the type optparse.Values
 
934
        connect -- if the daemon should connect to the D-Bus (default is True)
 
935
        bus -- the D-Bus to connect to (defaults to the system bus)
 
936
        """
 
937
        log.info("Initializing daemon")
 
938
        signal.signal(signal.SIGQUIT, self._sigquit)
 
939
        signal.signal(signal.SIGTERM, self._sigquit)
 
940
        self.options = options
 
941
        if connect == True:
 
942
            if bus is None:
 
943
                bus = dbus.SystemBus()
 
944
            # Check if another object has already registered the name on
 
945
            # the bus. Quit the other daemon if replace would be set
 
946
            try:
 
947
                bus_name = dbus.service.BusName(APTDAEMON_DBUS_SERVICE,
 
948
                                                bus,
 
949
                                                do_not_queue=True)
 
950
            except dbus.exceptions.NameExistsException:
 
951
                if self.options.replace == False:
 
952
                    log.critical("Another daemon is already running")
 
953
                    sys.exit(1)
 
954
                log.warn("Replacing already running daemon")
 
955
                the_other_guy = bus.get_object(APTDAEMON_DBUS_SERVICE,
 
956
                                               APTDAEMON_DBUS_PATH)
 
957
                the_other_guy.Quit(dbus_interface=APTDAEMON_DBUS_INTERFACE,
 
958
                                   timeout=300)
 
959
                time.sleep(1)
 
960
                bus_name = dbus.service.BusName(APTDAEMON_DBUS_SERVICE,
 
961
                                                bus,
 
962
                                                do_not_queue=True)
 
963
        else:
 
964
            bus_name = None
 
965
        dbus.service.Object.__init__(self, bus_name, APTDAEMON_DBUS_PATH)
 
966
        self.queue = TransactionQueue(options.dummy)
 
967
        self.queue.connect("queue-changed", self._on_queue_changed)
 
968
        log.debug("Setting up worker thread")
 
969
        log.debug("Daemon was initialized")
 
970
 
 
971
    def _on_queue_changed(self, queue):
 
972
        """Callback for a changed transaction queue."""
 
973
        if self.queue.worker.trans:
 
974
            current = self.queue.worker.trans.tid
 
975
        else:
 
976
            current = ""
 
977
        queued = [trans.tid for trans in self.queue.items]
 
978
        self.ActiveTransactionsChanged(current, queued)
 
979
 
 
980
    @dbus.service.signal(dbus_interface=APTDAEMON_DBUS_INTERFACE,
 
981
                         signature="sas")
 
982
    def ActiveTransactionsChanged(self, current, queued):
 
983
        """Emit the ActiveTransactionsChanged signal.
 
984
 
 
985
        Keyword arguments:
 
986
        current -- the tid of the currently running transaction or an empty
 
987
                   string
 
988
        queued -- list of the ids of the queued transactions
 
989
        """
 
990
        log.debug("Emitting ActiveTransactionsChanged signal: %s, %s",
 
991
                  current, queued)
 
992
 
 
993
    def run(self):
 
994
        """Start the daemon and listen for calls."""
 
995
        if self.options.disable_timeout == False:
 
996
            log.debug("Using inactivity check")
 
997
            gobject.timeout_add_seconds(APTDAEMON_IDLE_CHECK_INTERVAL,
 
998
                                        self._check_for_inactivity)
 
999
        log.debug("Waiting for calls")
 
1000
        try:
 
1001
            mainloop.run()
 
1002
        except KeyboardInterrupt:
 
1003
            self.Quit(None)
 
1004
 
 
1005
    @inline_callbacks
 
1006
    def _create_trans(self, role, action, sender, packages=None, kwargs=None):
 
1007
        """Helper method which returns the tid of a new transaction."""
 
1008
        yield policykit1.check_authorization_by_name(sender, action)
 
1009
        uid = yield policykit1.get_uid_from_dbus_name(sender)
 
1010
        trans = Transaction(role, self.queue, uid, packages=packages,
 
1011
                            kwargs=kwargs)
 
1012
        return_value(trans.tid)
 
1013
 
 
1014
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1015
                          in_signature="", out_signature="s",
 
1016
                          sender_keyword="sender")
 
1017
    def FixIncompleteInstall(self, sender):
 
1018
        """Return the id of a newly create transaction which will try to
 
1019
        fix incomplete installations by running dpkg --configure -a.
 
1020
 
 
1021
        Keyword argument:
 
1022
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1023
        """
 
1024
        log.info("FixIncompleteInstall() called")
 
1025
        return self._create_trans(ROLE_FIX_INCOMPLETE_INSTALL,
 
1026
                                  policykit1.PK_ACTION_FIX,
 
1027
                                  sender)
 
1028
 
 
1029
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1030
                          in_signature="", out_signature="s",
 
1031
                          sender_keyword="sender")
 
1032
    def FixBrokenDepends(self, sender):
 
1033
        """Return the id of a newly create transaction which will try to
 
1034
        fix broken dependencies.
 
1035
 
 
1036
        Keyword argument:
 
1037
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1038
        """
 
1039
        log.info("FixBrokenDepends() called")
 
1040
        return self._create_trans(ROLE_FIX_BROKEN_DEPENDS,
 
1041
                                  policykit1.PK_ACTION_FIX,
 
1042
                                  sender)
 
1043
 
 
1044
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1045
                          in_signature="", out_signature="s",
 
1046
                          sender_keyword="sender")
 
1047
    def UpdateCache(self, sender):
 
1048
        """Return the id of a newly create transaction which will update
 
1049
        the package cache.
 
1050
 
 
1051
        Download the latest information about available packages from the
 
1052
        repositories.
 
1053
 
 
1054
        Keyword argument:
 
1055
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1056
        """
 
1057
        log.info("UpdateCache() was called")
 
1058
        return self._create_trans(ROLE_UPDATE_CACHE,
 
1059
                                  policykit1.PK_ACTION_UPDATE_CACHE,
 
1060
                                  sender)
 
1061
 
 
1062
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1063
                          in_signature="as", out_signature="s",
 
1064
                          sender_keyword="sender")
 
1065
    def RemovePackages(self, package_names, sender):
 
1066
        """Return the id of a newly create transaction which will remove
 
1067
        the given packages.
 
1068
 
 
1069
        Keyword arguments:
 
1070
        package_names -- list of package names to remove
 
1071
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1072
        """
 
1073
        log.info("RemovePackages() was called: '%s'", package_names)
 
1074
        return self._create_trans(ROLE_REMOVE_PACKAGES,
 
1075
                                  policykit1.PK_ACTION_REMOVE_PACKAGES,
 
1076
                                  sender,
 
1077
                                  packages=([], [], package_names, [], []))
 
1078
 
 
1079
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1080
                          in_signature="b", out_signature="s",
 
1081
                          sender_keyword="sender")
 
1082
    def UpgradeSystem(self, safe_mode, sender):
 
1083
        """Upgrade the whole system.
 
1084
 
 
1085
        If in safe mode only already installed packages will be updated.
 
1086
        Updates which require to remove installed packages or to install
 
1087
        additional packages will be skipped.
 
1088
 
 
1089
        Keyword arguments:
 
1090
        safe_mode -- boolean value if the safe mode should be used
 
1091
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1092
        """
 
1093
        log.info("UpgradeSystem() was called with safe mode: "
 
1094
                 "%s" % safe_mode)
 
1095
        return self._create_trans(ROLE_UPGRADE_SYSTEM,
 
1096
                                  policykit1.PK_ACTION_UPGRADE_SYSTEM,
 
1097
                                  sender,
 
1098
                                  kwargs={"safe_mode": safe_mode})
 
1099
 
 
1100
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1101
                          in_signature="asasasasas", out_signature="s",
 
1102
                          sender_keyword="sender")
 
1103
    def CommitPackages(self, install, reinstall, remove, purge, upgrade,
 
1104
                       sender):
 
1105
        """Perform a complex package operation.
 
1106
 
 
1107
        Keyword arguments:
 
1108
        install -- package names to be installed
 
1109
        reinstall -- packages names to be reinstalled
 
1110
        remove -- package names to be removed
 
1111
        purge -- package names to be removed including configuration
 
1112
        upgrade -- package names to be upgraded
 
1113
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1114
        """
 
1115
        #FIXME: take sha1 or md5 cash into accout to allow selecting a version
 
1116
        #       or an origin different from the candidate
 
1117
        log.info("CommitPackages() was called: %s, %s, %s, %s, %s",
 
1118
                 install, reinstall, remove, purge, upgrade)
 
1119
        return self._commit_packages(install, reinstall, remove, purge, upgrade,
 
1120
                                     sender)
 
1121
 
 
1122
    @inline_callbacks
 
1123
    def _commit_packages(self, install, reinstall, remove, purge, upgrade,
 
1124
                         sender):
 
1125
        def check_empty_list(lst):
 
1126
            if lst == [""]:
 
1127
                return []
 
1128
            else:
 
1129
                return lst
 
1130
        packages = [check_empty_list(lst) for lst in [install, reinstall,
 
1131
                                                      remove, purge, upgrade]]
 
1132
        if install != [""] or reinstall != [""]:
 
1133
            action = policykit1.PK_ACTION_INSTALL_PACKAGES
 
1134
            yield policykit1.check_authorization_by_name(sender, action)
 
1135
        elif upgrade != [""]:
 
1136
            action = policykit1.PK_ACTION_UPGRADE_PACKAGES
 
1137
            yield policykit1.check_authorization_by_name(sender, action)
 
1138
        if remove !=  [""] or purge != [""]:
 
1139
            action = policykit1.PK_ACTION_REMOVE_PACKAGES
 
1140
            yield policykit1.check_authorization_by_name(sender, action)
 
1141
        uid = yield policykit1.get_uid_from_dbus_name(sender)
 
1142
        trans = Transaction(ROLE_COMMIT_PACKAGES, self.queue, uid,
 
1143
                            packages=packages)
 
1144
        return_value(trans.tid)
 
1145
 
 
1146
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1147
                          in_signature="as", out_signature="s",
 
1148
                          sender_keyword="sender")
 
1149
    def InstallPackages(self, package_names, sender):
 
1150
        """Install the given packages.
 
1151
 
 
1152
        Keyword arguments:
 
1153
        package_names -- list of package names to install
 
1154
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1155
        """
 
1156
        log.info("InstallPackages() was called: %s" % package_names)
 
1157
        return self._create_trans(ROLE_INSTALL_PACKAGES,
 
1158
                                  policykit1.PK_ACTION_INSTALL_PACKAGES,
 
1159
                                  sender,
 
1160
                                  packages=(package_names, [], [], [], []))
 
1161
 
 
1162
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1163
                         in_signature="as", out_signature="s",
 
1164
                         sender_keyword="sender")
 
1165
    def UpgradePackages(self, package_names, sender):
 
1166
        """Upgrade the given packages to their latest version.
 
1167
 
 
1168
        Keyword arguments:
 
1169
        package_names -- list of package names to upgrade
 
1170
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1171
        """
 
1172
        log.info("UpgradePackages() was called: %s" % package_names)
 
1173
        return self._create_trans(ROLE_UPGRADE_PACKAGES,
 
1174
                                  policykit1.PK_ACTION_UPGRADE_PACKAGES,
 
1175
                                  sender,
 
1176
                                  packages=([], [], [], [], package_names))
 
1177
 
 
1178
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1179
                          in_signature="s", out_signature="s",
 
1180
                          sender_keyword="sender")
 
1181
    def AddVendorKeyFromFile(self, path, sender):
 
1182
        """Install the given key file.
 
1183
 
 
1184
        Keyword arguments:
 
1185
        path -- the absolute path to the key file
 
1186
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1187
        """
 
1188
        #FIXME: Should not be a transaction
 
1189
        log.info("InstallVendorKeyFile() was called: %s" % path)
 
1190
        return self._create_trans(ROLE_ADD_VENDOR_KEY,
 
1191
                                  policykit1.PK_ACTION_ADD_VENDOR_KEY,
 
1192
                                  sender,
 
1193
                                  kwargs={"path": path})
 
1194
 
 
1195
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1196
                          in_signature="s", out_signature="s",
 
1197
                          sender_keyword="sender")
 
1198
    def RemoveVendorKey(self, fingerprint, sender):
 
1199
        """Remove the given key.
 
1200
 
 
1201
        Keyword arguments:
 
1202
        fingerprint -- the fingerprint of the key to remove
 
1203
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1204
        """
 
1205
        #FIXME: Should not be a transaction
 
1206
        log.info("RemoveVendorKey() was called: %s" % fingerprint)
 
1207
        return self._create_trans(ROLE_REMOVE_VENDOR_KEY,
 
1208
                                  policykit1.PK_ACTION_REMOVE_VENDOR_KEY,
 
1209
                                  sender,
 
1210
                                  kwargs={"fingerprint": fingerprint})
 
1211
 
 
1212
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1213
                          in_signature="s", out_signature="s",
 
1214
                          sender_keyword="sender")
 
1215
    def InstallFile(self, path, sender):
 
1216
        """Install the given package file.
 
1217
 
 
1218
        Keyword arguments:
 
1219
        path -- the absolute path to the package file
 
1220
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1221
        """
 
1222
        log.info("InstallFile() was called: %s" % path)
 
1223
        #FIXME: Perform some checks
 
1224
        #FIXME: Should we already extract the package name here?
 
1225
        return self._create_trans(ROLE_INSTALL_FILE,
 
1226
                                  policykit1.PK_ACTION_INSTALL_FILE,
 
1227
                                  sender,
 
1228
                                  kwargs={"path": path})
 
1229
 
 
1230
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1231
                          in_signature="sssasss", out_signature="",
 
1232
                          sender_keyword="sender")
 
1233
    def AddRepository(self, type, uri, dist, comps, comment, sourcesfile,
 
1234
                      sender):
 
1235
        """Add given repository to the sources list.
 
1236
 
 
1237
        Keyword arguments:
 
1238
        type -- the type of the entry (deb, deb-src)
 
1239
        uri -- the main repository uri (e.g. http://archive.ubuntu.com/ubuntu)
 
1240
        dist -- the distribution to use (e.g. karmic, "/")
 
1241
        comps -- a (possible empty) list of components (main, restricted)
 
1242
        comment -- an (optional) comment
 
1243
        sourcesfile -- an (optinal) filename in sources.list.d 
 
1244
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1245
        """
 
1246
        log.info("AddRepository() was called: type='%s' uri='%s' "
 
1247
                 "dist='%s' comps='%s' comment='%s' sourcefile='%s'",
 
1248
                 type, uri, dist, comps, comment, sourcesfile)
 
1249
        return self._add_repository(self, type, uri, dist, comps, comment,
 
1250
                                    sourcesfile, sender)
 
1251
 
 
1252
    @inline_callbacks
 
1253
    def _add_repository(self, type, uri, dist, comps, comment, sourcesfile,
 
1254
                        sender):
 
1255
        if sourcesfile:
 
1256
            if not sourcesfile.endswith(".list"):
 
1257
                sourcesfile += ".list"
 
1258
            d = apt_pkg.config.find_dir("Dir::Etc::sourceparts")
 
1259
            sourcesfile = os.path.join(d, os.path.basename(sourcesfile))
 
1260
        else:
 
1261
            sourcesfile = None
 
1262
        action = policykit1.PK_ACTION_CHANGE_REPOSITORY
 
1263
        yield policykit1.check_authorization_by_name(sender, action)
 
1264
        old_umask = os.umask(0022)
 
1265
        try:
 
1266
            sources = SourcesList()
 
1267
            entry = sources.add(type, uri, dist, comps, comment,
 
1268
                                file=sourcesfile)
 
1269
            if entry.invalid:
 
1270
                raise errors.RepositoryInvalidError()
 
1271
        #FIXME: Should be removed after requiring Python 2.6
 
1272
        except Exception, error:
 
1273
            raise error
 
1274
        else:
 
1275
            sources.save()
 
1276
        finally:
 
1277
            os.umask(old_umask)
 
1278
 
 
1279
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1280
                          in_signature="s", out_signature="b",
 
1281
                          sender_keyword="sender")
 
1282
    def EnableDistroComponent(self, component, sender):
 
1283
        """Enable given component in the sources list.
 
1284
 
 
1285
        Keyword arguments:
 
1286
        component -- a components, e.g. main or universe
 
1287
        sender -- the unique D-Bus name of the sender (provided by D-Bus)
 
1288
        """
 
1289
        log.info("EnableComponent() was called: component='%s' ", component)
 
1290
        return self._enable_distro_component(component, sender)
 
1291
 
 
1292
    @inline_callbacks
 
1293
    def _enable_distro_component(self, component, sender):
 
1294
        action = policykit1.PK_ACTION_CHANGE_REPOSITORY
 
1295
        yield policykit1.check_authorization_by_name(sender, action)
 
1296
        old_umask = os.umask(0022)
 
1297
        try:
 
1298
            sourceslist = SourcesList()
 
1299
            distro = aptsources.distro.get_distro()
 
1300
            distro.get_sources(sourceslist)
 
1301
            distro.enable_component(component)
 
1302
            sourceslist.save()
 
1303
        finally:
 
1304
            os.umask(old_umask)
 
1305
 
 
1306
    @dbus_deferred_method(APTDAEMON_DBUS_INTERFACE,
 
1307
                          in_signature="", out_signature="as",
 
1308
                          sender_keyword="sender")
 
1309
    def GetTrustedVendorKeys(self, sender):
 
1310
        """Return a list of installed repository keys."""
 
1311
        log.info("GetTrustedVendorKeys() was called")
 
1312
        return self._get_trusted_vendor_keys(sender)
 
1313
 
 
1314
    @inline_callbacks
 
1315
    def _get_trusted_vendor_keys(self, sender):
 
1316
        aptauth = AptAuth()
 
1317
        action = policykit1.PK_ACTION_GET_TRUSTED_VENDOR_KEYS
 
1318
        yield policykit1.check_authorization_by_name(sender, action)
 
1319
        return_value([key.decode("utf-8", "ignore") for key in aptauth.list()])
 
1320
 
 
1321
    @dbus.service.method(APTDAEMON_DBUS_INTERFACE,
 
1322
                         in_signature="", out_signature="sas")
 
1323
    def GetActiveTransactions(self):
 
1324
        """Return the currently running transaction and the list of queued
 
1325
        transactions.
 
1326
        """
 
1327
        log.debug("GetActiveTransactions() was called")
 
1328
        queued = [trans.tid for trans in self.queue.items]
 
1329
        if self.queue.worker.trans:
 
1330
            current = self.queue.worker.trans.tid
 
1331
        else:
 
1332
            current = ""
 
1333
        return current, queued
 
1334
 
 
1335
    @dbus.service.method(APTDAEMON_DBUS_INTERFACE,
 
1336
                         in_signature="", out_signature="",
 
1337
                         sender_keyword="caller_name")
 
1338
    def Quit(self, caller_name):
 
1339
        """Request a shutdown of the daemon.
 
1340
 
 
1341
        Keyword argument:
 
1342
        caller_name -- the D-Bus name of the caller (provided by D-Bus)
 
1343
        """
 
1344
        log.info("Shutdown was requested")
 
1345
        log.debug("Quitting main loop...")
 
1346
        mainloop.quit()
 
1347
        log.debug("Exit")
 
1348
 
 
1349
    def _sigquit(self, signum, frame):
 
1350
        """Internal callback for the quit signal."""
 
1351
        self.Quit(None)
 
1352
 
 
1353
    def _check_for_inactivity(self):
 
1354
        """Shutdown the daemon if it has been inactive for time specified
 
1355
        in APTDAEMON_IDLE_TIMEOUT.
 
1356
        """
 
1357
        log.debug("Checking for inactivity")
 
1358
        timestamp = self.queue.worker.last_action_timestamp
 
1359
        if not self.queue.worker.trans and \
 
1360
           not gobject.main_context_default().pending() and \
 
1361
           time.time() - timestamp > APTDAEMON_IDLE_TIMEOUT and \
 
1362
           not self.queue:
 
1363
            log.info("Quiting due to inactivity")
 
1364
            self.Quit(None)
 
1365
            return False
 
1366
        return True
 
1367
 
 
1368
 
 
1369
def main():
 
1370
    """Allow to run the daemon from the command line."""
 
1371
    parser = OptionParser()
 
1372
    parser.add_option("-t", "--disable-timeout",
 
1373
                      default=False,
 
1374
                      action="store_true", dest="disable_timeout",
 
1375
                      help=_("Do not shutdown the daemon because of "
 
1376
                             "inactivity"))
 
1377
    parser.add_option("-d", "--debug",
 
1378
                      default=False,
 
1379
                      action="store_true", dest="debug",
 
1380
                      help=_("Show internal processing information"))
 
1381
    parser.add_option("-r", "--replace",
 
1382
                      default=False,
 
1383
                      action="store_true", dest="replace",
 
1384
                      help=_("Quit and replace an already running daemon"))
 
1385
    parser.add_option("-p", "--profile",
 
1386
                      default=False,
 
1387
                      action="store", type="string", dest="profile",
 
1388
                      help=_("Store profile stats in the specified file"))
 
1389
    parser.add_option("--dummy",
 
1390
                      default=False,
 
1391
                      action="store_true", dest="dummy",
 
1392
                      help=_("Do not make any changes to the system (Only "
 
1393
                             "of use to developers)"))
 
1394
    options, args = parser.parse_args()
 
1395
    if options.debug == True:
 
1396
        log.setLevel(logging.DEBUG)
 
1397
    else:
 
1398
        log.setLevel(logging.INFO)
 
1399
        _console_handler.setLevel(logging.INFO)
 
1400
    daemon = AptDaemon(options)
 
1401
    if options.profile:
 
1402
        import profile
 
1403
        profiler = profile.Profile()
 
1404
        profiler.runcall(daemon.run)
 
1405
        profiler.dump_stats(options.profile)
 
1406
        profiler.print_stats()
 
1407
    else:
 
1408
        daemon.run()
 
1409
 
 
1410
# vim:ts=4:sw=4:et