#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Handles the apt system lock"""
# Copyright (C) 2010 Sebastian Heinlein <devel@glatzor.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
__author__ = "Sebastian Heinlein <devel@glatzor.de>"
__all__ = ("LockFailedError", "system")
import fcntl
import os
import struct
import apt_pkg
from gi.repository import GLib
from aptdaemon import enums
from aptdaemon.errors import TransactionCancelled
class LockFailedError(Exception):
"""The locking of file failed."""
def __init__(self, flock, process=None):
"""Return a new LockFailedError instance.
Keyword arguments:
flock -- the path of the file lock
process -- the process which holds the lock or None
"""
msg = "Could not acquire lock on %s." % flock
if process:
msg += " The lock is held by %s." % process
Exception.__init__(self, msg)
self.flock = flock
self.process = process
class FileLock(object):
"""Represents a file lock."""
def __init__(self, path):
self.path = path
self.fd = None
@property
def locked(self):
return self.fd is not None
def acquire(self):
"""Return the file descriptor of the lock file or raise
LockFailedError if the lock cannot be obtained.
"""
if self.fd:
return self.fd
fd_lock = apt_pkg.get_lock(self.path)
if fd_lock < 0:
process = get_locking_process_name(self.path)
raise LockFailedError(self.path, process)
else:
self.fd = fd_lock
return fd_lock
def release(self):
"""Relase the lock."""
if self.fd:
os.close(self.fd)
self.fd = None
def get_locking_process_name(lock_path):
"""Return the name of a process which holds a lock. It will be None if
the name cannot be retrivied.
"""
try:
fd_lock_read = open(lock_path, "r")
except IOError:
return None
else:
# Get the pid of the locking application
flk = struct.pack('hhQQi', fcntl.F_WRLCK, os.SEEK_SET, 0, 0, 0)
flk_ret = fcntl.fcntl(fd_lock_read, fcntl.F_GETLK, flk)
pid = struct.unpack("hhQQi", flk_ret)[4]
# Get the command of the pid
try:
with open("/proc/%s/status" % pid, "r") as fd_status:
try:
for key, value in (line.split(":") for line in
fd_status.readlines()):
if key == "Name":
return value.strip()
except Exception:
return None
except IOError:
return None
finally:
fd_lock_read.close()
return None
apt_pkg.init()
#: The lock for dpkg status file
_status_dir = os.path.dirname(apt_pkg.config.find_file("Dir::State::status"))
status_lock = FileLock(os.path.join(_status_dir, "lock"))
frontend_lock = FileLock(os.path.join(_status_dir, "lock-frontend"))
#: The lock for the package archive
_archives_dir = apt_pkg.config.find_dir("Dir::Cache::Archives")
archive_lock = FileLock(os.path.join(_archives_dir, "lock"))
#: The lock for the repository indexes
lists_lock = FileLock(os.path.join(
apt_pkg.config.find_dir("Dir::State::lists"), "lock"))
def acquire():
"""Acquire an exclusive lock for the package management system."""
try:
for lock in frontend_lock, status_lock, archive_lock, lists_lock:
if not lock.locked:
lock.acquire()
except:
release()
raise
os.environ['DPKG_FRONTEND_LOCKED'] = '1'
def release():
"""Release an exclusive lock for the package management system."""
for lock in lists_lock, archive_lock, status_lock, frontend_lock:
lock.release()
try:
del os.environ['DPKG_FRONTEND_LOCKED']
except KeyError:
pass
def wait_for_lock(trans, alt_lock=None):
"""Acquire the system lock or the optionally given one. If the lock
cannot be obtained pause the transaction in the meantime.
:param trans: the transaction
:param lock: optional alternative lock
"""
def watch_lock():
"""Helper to unpause the transaction if the lock can be obtained.
Keyword arguments:
trans -- the corresponding transaction
alt_lock -- alternative lock to the system lock
"""
try:
if alt_lock:
alt_lock.acquire()
else:
acquire()
except LockFailedError:
return True
trans.paused = False
return True
try:
if alt_lock:
alt_lock.acquire()
else:
acquire()
except LockFailedError as error:
trans.paused = True
trans.status = enums.STATUS_WAITING_LOCK
if error.process:
# TRANSLATORS: %s is the name of a package manager
msg = trans.gettext("Waiting for %s to exit")
trans.status_details = msg % error.process
lock_watch = GLib.timeout_add_seconds(3, watch_lock)
while trans.paused and not trans.cancelled:
GLib.main_context_default().iteration()
GLib.source_remove(lock_watch)
if trans.cancelled:
raise TransactionCancelled()
# vim:ts=4:sw=4:et