[Bug 263879] pkgbase removes critical etc files upon upgrade

From: <bugzilla-noreply_at_freebsd.org>
Date: Tue, 04 Oct 2022 16:25:53 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263879

--- Comment #4 from Mark Johnston <markj@FreeBSD.org> ---
I spent some time reading the libpkg job scheduler and I think I see part of
the problem, but I'm not sure yet how to fix it.

Basically, upon an upgrade I get a conflict on /etc/termcap.small between
FreeBSD-runtime(local) and FreeBSD-utilities(remote).  When handling the
conflict, pkg decides to split the upgrade of -runtime into separate uninstall
and install jobs, which wipes my /etc files.  But, obviously we can avoid the
conflict by simply upgrading -runtime before -utilities; the split is not
needed.  And, pkg schedules the uninstall/install of -runtime back-to-back
anyway, so it's no different from an upgrade.

After pkg has figured out which jobs (i.e. package installs/upgrades/removals)
it will execute, it assigns priorities to determine the execution order.  This
happens in pkg_jobs_set_priorities().  If there's a conflict on a package being
deleted as part of an upgrade, pkg bumps the priorities of both the deletion
and the addition jobs:

 770         if (rit->priority >= req->items[1]->priority) {                    
 771             pkg_jobs_update_universe_item_priority(universe,
req->items[1],                                                                  
 772                 rit->priority + 1, PKG_PRIORITY_UPDATE_CONFLICT);          
 773             /*                                                             
 774              * Update priorities for a remote part as well                 
 775              */                                                            
 776             pkg_jobs_update_universe_item_priority(universe,
req->items[0],                                                                  
 777                 req->items[0]->priority, PKG_PRIORITY_UPDATE_REQUEST);     
 778         }

pkg_jobs_update_universe_item_priority() recursively updates the priorities of
dependent jobs.  However, it also has this check which I do not quite
understand:

 668                 if ((item->next != NULL || item->prev != NULL) &&          
 669                     it->pkg->type != PKG_INSTALLED &&                      
 670                     (type == PKG_PRIORITY_UPDATE_CONFLICT ||               
 671                      type == PKG_PRIORITY_UPDATE_DELETE)) {                
 672                         /*                                                 
 673                          * We do not update priority of a remote part of
conflict, as we know                                                            
 674                          * that remote packages should not contain
conflicts (they should be                                                       
 675                          * resolved in request prior to calling of this
function)                                                                       
 676                          */                                                
 677                         pkg_debug(2, "skip update priority for %s-%s",     
 678                             it->pkg->uid, it->pkg->digest);                
 679                         continue;                                          
 680                 }

In my case, it causes installation of the remote FreeBSD-runtime to have lower
priority than removal of the local FreeBSD-runtime package.  This causes pkg to
split the job.  Commenting out this block "fixes" my problem, but I can't tell
if it actually affects correctness of the code, or if it's just an
optimization, or...

BTW, this behaviour is highly dependent on the order in which packages are
loaded into various lists.  If I fetch packages first, then cancel the upgrade,
then try to upgrade again, I get different behaviour from the job scheduler. 
So it's not too surprising that others may not observe the problem.

What's an example of an upgrade where a package really does need to be split? 
Consider two packages p1, p2 with files a and b respectively, and suppose that
new versions of those packages switch ownership, so p1 contains b and p2
contains a.  Then to upgrade, we have to uninstall one of p1 or p2, upgrade the
other, then install the missing package.  This situation can arise in the base
system, so how do we handle the case where two critical pkgbase packages are
entangled this way?

-- 
You are receiving this mail because:
You are the assignee for the bug.