Gentoo Archives: gentoo-portage-dev

From: Zac Medico <zmedico@g.o>
To: gentoo-portage-dev@l.g.o
Cc: Zac Medico <zmedico@g.o>
Subject: [gentoo-portage-dev] [PATCH] binarytree.move_ent: copy on write for package move
Date: Tue, 19 Jan 2021 10:32:19
Message-Id: 20210119103159.1755373-1-zmedico@gentoo.org
1 Copy on write when applying package moves, and silently
2 skip package moves when the same move has already been
3 applied to the same build of the package. Since the old
4 package instance is preserved, it avoids the problem
5 of having enries for deleted packages remain in the
6 package index. We can simply assume that the package
7 will be deleted by eclean-pkg when its time comes.
8
9 Bug: https://bugs.gentoo.org/766012
10 Signed-off-by: Zac Medico <zmedico@g.o>
11 ---
12 lib/portage/dbapi/bintree.py | 40 ++++++++++++++++-------
13 lib/portage/emaint/modules/move/move.py | 13 ++++++--
14 lib/portage/tests/update/test_move_ent.py | 7 ++--
15 3 files changed, 44 insertions(+), 16 deletions(-)
16
17 diff --git a/lib/portage/dbapi/bintree.py b/lib/portage/dbapi/bintree.py
18 index 180e48c3b..76fca5523 100644
19 --- a/lib/portage/dbapi/bintree.py
20 +++ b/lib/portage/dbapi/bintree.py
21 @@ -31,6 +31,7 @@ from portage.exception import AlarmSignal, InvalidPackageName, \
22 ParseError, PortageException
23 from portage.localization import _
24 from portage.package.ebuild.profile_iuse import iter_iuse_vars
25 +from portage.util.file_copy import copyfile
26 from portage.util.futures import asyncio
27 from portage.util.futures.compat_coroutine import coroutine
28 from portage.util.futures.executor.fork import ForkExecutor
29 @@ -483,6 +484,17 @@ class binarytree:
30 myoldpkg = catsplit(mycpv)[1]
31 mynewpkg = catsplit(mynewcpv)[1]
32
33 + # If this update has already been applied to the same
34 + # package build then silently continue.
35 + applied = False
36 + for maybe_applied in self.dbapi.match('={}'.format(mynewcpv)):
37 + if maybe_applied.build_time == mycpv.build_time:
38 + applied = True
39 + break
40 +
41 + if applied:
42 + continue
43 +
44 if (mynewpkg != myoldpkg) and self.dbapi.cpv_exists(mynewcpv):
45 writemsg(_("!!! Cannot update binary: Destination exists.\n"),
46 noiselevel=-1)
47 @@ -513,24 +525,30 @@ class binarytree:
48 mydata[_unicode_encode(mynewpkg + '.ebuild',
49 encoding=_encodings['repo.content'])] = ebuild_data
50
51 - mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
52 -
53 - self.dbapi.cpv_remove(mycpv)
54 - del self._pkg_paths[self.dbapi._instance_key(mycpv)]
55 metadata = self.dbapi._aux_cache_slot_dict()
56 for k in self.dbapi._aux_cache_keys:
57 v = mydata.get(_unicode_encode(k))
58 if v is not None:
59 v = _unicode_decode(v)
60 metadata[k] = " ".join(v.split())
61 +
62 + # Create a copy of the old version of the package and
63 + # apply the update to it. Leave behind the old version,
64 + # assuming that it will be deleted by eclean-pkg when its
65 + # time comes.
66 mynewcpv = _pkg_str(mynewcpv, metadata=metadata, db=self.dbapi)
67 - new_path = self.getname(mynewcpv)
68 - self._pkg_paths[
69 - self.dbapi._instance_key(mynewcpv)] = new_path[len(self.pkgdir)+1:]
70 - if new_path != tbz2path:
71 - self._ensure_dir(os.path.dirname(new_path))
72 - _movefile(tbz2path, new_path, mysettings=self.settings)
73 - self.inject(mynewcpv)
74 + update_path = self.getname(mynewcpv, allocate_new=True) + ".partial"
75 + self._ensure_dir(os.path.dirname(update_path))
76 + update_path_lock = None
77 + try:
78 + update_path_lock = lockfile(update_path, wantnewlockfile=True)
79 + copyfile(tbz2path, update_path)
80 + mytbz2 = portage.xpak.tbz2(update_path)
81 + mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
82 + self.inject(mynewcpv, filename=update_path)
83 + finally:
84 + if update_path_lock is not None:
85 + unlockfile(update_path_lock)
86
87 return moves
88
89 diff --git a/lib/portage/emaint/modules/move/move.py b/lib/portage/emaint/modules/move/move.py
90 index 8fc3269ca..2a95e99c4 100644
91 --- a/lib/portage/emaint/modules/move/move.py
92 +++ b/lib/portage/emaint/modules/move/move.py
93 @@ -1,4 +1,4 @@
94 -# Copyright 2005-2020 Gentoo Authors
95 +# Copyright 2005-2021 Gentoo Authors
96 # Distributed under the terms of the GNU General Public License v2
97
98 from _emerge.Package import Package
99 @@ -76,7 +76,16 @@ class MoveHandler:
100 except (KeyError, InvalidData):
101 continue
102 if repo_match(cpv.repo):
103 - errors.append("'%s' moved to '%s'" % (cpv, newcp))
104 + build_time = getattr(cpv, 'build_time', None)
105 + if build_time is not None:
106 + # If this update has already been applied to the same
107 + # package build then silently continue.
108 + for maybe_applied in match('={}'.format(
109 + cpv.replace(cpv.cp, str(newcp), 1))):
110 + if maybe_applied.build_time == build_time:
111 + break
112 + else:
113 + errors.append("'%s' moved to '%s'" % (cpv, newcp))
114 elif update_cmd[0] == "slotmove":
115 pkg, origslot, newslot = update_cmd[1:]
116 atom = pkg.with_slot(origslot)
117 diff --git a/lib/portage/tests/update/test_move_ent.py b/lib/portage/tests/update/test_move_ent.py
118 index d9647a95e..d9036db0a 100644
119 --- a/lib/portage/tests/update/test_move_ent.py
120 +++ b/lib/portage/tests/update/test_move_ent.py
121 @@ -1,4 +1,4 @@
122 -# Copyright 2012-2013 Gentoo Foundation
123 +# Copyright 2012-2021 Gentoo Authors
124 # Distributed under the terms of the GNU General Public License v2
125
126 import textwrap
127 @@ -93,8 +93,9 @@ class MoveEntTestCase(TestCase):
128 self.assertRaises(KeyError,
129 vardb.aux_get, "dev-libs/A-1", ["EAPI"])
130 vardb.aux_get("dev-libs/A-moved-1", ["EAPI"])
131 - self.assertRaises(KeyError,
132 - bindb.aux_get, "dev-libs/A-1", ["EAPI"])
133 + # The original package should still exist because a binary
134 + # package move is a copy on write operation.
135 + bindb.aux_get("dev-libs/A-1", ["EAPI"])
136 bindb.aux_get("dev-libs/A-moved-1", ["EAPI"])
137
138 # dont_apply_updates
139 --
140 2.26.2