Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
Bug 920828 - Handle KeyError for missing binpkg when running _do_global_updates
Summary: Handle KeyError for missing binpkg when running _do_global_updates
Status: RESOLVED FIXED
Alias: None
Product: Portage Development
Classification: Unclassified
Component: Binary packages support (show other bugs)
Hardware: All Linux
: Normal normal
Assignee: Portage team
URL:
Whiteboard:
Keywords: InVCS, PullRequest
Depends on: 920827
Blocks:
  Show dependency tree
 
Reported: 2023-12-27 21:34 UTC by Sam James
Modified: 2024-02-24 23:37 UTC (History)
2 users (show)

See Also:
Package list:
Runtime testing required: ---


Attachments
test_move_ent.py modified (file_920828.txt,12.66 KB, text/plain)
2023-12-28 23:30 UTC, Sam James
Details
corrupt.patch (corrupt.patch,3.71 KB, patch)
2023-12-29 02:01 UTC, Sam James
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-27 21:34:32 UTC
See bug 920095 where we crashed on a stale entry in Packages. We should catch the error in _do_global_updates and give a nicer error in that case.

It's also possible, I suppose, that it happens because of binpkg-pkgdir-index-trusted if the PKGDIR is manually modified.
Comment 1 Zac Medico gentoo-dev 2023-12-28 20:19:05 UTC
We can make the bindbapi move_ent, move_slot_ent, and update_ents methods handle it by calling binarytree.remove to clean up the state. We can use a different exception type to distinguish this case from any other KeyError, or possibly just use "raise from" to set the __cause__ of the KeyError to the FileNotFoundError.
Comment 2 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-28 23:29:04 UTC
Thanks Zac! That sounds like a better approach.

I'll try that.
Comment 3 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-28 23:30:51 UTC
Created attachment 880873 [details]
test_move_ent.py modified

I hit something I find odd so far.

1) With the attached modified test_move_ent.py + 'pytest lib/portage/tests/update/test_move_ent.py -s -vvv -k testMoveEntWithCorruptIndex', I get:

  /home/sam/git/portage/lib/portage/dbapi/__init__.py:428: UserWarning: gpkg file structure mismatch in /tmp/tmpug3ckom4/pkgdir/dev-libs/B/B-1-1.gpkg.tar; files: ['B-1/gpkg-1', 'B-1-1/metadata.tar.zst', 'B-1/image.tar.zst', 'B-1-1/Manifest']

2) If I uncomment the os.remove line, I get a portage.exception.FileNotFound. Not yet figured out how to trigger a KeyError in the test yet.
Comment 4 Zac Medico gentoo-dev 2023-12-29 01:37:26 UTC
(In reply to Sam James from comment #3)
> Created attachment 880873 [details]
> test_move_ent.py modified
> 
> I hit something I find odd so far.
> 
> 1) With the attached modified test_move_ent.py + 'pytest
> lib/portage/tests/update/test_move_ent.py -s -vvv -k
> testMoveEntWithCorruptIndex', I get:
> 
>   /home/sam/git/portage/lib/portage/dbapi/__init__.py:428: UserWarning: gpkg
> file structure mismatch in
> /tmp/tmpug3ckom4/pkgdir/dev-libs/B/B-1-1.gpkg.tar; files: ['B-1/gpkg-1',
> 'B-1-1/metadata.tar.zst', 'B-1/image.tar.zst', 'B-1-1/Manifest']

Can you try applying your change(s) on top of https://github.com/gentoo/portage/pull/1220 and see if you still have an issue?

> 2) If I uncomment the os.remove line, I get a
> portage.exception.FileNotFound. Not yet figured out how to trigger a
> KeyError in the test yet.

In https://github.com/gentoo/portage/pull/1220 the test case is properly triggering a KeyError after bindb.bintree.remove(cpv).
Comment 5 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-29 02:01:42 UTC
Created attachment 880879 [details, diff]
corrupt.patch

(In reply to Zac Medico from comment #4)
> (In reply to Sam James from comment #3)
> > Created attachment 880873 [details]
> > test_move_ent.py modified
> > 
> > I hit something I find odd so far.
> > 
> > 1) With the attached modified test_move_ent.py + 'pytest
> > lib/portage/tests/update/test_move_ent.py -s -vvv -k
> > testMoveEntWithCorruptIndex', I get:
> > 
> >   /home/sam/git/portage/lib/portage/dbapi/__init__.py:428: UserWarning: gpkg
> > file structure mismatch in
> > /tmp/tmpug3ckom4/pkgdir/dev-libs/B/B-1-1.gpkg.tar; files: ['B-1/gpkg-1',
> > 'B-1-1/metadata.tar.zst', 'B-1/image.tar.zst', 'B-1-1/Manifest']
> 
> Can you try applying your change(s) on top of
> https://github.com/gentoo/portage/pull/1220 and see if you still have an
> issue?
> 

I do!

$ pytest lib/portage/tests/update/test_move_ent.py -s -vvv -k testMoveEntWithCorruptIndex
[...]
lib/portage/tests/update/test_move_ent.py::MoveEntTestCase::testMoveEntWithCorruptIndex
  /home/sam/git/portage/lib/portage/dbapi/__init__.py:432: UserWarning: gpkg file structure mismatch in /tmp/tmpo1x85ec1/pkgdir/dev-libs/B/B-1-1.gpkg.tar; files: ['B-1/gpkg-1', 'B-1-1/metadata.tar.zst', 'B-1/image.tar.zst', 'B-1-1/Manifest']
    warnings.warn(e)

with attached patch on top of your PR
Comment 6 Zac Medico gentoo-dev 2023-12-29 08:43:09 UTC
(In reply to Sam James from comment #5)
> $ pytest lib/portage/tests/update/test_move_ent.py -s -vvv -k
> testMoveEntWithCorruptIndex
> [...]
> lib/portage/tests/update/test_move_ent.py::MoveEntTestCase::
> testMoveEntWithCorruptIndex
>   /home/sam/git/portage/lib/portage/dbapi/__init__.py:432: UserWarning: gpkg
> file structure mismatch in
> /tmp/tmpo1x85ec1/pkgdir/dev-libs/B/B-1-1.gpkg.tar; files: ['B-1/gpkg-1',
> 'B-1-1/metadata.tar.zst', 'B-1/image.tar.zst', 'B-1-1/Manifest']
>     warnings.warn(e)
> 
> with attached patch on top of your PR

There's something wrong with the old_basename logic in the gpkg update_metadata method, since the test passes with this change than causes old_basename to be B-1-1 instead of B-1:

--- a/lib/portage/gpkg.py
+++ b/lib/portage/gpkg.py
@@ -995,13 +995,13 @@ class gpkg:
     def update_metadata(self, metadata, new_basename=None, force=False):
         """
         Update metadata in the gpkg file.
         """
         self._verify_binpkg()
         self.checksums = []
-        old_basename = self.prefix
+        old_basename = self.basename
 
         if self.signature_exist and not force:
             raise SignedPackage("Cannot update a signed gpkg file")
 
         if new_basename is None:
             new_basename = old_basename
Comment 7 Zac Medico gentoo-dev 2023-12-29 08:49:32 UTC
(In reply to Zac Medico from comment #6)
Also, this has nothing to do with the os.remove part of your test case, since it behaves the same if you comment out the os.remove lines.

It's just something about the way that aux_update calls the gpkg constructor and update_metadata method. I checked the state in aux_update, and cpv_str was dev-libs/B-1-1, which explains why self.basename has the correct B-1-1 value.
Comment 8 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-29 13:33:10 UTC
(In reply to Zac Medico from comment #6)
> There's something wrong with the old_basename logic in the gpkg
> update_metadata method, since the test passes with this change than causes
> old_basename to be B-1-1 instead of B-1:
> 
> --- a/lib/portage/gpkg.py
> +++ b/lib/portage/gpkg.py
> @@ -995,13 +995,13 @@ class gpkg:
>      def update_metadata(self, metadata, new_basename=None, force=False):
>
> -        old_basename = self.prefix
> +        old_basename = self.basename

This part has been unchanged since:

commit 1d94992a2df2b5cc963c26c7978a899dc642deb1
Author: Sheng Yu <syu.os@protonmail.com>
Date:   Thu Sep 1 10:44:55 2022 -0400

    Move all files into basename/DATA structure

    Signed-off-by: Sheng Yu <syu.os@protonmail.com>
    Signed-off-by: Michał Górny <mgorny@gentoo.org>
Comment 9 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-29 13:38:37 UTC
I wonder if this is a leftover from trying to handle corrupted early gpkgs, if it's not just a typo/copypaste error.

In _get_inner_tarinfo, for example, we do:
```
        if self.basename and self.prefix and not self.prefix.startswith(self.basename):
            writemsg(
                colorize("WARN", f"Package basename mismatched, using {self.prefix}")
            )
```
Comment 10 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-29 13:46:46 UTC
(In reply to Sam James from comment #9)
> In _get_inner_tarinfo, for example, we do:
>

Or maybe we should copy that logic for figuring out the old_basename. At least in other places, we prefer basename > prefix > abort.
Comment 11 Zac Medico gentoo-dev 2023-12-30 05:00:20 UTC
This will make the test pass without changing the gpkg internals:

--- a/lib/portage/dbapi/bintree.py
+++ b/lib/portage/dbapi/bintree.py
@@ -329,7 +329,7 @@ class bindbapi(fakedbapi):
         if binpkg_format == "xpak":
             mytbz2.recompose_mem(portage.xpak.xpak_mem(mydata))
         elif binpkg_format == "gpkg":
-            mybinpkg.update_metadata(mydata)
+            mybinpkg.update_metadata(mydata, new_basename=cpv_str)
         else:
             raise InvalidBinaryPackageFormat(
                 f"Unknown binary package format {binpkg_path}"
Comment 12 Zac Medico gentoo-dev 2023-12-30 06:07:36 UTC
Adding BUILD_ID to the gpkg basename/prefix in ResolverPlayground will suppress the aux_update issue:

--- a/lib/portage/tests/resolver/ResolverPlayground.py
+++ b/lib/portage/tests/resolver/ResolverPlayground.py
@@ -394,7 +394,12 @@ class ResolverPlayground:
                 t = portage.xpak.tbz2(binpkg_path)
                 t.recompose_mem(portage.xpak.xpak_mem(metadata))
             elif binpkg_format == "gpkg":
-                t = portage.gpkg.gpkg(self.settings, a.cpv, binpkg_path)
+                t = portage.gpkg.gpkg(
+                    self.settings,
+                    a.cpv
+                    + (f"-{metadata['BUILD_ID']}" if metadata.get("BUILD_ID") else ""),
+                    binpkg_path,
+                )
                 t.compress(os.path.dirname(binpkg_path), metadata)
             else:
                 raise InvalidBinaryPackageFormat(binpkg_format)
Comment 13 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-30 21:29:33 UTC
If we do that latter change, I worry we lose a useful test case for package structure mismatches even though it feels okay by itself :(

I hit this kind of issue in the wild within the last few weeks but couldn't reproduce it manually until now; all the fixes we did so far were for e.g signed pkgs which my case wasn't, so I think we should go for that caller patch instead of changing the internals?

Not sure what to do about the tests then though...
Comment 14 Larry the Git Cow gentoo-dev 2023-12-30 21:47:49 UTC
The bug has been referenced in the following commit(s):

https://gitweb.gentoo.org/proj/portage.git/commit/?id=fb1d0a22f65747b750143080536a4129e8654f97

commit fb1d0a22f65747b750143080536a4129e8654f97
Author:     Zac Medico <zmedico@gentoo.org>
AuthorDate: 2023-12-29 00:29:40 +0000
Commit:     Zac Medico <zmedico@gentoo.org>
CommitDate: 2023-12-30 21:46:04 +0000

    dbapi: KeyError tolerance during package moves
    
    Raise a new CorruptionKeyError exception type instead
    of a plain KeyError when os.stat fails. Treat this
    type of exception as a warning during package moves.
    
    Also fix this error that the test case triggered in
    the binarytree.remove method:
    
      NameError: name 'binpkg_path' is not defined
    
    Bug: https://bugs.gentoo.org/920828
    Signed-off-by: Zac Medico <zmedico@gentoo.org>

 lib/portage/dbapi/__init__.py                   | 14 ++++--
 lib/portage/dbapi/bintree.py                    | 28 +++++++++---
 lib/portage/dbapi/vartree.py                    |  9 ++--
 lib/portage/exception.py                        |  6 ++-
 lib/portage/tests/update/test_update_dbentry.py | 60 +++++++++++++++++++++++++
 5 files changed, 103 insertions(+), 14 deletions(-)
Comment 15 Zac Medico gentoo-dev 2023-12-30 22:41:51 UTC
(In reply to Sam James from comment #13)
> If we do that latter change, I worry we lose a useful test case for package
> structure mismatches even though it feels okay by itself :(

Since update_metadata calls _create_tarinfo it doesn't make any sense for update_metadata to use different logic than what _create_tarinfo uses here:

        if self.basename:
            basename = self.basename
        elif self.prefix:
            basename = self.prefix
        else:
            raise InvalidBinaryPackageFormat("No basename or prefix specified")

This makes the test pass:

diff --git a/lib/portage/gpkg.py b/lib/portage/gpkg.py
index 2e1130857b..031b3f87cb 100644
--- a/lib/portage/gpkg.py
+++ b/lib/portage/gpkg.py
@@ -998,13 +998,16 @@ class gpkg:
         """
         self._verify_binpkg()
         self.checksums = []
-        old_basename = self.prefix
-
         if self.signature_exist and not force:
             raise SignedPackage("Cannot update a signed gpkg file")
 
         if new_basename is None:
-            new_basename = old_basename
+            if self.basename:
+                new_basename = self.basename
+            elif self.prefix:
+                new_basename = self.prefix
+            else:
+                raise InvalidBinaryPackageFormat("No basename or prefix specified")
         else:
             new_basename = new_basename.split("/", maxsplit=1)[-1]
             self.basename = new_basename
Comment 16 Sam James archtester Gentoo Infrastructure gentoo-dev Security 2023-12-30 22:53:03 UTC
oh great, that works then, then shove https://docs.pytest.org/en/7.1.x/how-to/capture-warnings.html#pytest-mark-filterwarnings onto the test so it fails w/ the warning
Comment 17 Larry the Git Cow gentoo-dev 2024-01-02 03:09:01 UTC
The bug has been referenced in the following commit(s):

https://gitweb.gentoo.org/proj/portage.git/commit/?id=1e9f25d74fd6df7ef54edffe496499dc1711e911

commit 1e9f25d74fd6df7ef54edffe496499dc1711e911
Author:     Sam James <sam@gentoo.org>
AuthorDate: 2023-12-31 18:14:33 +0000
Commit:     Sam James <sam@gentoo.org>
CommitDate: 2023-12-31 22:23:27 +0000

    gpkg: fix basename handling via aux_update
    
    When using a binpkg with BUILD_ID, we might sometimes get a UserWarning
    for mismatched/corrupt gpkg structure:
    ```
    UserWarning: gpkg file structure mismatch in /tmp/tmpo1x85ec1/pkgdir/dev-libs/B/B-1-1.gpkg.tar; files: ['B-1/gpkg-1', 'B-1-1/metadata.tar.zst', 'B-1/image.tar.zst', 'B-1-1/Manifest']
        warnings.warn(e)
    ```
    
    update_metadata directly uses self.prefix instead of self.basename, while
    e.g. _get_inner_tarinfo prefers self.basename > self.prefix > error.
    
    Given update_metadata calls _create_tarinfo, use the same logic as it
    and _get_inner_tarinfo to avoid corrupting the filename for the image.
    
    Bug: https://bugs.gentoo.org/920828
    Thanks-to: Zac Medico <zmedico@gentoo.org>
    Signed-off-by: Sam James <sam@gentoo.org>

 lib/portage/gpkg.py                       |  9 ++-
 lib/portage/tests/update/test_move_ent.py | 92 ++++++++++++++++++++++++++++++-
 2 files changed, 97 insertions(+), 4 deletions(-)
Comment 18 Larry the Git Cow gentoo-dev 2024-01-02 03:39:31 UTC
The bug has been closed via the following commit(s):

https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=73c980cc99406a9257659adbe10af348897d7a98

commit 73c980cc99406a9257659adbe10af348897d7a98
Author:     Sam James <sam@gentoo.org>
AuthorDate: 2024-01-02 03:38:51 +0000
Commit:     Sam James <sam@gentoo.org>
CommitDate: 2024-01-02 03:38:51 +0000

    sys-apps/portage: add 3.0.60
    
    Closes: https://bugs.gentoo.org/920827
    Closes: https://bugs.gentoo.org/920828
    Closes: https://bugs.gentoo.org/921089
    Closes: https://bugs.gentoo.org/921107
    Signed-off-by: Sam James <sam@gentoo.org>

 sys-apps/portage/Manifest              |   1 +
 sys-apps/portage/portage-3.0.60.ebuild | 246 +++++++++++++++++++++++++++++++++
 2 files changed, 247 insertions(+)