Commit a01b500b authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

utils: Add function to check whether process handles a signal



This will be used to avoid a race condition between starting a program (dd
for import/export) and sending signals to it.
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent 424f51ec
......@@ -844,6 +844,17 @@ def ForceDictType(target, key_types, allowed_values=None):
raise errors.TypeEnforcementError(msg)
def _GetProcStatusPath(pid):
"""Returns the path for a PID's proc status file.
@type pid: int
@param pid: Process ID
@rtype: string
"""
return "/proc/%d/status" % pid
def IsProcessAlive(pid):
"""Check if a given pid exists on the system.
......@@ -870,15 +881,99 @@ def IsProcessAlive(pid):
if pid <= 0:
return False
proc_entry = "/proc/%d/status" % pid
# /proc in a multiprocessor environment can have strange behaviors.
# Retry the os.stat a few times until we get a good result.
try:
return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5, args=[proc_entry])
return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
args=[_GetProcStatusPath(pid)])
except RetryTimeout, err:
err.RaiseInner()
def _ParseSigsetT(sigset):
"""Parse a rendered sigset_t value.
This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
function.
@type sigset: string
@param sigset: Rendered signal set from /proc/$pid/status
@rtype: set
@return: Set of all enabled signal numbers
"""
result = set()
signum = 0
for ch in reversed(sigset):
chv = int(ch, 16)
# The following could be done in a loop, but it's easier to read and
# understand in the unrolled form
if chv & 1:
result.add(signum + 1)
if chv & 2:
result.add(signum + 2)
if chv & 4:
result.add(signum + 3)
if chv & 8:
result.add(signum + 4)
signum += 4
return result
def _GetProcStatusField(pstatus, field):
"""Retrieves a field from the contents of a proc status file.
@type pstatus: string
@param pstatus: Contents of /proc/$pid/status
@type field: string
@param field: Name of field whose value should be returned
@rtype: string
"""
for line in pstatus.splitlines():
parts = line.split(":", 1)
if len(parts) < 2 or parts[0] != field:
continue
return parts[1].strip()
return None
def IsProcessHandlingSignal(pid, signum, status_path=None):
"""Checks whether a process is handling a signal.
@type pid: int
@param pid: Process ID
@type signum: int
@param signum: Signal number
@rtype: bool
"""
if status_path is None:
status_path = _GetProcStatusPath(pid)
try:
proc_status = ReadFile(status_path)
except EnvironmentError, err:
# In at least one case, reading /proc/$pid/status failed with ESRCH.
if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
return False
raise
sigcgt = _GetProcStatusField(proc_status, "SigCgt")
if sigcgt is None:
raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
# Now check whether signal is handled
return signum in _ParseSigsetT(sigcgt)
def ReadPidFile(pidfile):
"""Read a pid from a file.
......
......@@ -79,6 +79,104 @@ class TestIsProcessAlive(unittest.TestCase):
"nonexisting process detected")
class TestGetProcStatusPath(unittest.TestCase):
def test(self):
self.assert_("/1234/" in utils._GetProcStatusPath(1234))
self.assertNotEqual(utils._GetProcStatusPath(1),
utils._GetProcStatusPath(2))
class TestIsProcessHandlingSignal(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.tmpdir)
def testParseSigsetT(self):
self.assertEqual(len(utils._ParseSigsetT("0")), 0)
self.assertEqual(utils._ParseSigsetT("1"), set([1]))
self.assertEqual(utils._ParseSigsetT("1000a"), set([2, 4, 17]))
self.assertEqual(utils._ParseSigsetT("810002"), set([2, 17, 24, ]))
self.assertEqual(utils._ParseSigsetT("0000000180000202"),
set([2, 10, 32, 33]))
self.assertEqual(utils._ParseSigsetT("0000000180000002"),
set([2, 32, 33]))
self.assertEqual(utils._ParseSigsetT("0000000188000002"),
set([2, 28, 32, 33]))
self.assertEqual(utils._ParseSigsetT("000000004b813efb"),
set([1, 2, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 17,
24, 25, 26, 28, 31]))
self.assertEqual(utils._ParseSigsetT("ffffff"), set(range(1, 25)))
def testGetProcStatusField(self):
for field in ["SigCgt", "Name", "FDSize"]:
for value in ["", "0", "cat", " 1234 KB"]:
pstatus = "\n".join([
"VmPeak: 999 kB",
"%s: %s" % (field, value),
"TracerPid: 0",
])
result = utils._GetProcStatusField(pstatus, field)
self.assertEqual(result, value.strip())
def test(self):
sp = utils.PathJoin(self.tmpdir, "status")
utils.WriteFile(sp, data="\n".join([
"Name: bash",
"State: S (sleeping)",
"SleepAVG: 98%",
"Pid: 22250",
"PPid: 10858",
"TracerPid: 0",
"SigBlk: 0000000000010000",
"SigIgn: 0000000000384004",
"SigCgt: 000000004b813efb",
"CapEff: 0000000000000000",
]))
self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
def testNoSigCgt(self):
sp = utils.PathJoin(self.tmpdir, "status")
utils.WriteFile(sp, data="\n".join([
"Name: bash",
]))
self.assertRaises(RuntimeError, utils.IsProcessHandlingSignal,
1234, 10, status_path=sp)
def testNoSuchFile(self):
sp = utils.PathJoin(self.tmpdir, "notexist")
self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
@staticmethod
def _TestRealProcess():
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
raise Exception("SIGUSR1 is handled when it should not be")
signal.signal(signal.SIGUSR1, lambda signum, frame: None)
if not utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
raise Exception("SIGUSR1 is not handled when it should be")
signal.signal(signal.SIGUSR1, signal.SIG_IGN)
if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
raise Exception("SIGUSR1 is not handled when it should be")
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
if utils.IsProcessHandlingSignal(os.getpid(), signal.SIGUSR1):
raise Exception("SIGUSR1 is handled when it should not be")
return True
def testRealProcess(self):
self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
class TestPidFileFunctions(unittest.TestCase):
"""Tests for WritePidFile, RemovePidFile and ReadPidFile"""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment