Skip to content

Commit e4321f8

Browse files
committed
issue #600: /etc/environment may be non-ASCII in an unknown encoding
1 parent e87e41e commit e4321f8

File tree

3 files changed

+111
-19
lines changed

3 files changed

+111
-19
lines changed

ansible_mitogen/runner.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
import ansible_mitogen.target # TODO: circular import
5353
from mitogen.core import b
5454
from mitogen.core import bytes_partition
55-
from mitogen.core import str_partition
5655
from mitogen.core import str_rpartition
5756
from mitogen.core import to_text
5857

@@ -104,12 +103,20 @@
104103
LOG = logging.getLogger(__name__)
105104

106105

107-
if mitogen.core.PY3:
108-
shlex_split = shlex.split
109-
else:
110-
def shlex_split(s, comments=False):
111-
return [mitogen.core.to_text(token)
112-
for token in shlex.split(str(s), comments=comments)]
106+
def shlex_split_b(s):
107+
"""
108+
Use shlex.split() to split characters in some single-byte encoding, without
109+
knowing what that encoding is. The input is bytes, the output is a list of
110+
bytes.
111+
"""
112+
assert isinstance(s, mitogen.core.BytesType)
113+
if mitogen.core.PY3:
114+
return [
115+
t.encode('latin1')
116+
for t in shlex.split(s.decode('latin1'), comments=True)
117+
]
118+
119+
return [t for t in shlex.split(s, comments=True)]
113120

114121

115122
class TempFileWatcher(object):
@@ -165,13 +172,19 @@ class EnvironmentFileWatcher(object):
165172
A more robust future approach may simply be to arrange for the persistent
166173
interpreter to restart when a change is detected.
167174
"""
175+
# We know nothing about the character set of /etc/environment or the
176+
# process environment.
177+
environ = getattr(os, 'environb', os.environ)
178+
168179
def __init__(self, path):
169180
self.path = os.path.expanduser(path)
170181
#: Inode data at time of last check.
171182
self._st = self._stat()
172183
#: List of inherited keys appearing to originated from this file.
173-
self._keys = [key for key, value in self._load()
174-
if value == os.environ.get(key)]
184+
self._keys = [
185+
key for key, value in self._load()
186+
if value == self.environ.get(key)
187+
]
175188
LOG.debug('%r installed; existing keys: %r', self, self._keys)
176189

177190
def __repr__(self):
@@ -185,7 +198,7 @@ def _stat(self):
185198

186199
def _load(self):
187200
try:
188-
fp = codecs.open(self.path, 'r', encoding='utf-8')
201+
fp = open(self.path, 'rb')
189202
try:
190203
return list(self._parse(fp))
191204
finally:
@@ -199,36 +212,36 @@ def _parse(self, fp):
199212
"""
200213
for line in fp:
201214
# ' #export foo=some var ' -> ['#export', 'foo=some var ']
202-
bits = shlex_split(line, comments=True)
203-
if (not bits) or bits[0].startswith('#'):
215+
bits = shlex_split_b(line)
216+
if (not bits) or bits[0].startswith(b('#')):
204217
continue
205218

206-
if bits[0] == u'export':
219+
if bits[0] == b('export'):
207220
bits.pop(0)
208221

209-
key, sep, value = str_partition(u' '.join(bits), u'=')
222+
key, sep, value = bytes_partition(b(' ').join(bits), b('='))
210223
if key and sep:
211224
yield key, value
212225

213226
def _on_file_changed(self):
214227
LOG.debug('%r: file changed, reloading', self)
215228
for key, value in self._load():
216-
if key in os.environ:
229+
if key in self.environ:
217230
LOG.debug('%r: existing key %r=%r exists, not setting %r',
218-
self, key, os.environ[key], value)
231+
self, key, self.environ[key], value)
219232
else:
220233
LOG.debug('%r: setting key %r to %r', self, key, value)
221234
self._keys.append(key)
222-
os.environ[key] = value
235+
self.environ[key] = value
223236

224237
def _remove_existing(self):
225238
"""
226239
When a change is detected, remove keys that existed in the old file.
227240
"""
228241
for key in self._keys:
229-
if key in os.environ:
242+
if key in self.environ:
230243
LOG.debug('%r: removing old key %r', self, key)
231-
del os.environ[key]
244+
del self.environ[key]
232245
self._keys = []
233246

234247
def check(self):
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
import sys
3+
import tempfile
4+
5+
import mock
6+
import unittest2
7+
import testlib
8+
9+
from mitogen.core import b
10+
import ansible_mitogen.runner
11+
12+
13+
klass = ansible_mitogen.runner.EnvironmentFileWatcher
14+
environb = getattr(os, 'environb', os.environ)
15+
16+
17+
class WatcherTest(testlib.TestCase):
18+
def setUp(self):
19+
self.original_env = environb.copy()
20+
self.tf = tempfile.NamedTemporaryFile()
21+
22+
def tearDown(self):
23+
self.tf.close()
24+
environb.clear()
25+
environb.update(self.original_env)
26+
27+
def test_missing_file(self):
28+
# just ensure it doesn't crash
29+
watcher = klass('/nonexistent')
30+
watcher.check()
31+
32+
def test_file_becomes_missing(self):
33+
# just ensure it doesn't crash
34+
watcher = klass(self.tf.name)
35+
watcher.check()
36+
os.unlink(self.tf.name)
37+
watcher.check()
38+
open(self.tf.name,'wb').close()
39+
40+
def test_key_deleted(self):
41+
environb[b('SOMEKEY')] = b('123')
42+
self.tf.write(b('SOMEKEY=123\n'))
43+
self.tf.flush()
44+
watcher = klass(self.tf.name)
45+
self.tf.seek(0)
46+
self.tf.truncate(0)
47+
watcher.check()
48+
self.assertTrue(b('SOMEKEY') not in environb)
49+
50+
def test_key_added(self):
51+
watcher = klass(self.tf.name)
52+
self.tf.write(b('SOMEKEY=123\n'))
53+
self.tf.flush()
54+
watcher.check()
55+
self.assertEqual(environb[b('SOMEKEY')], b('123'))
56+
57+
def test_key_shadowed_nuchange(self):
58+
environb[b('SOMEKEY')] = b('234')
59+
self.tf.write(b('SOMEKEY=123\n'))
60+
self.tf.flush()
61+
watcher = klass(self.tf.name)
62+
watcher.check()
63+
self.assertEqual(environb[b('SOMEKEY')], b('234'))
64+
65+
def test_binary_key_added(self):
66+
watcher = klass(self.tf.name)
67+
self.tf.write(b('SOMEKEY=\xff\xff\xff\n'))
68+
self.tf.flush()
69+
watcher.check()
70+
self.assertEqual(environb[b('SOMEKEY')], b('\xff\xff\xff'))
71+
72+
73+
if __name__ == '__main__':
74+
unittest2.main()

tests/image_prep/_container_setup.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
shell: locale-gen
9292
when: distro == "Debian"
9393

94+
- name: Write Unicode into /etc/environment
95+
copy:
96+
dest: /etc/environment
97+
content: "UNICODE_SNOWMAN=\u2603\n"
98+
9499
- name: Install prebuilt 'doas' binary
95100
unarchive:
96101
dest: /

0 commit comments

Comments
 (0)