Skip to content

Commit c62b41f

Browse files
committed
expose https://github.com/fbenkstein and fix name() / cmdline() encoding errors on linux / py3
1 parent 0aa753f commit c62b41f

6 files changed

Lines changed: 67 additions & 23 deletions

File tree

CREDITS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,7 @@ I: 688
354354
N: Syohei YOSHIDA
355355
W: https://github.com/syohex
356356
I: 730
357+
358+
N: Frank Benkstein
359+
W: https://github.com/fbenkstein
360+
I: 732, 733

HISTORY.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
Bug tracker at https://github.com/giampaolo/psutil/issues
22

3+
3.5.0 - XXXX-XX-XX
4+
==================
5+
6+
**Enhancements**
7+
8+
- #733: exposed a new ENCODING_ERRORS_HANDLER constant for dealing with
9+
encoding errors on Python 3.
10+
11+
12+
**Bug fixes**
13+
14+
- #733: [Linux] process name() and exe() can fail on Python 3 if string
15+
contains non-UTF8 charaters. (patch by Frank Benkstein)
16+
317

418
3.4.2 - 2016-01-20
519
==================

docs/index.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,7 @@ Popen class
12841284
Constants
12851285
=========
12861286

1287-
.. _const-pstatus:
1287+
.. _const-procfs_path:
12881288
.. data:: PROCFS_PATH
12891289

12901290
The path of the /proc filesystem on Linux and Solaris (defaults to "/proc").
@@ -1296,6 +1296,18 @@ Constants
12961296
.. versionadded:: 3.2.3
12971297
.. versionchanged:: 3.4.2 also available on Solaris.
12981298

1299+
.. _const-encoding_errors_handler:
1300+
.. data:: ENCODING_ERRORS_HANDLER
1301+
1302+
Dictates how to handle encoding and decoding errors (for instance when
1303+
reading files in /proc via `open <https://docs.python.org/3/library/functions.html#open>`__).
1304+
This is only used in Python 3 (Python 2 ignores this constant).
1305+
By default this is set to `'surrogateescape'`. See
1306+
`here <https://docs.python.org/3/library/codecs.html#error-handlers>`__ for
1307+
a complete list of available error handlers.
1308+
1309+
.. versionadded:: 3.5.0
1310+
12991311
.. _const-pstatus:
13001312
.. data:: STATUS_RUNNING
13011313
STATUS_SLEEPING

psutil/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@
141141
raise NotImplementedError('platform %s is not supported' % sys.platform)
142142

143143

144+
# Dictates how to handle encoding and decoding errors (with open())
145+
# on Python 3. This is public API and it will be retrieved from _ps*.py
146+
# modules via sys.modules.
147+
ENCODING_ERRORS_HANDLER = 'surrogateescape'
148+
149+
144150
__all__ = [
145151
# exceptions
146152
"Error", "NoSuchProcess", "ZombieProcess", "AccessDenied",
@@ -155,6 +161,7 @@
155161
"CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", "CONN_NONE",
156162
"AF_LINK",
157163
"NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN",
164+
"ENCODING_ERRORS_HANDLER",
158165
# classes
159166
"Process", "Popen",
160167
# functions
@@ -168,7 +175,7 @@
168175
]
169176
__all__.extend(_psplatform.__extra__all__)
170177
__author__ = "Giampaolo Rodola'"
171-
__version__ = "3.4.2"
178+
__version__ = "3.5.0"
172179
version_info = tuple([int(num) for num in __version__.split('.')])
173180
AF_LINK = _psplatform.AF_LINK
174181
_TOTAL_PHYMEM = None

psutil/_pslinux.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,27 @@ def open_binary(fname, **kwargs):
139139

140140

