Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/pynslcd/invalidator.py
blob: 6d2eefe24e038a1982ffbcbb00429a3f99b59d43 (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

# invalidator.py - functions for invalidating external caches
#
# Copyright (C) 2013-2019 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA

import fcntl
import logging
import os
import subprocess

import cfg


# the file descriptor used for sending messages to the child process
signalfd = None


# mapping between map name and signal character
_db_to_char = dict(
    aliases='A', ethers='E', group='G', hosts='H', netgroup='U',
    networks='N', passwd='P', protocols='L', rpc='R', services='V',
    shadow='S', nfsidmap='F',
)
_char_to_db = dict((reversed(item) for item in _db_to_char.items()))


def exec_invalidate(*args):
    cmd = ' '.join(args)
    logging.debug('invalidator: %s', cmd)
    try:
        p = subprocess.Popen(args, bufsize=4096, close_fds=True,
                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output, ignored = p.communicate()
        if output:
            output = ': %s' % output[:1024].strip()
        if p.returncode == 0:
            logging.debug('invalidator: %s (pid %d) success%s',
                          cmd, p.pid, output)
        elif p.returncode > 0:
            logging.debug('invalidator: %s (pid %d) failed (%d)%s',
                          cmd, p.pid, p.returncode, output)
        else:  # p.returncode < 0
            logging.error('invalidator: %s (pid %d) killed by signal %d%s',
                          cmd, p.pid, -p.returncode, output)
    except Exception:
        logging.warn('invalidator: %s failed', cmd, exc_info=True)


def loop(fd):
    # set process title
    try:
        import setproctitle
        setproctitle.setproctitle('(invalidator)')
    except ImportError:
        pass
    # set up clean environment
    os.chdir('/')
    os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
    while True:
        db = os.read(fd, 1).decode('ascii')
        if db == '':
            break  # close process down
        db = _char_to_db.get(db, None)
        if db == 'nfsidmap':
            exec_invalidate('nfsidmap', '-c')
        elif db:
            exec_invalidate('nscd', '-i', db)


def start_invalidator():
    r, w = os.pipe()
    # mark write end as non-blocking
    flags = fcntl.fcntl(w, fcntl.F_GETFL)
    fcntl.fcntl(w, fcntl.F_SETFL, flags | os.O_NONBLOCK)
    cpid = os.fork()
    if cpid == 0:
        # we are the child
        os.close(w)
        loop(r)
        os._exit(1)
    # we are the parent
    global signalfd
    signalfd = w
    os.close(r)


def invalidate(db=None):
    if signalfd is None:
        return  # nothing to do
    if db:
        db = _db_to_char.get(db, '')
    else:
        db = ''.join(_db_to_char[x] for x in cfg.reconnect_invalidate)
    try:
        os.write(signalfd, db.encode('ascii'))
    except Exception:
        logging.warn('requesting invalidation (%s) failed', db, exc_info=True)