summarylogtreecommitdiffstats
path: root/follow_hook.py
blob: 6e15fd2ee3bd2eb1f056ea779804fc597b5430c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python
"""Follow which hook is handled by the SELinux ALPM hook"""
import logging
import re
import os.path
import subprocess
import sys


ALPM_HOOK_DIR = '/usr/share/libalpm/hooks/'
FOLLOW_DB = os.path.join(os.path.dirname(__file__), 'hook_tracking_db.txt')

logger = logging.getLogger(__name__)


def run_pacman(args):
    """Run a pacman command and return its output"""
    proc = subprocess.Popen(
        ['pacman'] + args,
        env={'LANG': 'C'},
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output = proc.stdout.read()
    retval = proc.wait()
    if retval:
        errmsg = proc.communicate()[1].decode('ascii', errors='ignore').strip()
        logger.error("pacman error %d: %s", retval, errmsg)
        return
    return output.decode('ascii', errors='ignore')


def find_package_hooks():
    """Find all available package hooks in Arch Linux repositories

    This uses the file database, which needs to be updated with "pacman -Fy"

    Yield (repo/package name, hook name) tuples
    """
    alpm_hookdir_no_leading_slash = ALPM_HOOK_DIR.lstrip('/')
    pkglist = run_pacman(['-Fqo', ALPM_HOOK_DIR]) or ''
    for pkgname in pkglist.splitlines():
        filelist = run_pacman(['-Fql', pkgname]) or ''
        for filename in filelist.splitlines():
            if filename.startswith(alpm_hookdir_no_leading_slash):
                hookname = filename[len(alpm_hookdir_no_leading_slash):]
                if hookname and hookname.endswith('.hook'):
                    yield (pkgname, hookname)


def get_repo_version(pkgname):
    """Get the version of the package in the repositories"""
    # This function can be called with a "repo/pkgname" argument
    pkgname = pkgname.split('/')[-1]
    pkginfo = run_pacman(['-Si', pkgname]) or ''
    for line in pkginfo.splitlines():
        line = line.rstrip()
        matches = re.match(r'^Version\s*:\s*(\S+)$', line, re.I)
        if matches is not None:
            return matches.group(1)
    logger.error("Unable to find the version of package %s", pkgname)


def read_pkghook_db():
    """Read the package hook tracking database file"""
    pkghookdb = {}
    with open(FOLLOW_DB, 'r') as fdb:
        for line in fdb:
            line = line.strip()
            if not line or line.startswith('#'):
                continue
            try:
                pkg, ver, hook = line.split()
            except ValueError:
                # Ignore invalid lines
                logger.warning("Invalid line in tracking database: %r", line)
                continue
            else:
                pkghookdb[(pkg, hook)] = ver
    return pkghookdb


def main():
    current_pkghook_db = read_pkghook_db()
    everything_ok = True
    for pkg, hook in find_package_hooks():
        # Get the versions in the package repositories
        last_ver = get_repo_version(pkg)
        if last_ver is None:
            everything_ok = False
            continue
        # ... and in the tracking database
        cur_ver = current_pkghook_db.get((pkg, hook))

        if last_ver == cur_ver:
            logger.info("OK %s %s %s", pkg, cur_ver, hook)
            continue
        everything_ok = False
        if cur_ver is None:
            logger.info("New hook:")
        else:
            logger.info("Upgrade needed for %s: %s -> %s",
                        pkg, cur_ver, last_ver)
        logger.info("    DB line: %s %s %s", pkg, last_ver, hook)
        hookfile = os.path.join(ALPM_HOOK_DIR, hook)
        logger.info("    File: %s", hookfile)
        # Try to find the Exec line in the file
        if os.path.exists(hookfile):
            exec_lines = []
            target_lines = []
            need_target = False
            with open(hookfile, 'r') as fhook:
                for line in fhook:
                    line = line.rstrip()
                    if line.startswith('Exec'):
                        exec_lines.append(line)
                    elif line.startswith('Target'):
                        target_lines.append(line)
                    elif line.startswith('NeedsTargets'):
                        need_target = True
            # Show Target lines if NeedsTargets was found
            if need_target:
                for line in target_lines:
                    logger.info("    # %s", line)
            for line in exec_lines:
                logger.info("    # %s", line)
    if everything_ok:
        logger.info("Remember to run sudo pacman -Fy to update pacman db")
    return 0


if __name__ == '__main__':
    logging.basicConfig(
        format='[%(levelname)s] %(message)s',
        level=logging.DEBUG)
    sys.exit(main())