diff --git a/HISTORY.rst b/HISTORY.rst index 430fb5e4f..bfaa2568c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -17,6 +17,9 @@ - 1169_: [Linux] users() "hostname" returns username instead. (patch by janderbrain) - 1172_: [Windows] `make test` does not work. +- 1179_: [Linux] Process.cmdline() correctly splits cmdline args for + misbehaving processes who overwrite /proc/pid/cmdline by using spaces + instead of null bytes as args separator. - 1181_: [OSX] Process.memory_maps() may raise ENOENT. 5.4.1 diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 3fe62c5c4..a5a3fd891 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -1471,9 +1471,17 @@ def cmdline(self): if not data: # may happen in case of zombie process return [] - if data.endswith('\x00'): + # 'man proc' states that args are separated by null bytes '\0' + # and last char is supposed to be a null byte. Nevertheless + # some processes may change their cmdline after being started + # (via setproctitle() or similar), they are usually not + # compliant with this rule and use spaces instead. Google + # Chrome process is an example. See: + # https://github.com/giampaolo/psutil/issues/1179 + sep = '\x00' if data.endswith('\x00') else ' ' + if data.endswith(sep): data = data[:-1] - return [x for x in data.split('\x00')] + return [x for x in data.split(sep)] @wrap_exceptions def environ(self): diff --git a/psutil/tests/test_linux.py b/psutil/tests/test_linux.py index 71d428c31..6ba17b254 100755 --- a/psutil/tests/test_linux.py +++ b/psutil/tests/test_linux.py @@ -1585,6 +1585,20 @@ def test_cmdline_mocked(self): self.assertEqual(p.cmdline(), ['foo', 'bar', '']) assert m.called + def test_cmdline_spaces_mocked(self): + # see: https://github.com/giampaolo/psutil/issues/1179 + p = psutil.Process() + fake_file = io.StringIO(u('foo bar ')) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + self.assertEqual(p.cmdline(), ['foo', 'bar']) + assert m.called + fake_file = io.StringIO(u('foo bar ')) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + self.assertEqual(p.cmdline(), ['foo', 'bar', '']) + assert m.called + def test_readlink_path_deleted_mocked(self): with mock.patch('psutil._pslinux.os.readlink', return_value='/home/foo (deleted)'):