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)
|