141141
def open_text(fname, **kwargs):
142-
"""On Python 3 opens a file in text mode by using fs encoding.
142+
"""On Python 3 opens a file in text mode by using fs encoding and
143+
a proper en/decoding errors handler.
143144
On Python 2 this is just an alias for open(name, 'rt').
144145
"""
145-
if PY3 and 'encoding' not in kwargs:
146-
kwargs['encoding'] = FS_ENCODING
146+
if PY3:
147+
# See:
148+
# https://github.com/giampaolo/psutil/issues/675
149+
# https://github.com/giampaolo/psutil/pull/733
150+
kwargs.setdefault('encoding', FS_ENCODING)
151+
kwargs.setdefault('errors', get_encoding_errors_handler())
147152
return open(fname, "rt", **kwargs)
148153

149154

150155
def get_procfs_path():
151156
return sys.modules['psutil'].PROCFS_PATH
152157

153158

159+
def get_encoding_errors_handler():
160+
return sys.modules['psutil'].ENCODING_ERRORS_HANDLER
161+
162+
154163
def readlink(path):
155164
"""Wrapper around os.readlink()."""
156165
assert isinstance(path, basestring), path
@@ -600,9 +609,7 @@ def process_inet(self, file, family, type_, inodes, filter_pid=None):
600609

601610
def process_unix(self, file, family, inodes, filter_pid=None):
602611
"""Parse /proc/net/unix files."""
603-
# see: https://github.com/giampaolo/psutil/issues/675
604-
kw = dict(errors='replace') if PY3 else dict()
605-
with open_text(file, buffering=BIGGER_FILE_BUFFERING, **kw) as f:
612+
with open_text(file, buffering=BIGGER_FILE_BUFFERING) as f:
606613
f.readline() # skip the first line
607614
for line in f:
608615
tokens = line.split()

test/test_psutil.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import collections
2121
import contextlib
2222
import datetime
23-
import distutils.spawn
2423
import errno
2524
import functools
2625
import json
@@ -543,8 +542,8 @@ def get_winver():
543542
return (wv[0], wv[1], sp)
544543

545544

546-
# In Python 3 paths are unicode objects by default. Surrogate escapes are used
547-
# to handle non-character data.
545+
# In Python 3 paths are unicode objects by default. Surrogate escapes
546+
# are used to handle non-character data.
548547
def encode_path(path):
549548
if PY3:
550549
return path.encode(sys.getfilesystemencoding(),
@@ -3253,18 +3252,21 @@ def test_proc_open_files(self):
32533252

32543253

32553254
class TestNonUnicode(unittest.TestCase):
3256-
"Test handling of non-utf8 data."
3255+
"""Test handling of non-utf8 data."""
32573256

32583257
@classmethod
32593258
def setUpClass(cls):
3260-
cls.temp_directory = tempfile.mkdtemp(suffix=b"")
3259+
if PY3:
3260+
# Fix around https://bugs.python.org/issue24230
3261+
cls.temp_directory = tempfile.mkdtemp().encode('utf8')
3262+
else:
3263+
cls.temp_directory = tempfile.mkdtemp(suffix=b"")
32613264

3262-
# Return an executable that runs until we close its stdin
3265+
# Return an executable that runs until we close its stdin.
32633266
if WINDOWS:
3264-
cls.test_executable = distutils.spawn.find_executable("cmd.exe")
3267+
cls.test_executable = which("cmd.exe")
32653268
else:
3266-
assert POSIX
3267-
cls.test_executable = "/bin/cat"
3269+
cls.test_executable = which("cat")
32683270

32693271
@classmethod
32703272
def tearDownClass(cls):
@@ -3310,8 +3312,6 @@ def test_proc_cmdline(self):
33103312
cmd = [self.test_executable]
33113313
if WINDOWS:
33123314
cmd.extend(["/K", "type \xc0\x80"])
3313-
else:
3314-
cmd.extend([b"\xc0\x80", b"-"])
33153315
subp = get_test_subprocess(cmd=cmd,
33163316
stdin=subprocess.PIPE,
33173317
stdout=subprocess.PIPE,
@@ -3342,10 +3342,10 @@ def test_proc_open_files(self):
33423342
test_script = os.path.join(self.temp_directory, b"test.py")
33433343
with open(test_script, "wt") as f:
33443344
f.write(textwrap.dedent(r"""
3345-
import sys, os
3346-
with open(%r, "wb") as f1, open(__file__, "rb") as f2:
3347-
sys.stdin.read()
3348-
""" % funny_file))
3345+
import sys
3346+
with open(%r, "wb") as f1, open(__file__, "rb") as f2:
3347+
sys.stdin.read()
3348+
""" % funny_file))
33493349
self.addCleanup(safe_remove, test_script)
33503350
subp = get_test_subprocess(cmd=[PYTHON, decode_path(test_script)],
33513351
stdin=subprocess.PIPE,

0 commit comments

Comments
 (0)