Merge ~mwhudson/curtin:rprivate-more into curtin:master

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: 18bbbd0afda42d25a033f4c3b51faa162d688e10
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~mwhudson/curtin:rprivate-more
Merge into: curtin:master
Diff against target: 172 lines (+38/-36)
3 files modified
curtin/util.py (+29/-20)
tests/unittests/test_curthooks.py (+8/-15)
tests/unittests/test_pack.py (+1/-1)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Dan Bungert Approve
Review via email: mp+406418@code.launchpad.net

Commit message

move making mounts recursively private into do_unmount

This means that running "curtin unmount -t /target" gets the robustness
improvments that were recently added to ChrootableTarget.

Description of the change

a tweak to my recent change.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
~mwhudson/curtin:rprivate-more updated
d9b3a59... by Michael Hudson-Doyle

adapt to deal with the fact that do_umount is called with things that are not mountpoints

Revision history for this message
Dan Bungert (dbungert) :
review: Approve
~mwhudson/curtin:rprivate-more updated
18bbbd0... by Michael Hudson-Doyle

update comment

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/util.py b/curtin/util.py
2index d8f9c47..ec1d4f9 100644
3--- a/curtin/util.py
4+++ b/curtin/util.py
5@@ -519,13 +519,41 @@ def do_mount(src, target, opts=None):
6
7
8 def do_umount(mountpoint, recursive=False):
9+ mp = os.path.abspath(mountpoint)
10 # unmount mountpoint. if recursive, unmount all mounts under it.
11+ #
12+ # note that recursive unmounting is different to umount -R's
13+ # behaviour. Consider the following sequence:
14+ #
15+ # mkdir a a/b c
16+ # mount --bind a c
17+ # mount -t sysfs sysfs a/b
18+ #
19+ # "umount c" will fail, with "mountpoint is busy", because of the
20+ # mountpoint at c/b. But "umount -R c" will unmount a/b (and as in
21+ # pratice "a/b" is often actually "/sys" itself, this would be
22+ # Bad(tm)). So what we do is iterate over the mountpoints under
23+ # `mountpoint` and mark each one as "private" before unmounting
24+ # it, which means the unmount does not propagate to any mount tree
25+ # it was cloned from (See "Shared subtree operations" in mount(8)
26+ # for more on this).
27+ #
28+ # You might think we could replace all this with a call to "mount
29+ # --make-rprivate" followed by a call to "umount --recursive" but
30+ # that would fail in the case where `mountpoint` is not actually a
31+ # mount point, and not doing that is actually relied on by other
32+ # parts of curtin.
33+ #
34+ # Related bug reports:
35+ # https://bugs.launchpad.net/maas/+bug/1928839
36+ # https://bugs.launchpad.net/subiquity/+bug/1934775
37+ #
38 # return boolean indicating if mountpoint was previously mounted.
39- mp = os.path.abspath(mountpoint)
40 ret = False
41 for line in reversed(load_file("/proc/mounts", decode=True).splitlines()):
42 curmp = line.split()[1]
43 if curmp == mp or (recursive and curmp.startswith(mp + os.path.sep)):
44+ subp(['mount', '--make-private', curmp])
45 subp(['umount', curmp])
46 if curmp == mp:
47 ret = True
48@@ -695,25 +723,6 @@ class ChrootableTarget(object):
49 log_call(subp, ['udevadm', 'settle'])
50
51 for p in reversed(self.umounts):
52- # Consider the following sequence:
53- #
54- # mkdir a a/b c
55- # mount --bind a c
56- # mount -t sysfs sysfs a/b
57- # umount c
58- #
59- # This umount fails with "mountpoint is busy", because of
60- # the mountpoint at c/b. But if we unmount c recursively,
61- # a/b ends up getting unmounted. What we need to do is to
62- # make the mountpoints c and c/b "private" so that
63- # unmounting them does not propagate to the mount tree c
64- # was cloned from (See "Shared subtree operations" in
65- # mount(8) for more on this).
66- #
67- # Related bug reports:
68- # https://bugs.launchpad.net/maas/+bug/1928839
69- # https://bugs.launchpad.net/subiquity/+bug/1934775
70- subp(['mount', '--make-rprivate', p])
71 do_umount(p, recursive=True)
72
73 rconf = paths.target_path(self.target, "/etc/resolv.conf")
74diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
75index 1e65175..f878d9f 100644
76--- a/tests/unittests/test_curthooks.py
77+++ b/tests/unittests/test_curthooks.py
78@@ -109,7 +109,7 @@ class TestEnableDisableUpdateInitramfs(CiTestCase):
79
80 def test_disable_changes_binary_name_write_dummy_binary(self):
81 self.mock_which.return_value = self.update_initramfs
82- self.mock_subp.side_effect = iter([('', '')] * 15)
83+ self.mock_subp.side_effect = iter([('', '')] * 10)
84 curthooks.disable_update_initramfs({}, self.target)
85 self.assertIn(
86 call(['dpkg-divert', '--add', '--rename', '--divert',
87@@ -215,25 +215,18 @@ class TestUpdateInitramfs(CiTestCase):
88 target = os.path.join(self.target, point)
89 return call(['mount', '--bind', '/%s' % point, target])
90
91- def _rpriv_call(self, point):
92- target = os.path.join(self.target, point)
93- return call(['mount', '--make-rprivate', target])
94-
95 def _side_eff(self, cmd_out=None, cmd_err=None):
96 if cmd_out is None:
97 cmd_out = ''
98 if cmd_err is None:
99 cmd_err = ''
100 effects = ([('mount', '')] * len(self.mounts) +
101- [(cmd_out, cmd_err)] +
102- [('settle', '')] +
103- [('mount', '')]*len(self.mounts))
104+ [(cmd_out, cmd_err)] + [('settle', '')])
105 return effects
106
107 def _subp_calls(self, mycall):
108 pre = [self._mnt_call(point) for point in self.mounts]
109- post = [call(['udevadm', 'settle'])] + [
110- self._rpriv_call(point) for point in reversed(self.mounts)]
111+ post = [call(['udevadm', 'settle'])]
112 return pre + [mycall] + post
113
114 def test_does_nothing_if_binary_diverted(self):
115@@ -250,7 +243,7 @@ class TestUpdateInitramfs(CiTestCase):
116 target=self.target)
117 calls = self._subp_calls(dcall)
118 self.mock_subp.assert_has_calls(calls)
119- self.assertEqual(10, self.mock_subp.call_count)
120+ self.assertEqual(6, self.mock_subp.call_count)
121
122 def test_mounts_and_runs(self):
123 # in_chroot calls to dpkg-divert, update-initramfs
124@@ -263,7 +256,7 @@ class TestUpdateInitramfs(CiTestCase):
125 call(['update-initramfs', '-c', '-k', self.kversion],
126 target=self.target))
127 self.mock_subp.assert_has_calls(subp_calls)
128- self.assertEqual(20, self.mock_subp.call_count)
129+ self.assertEqual(12, self.mock_subp.call_count)
130
131 def test_mounts_and_runs_for_all_kernels(self):
132 kversion2 = '5.4.0-generic'
133@@ -287,7 +280,7 @@ class TestUpdateInitramfs(CiTestCase):
134 call(['update-initramfs', '-c', '-k', kversion2],
135 target=self.target))
136 self.mock_subp.assert_has_calls(subp_calls)
137- self.assertEqual(40, self.mock_subp.call_count)
138+ self.assertEqual(24, self.mock_subp.call_count)
139
140 def test_calls_update_if_initrd_exists_else_create(self):
141 kversion2 = '5.2.0-generic'
142@@ -297,7 +290,7 @@ class TestUpdateInitramfs(CiTestCase):
143 with open(os.path.join(self.boot, 'initrd.img-' + kversion2), 'w'):
144 pass
145
146- effects = self._side_eff() * 4
147+ effects = self._side_eff() * 3
148 self.mock_subp.side_effect = iter(effects)
149 curthooks.update_initramfs(self.target, True)
150 subp_calls = self._subp_calls(
151@@ -309,7 +302,7 @@ class TestUpdateInitramfs(CiTestCase):
152 call(['update-initramfs', '-c', '-k', self.kversion],
153 target=self.target))
154 self.mock_subp.assert_has_calls(subp_calls)
155- self.assertEqual(30, self.mock_subp.call_count)
156+ self.assertEqual(18, self.mock_subp.call_count)
157
158
159 class TestSetupKernelImgConf(CiTestCase):
160diff --git a/tests/unittests/test_pack.py b/tests/unittests/test_pack.py
161index cb0b135..803d7de 100644
162--- a/tests/unittests/test_pack.py
163+++ b/tests/unittests/test_pack.py
164@@ -87,7 +87,7 @@ class TestPack(TestCase):
165
166 return out, err, rc, log_contents
167
168- def test_psuedo_install(self):
169+ def test_pseudo_install(self):
170 # do a install that has only a early stage and only one command.
171 mystr = "HI MOM"
172 cfg = {

Subscribers

People subscribed via source and target branches