OSDN Git Service

Initial implementation for "remove" feature.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Sun, 27 Feb 2011 16:21:36 +0000 (16:21 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Sun, 27 Feb 2011 16:21:36 +0000 (16:21 +0000)
ChangeLog
Makefile.in
src/pkgbase.h
src/pkgexec.cpp
src/pkgproc.h
src/pkgtask.h
src/pkgunst.cpp [new file with mode: 0644]

index 5137d49..d067485 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,36 @@
+2011-02-27  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Initial implementation for "remove" feature.
+
+       * src/pkgbase.h: Update copyright notice; add current year.
+       (pkgActionItem::SetPrimary): New inline public method; declare it.
+       (pkgActionItem::SetAuthorities): New public method; declare it.
+
+       * src/pkgtask.h (ACTION_PREFLIGHT, ACTION_REMOVE_OK): New defines.
+
+       * src/pkgproc.h: Update copyright notice; add current year.
+       (pkgManifest::GetSysRootReference): New public method; declare it.
+       (pkgManifest::GetRoot): New inline method; declare and implement it.
+       (pkgManifest::DetachSysRoot): Argument type changed to const char*.
+       (pkgRemove): New extern "C" function; declare it.
+
+       * src/pkgexec.cpp (pkgActionItem::SetPrimary): Implement it, and...
+       (pkgXmlDocument::Schedule): ...use it to promote actions on all user
+       specified packages, as listed on the command line, to primary status.
+       (pkgActionItem::Execute): Repeatedly invoke...
+       (pkgActionItem::SetAuthorities): ...this; incorporate call of...
+       (pkgActionItem::DownloadArchiveFiles): ...this, within the loop, so
+       that we retry failed downloads at least a second time.
+       [ACTION_REMOVE]: Delete stub; invoke pkgRemove().
+
+       * src/pkgunst.cpp: New file.
+       (pkgActionItem::SetAuthorities): Implement it.
+       (pkgManifest::GetSysRootReference): Implement it.
+       (pkgManifest::DetachSysRoot): Implement it per new declaration.
+       (pkgRemove): Implement it.
+
+       * Makefile.in (CORE_DLL_OBJECTS): Add pkgunst.OBJEXT
+
 2011-02-26  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Work-around for improper stderr buffering in MSYS mintty and rxvt.
index 47d01a7..ead2b38 100644 (file)
@@ -54,12 +54,12 @@ EXEEXT = @EXEEXT@
 LDFLAGS = @LDFLAGS@
 LIBS = -Wl,-Bstatic -lz -lbz2 -llzma -Wl,-Bdynamic -lwininet
 
-CORE_DLL_OBJECTS  =  climain.$(OBJEXT) pkgshow.$(OBJEXT) \
+CORE_DLL_OBJECTS  =  climain.$(OBJEXT) pkgshow.$(OBJEXT) dmh.$(OBJEXT) \
    pkgbind.$(OBJEXT) pkginet.$(OBJEXT) pkgstrm.$(OBJEXT) pkgname.$(OBJEXT) \
    pkgexec.$(OBJEXT) pkgfind.$(OBJEXT) pkginfo.$(OBJEXT) pkgspec.$(OBJEXT) \
    sysroot.$(OBJEXT) pkghash.$(OBJEXT) pkgkeys.$(OBJEXT) pkgdeps.$(OBJEXT) \
-   mkpath.$(OBJEXT)  pkgreqs.$(OBJEXT) pkginst.$(OBJEXT) tarproc.$(OBJEXT) \
-   xmlfile.$(OBJEXT) keyword.$(OBJEXT) vercmp.$(OBJEXT)  dmh.$(OBJEXT) \
+   mkpath.$(OBJEXT)  pkgreqs.$(OBJEXT) pkginst.$(OBJEXT) pkgunst.$(OBJEXT) \
+   tarproc.$(OBJEXT) xmlfile.$(OBJEXT) keyword.$(OBJEXT) vercmp.$(OBJEXT) \
    tinyxml.$(OBJEXT) tinyxmlparser.$(OBJEXT) \
    tinystr.$(OBJEXT) tinyxmlerror.$(OBJEXT)
 
index f013ae7..268a04d 100644 (file)
@@ -5,7 +5,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2009, 2010, MinGW Project
+ * Copyright (C) 2009, 2010, 2011, MinGW Project
  *
  *
  * Public interface for the package directory management routines;
@@ -239,8 +239,10 @@ class pkgActionItem
 
     /* Methods for compiling the schedule of actions.
      */
+    int SetAuthorities( pkgActionItem* );
     pkgActionItem* GetReference( pkgActionItem& );
     pkgActionItem* Schedule( unsigned long, pkgActionItem& );
+    inline void SetPrimary( pkgActionItem* );
 
     /* Methods for defining the selection criteria for
      * packages to be processed.
index 57dcfd2..7eb2ead 100644 (file)
@@ -323,6 +323,13 @@ pkgXmlNode *pkgActionItem::SelectIfMostRecentFit( pkgXmlNode *package )
   return Selection();
 }
 
+inline void pkgActionItem::SetPrimary( pkgActionItem* ref )
+{
+  flags = ref->flags;
+  selection[ to_install ] = ref->selection[ to_install ];
+  selection[ to_remove ] = ref->selection[ to_remove ];
+}
+
 pkgActionItem* pkgXmlDocument::Schedule
 ( unsigned long action, pkgActionItem& item, pkgActionItem* rank )
 {
@@ -332,15 +339,23 @@ pkgActionItem* pkgXmlDocument::Schedule
    */
   pkgActionItem *ref = rank ? rank : actions;
 
-  /* Don't reschedule, if we already have a prior matching item...
+  /* If we already have a prior matching item...
    */
-  if(  (actions->GetReference( item ) == NULL)
-  /*
-   * ...but, when we don't, and when this request produces a valid
-   * package reference, we raise a new scheduling request...
+  pkgActionItem *prior;
+  if( (prior = actions->GetReference( item )) != NULL )
+  {
+    /* ...then, when the current request refers to a primary action,
+     * we update the already scheduled request to reflect this...
+     */
+    if( (action & ACTION_PRIMARY) == ACTION_PRIMARY )
+      prior->SetPrimary( rank = ref->Schedule( action & ACTION_MASK, item ) );
+    return prior;
+  }
+  /* ...otherwise, when this request produces a valid package reference,
+   * we raise a new scheduling request...
    */
-  &&  ((ref = ref->Schedule( action, item )) != NULL)
-  &&  ((ref->Selection() != NULL) || (ref->Selection( to_remove ) != NULL)) )
+  else if( ((ref = ref->Schedule( action, item )) != NULL)
+  &&   ((ref->Selection() != NULL) || (ref->Selection( to_remove ) != NULL)) )
   {
     /* ...and, when successfully raised, add it to the task list...
      */
@@ -369,56 +384,55 @@ void pkgActionItem::Execute()
     pkgActionItem *current = this;
     bool init_rites_pending = true;
     while( current->prev != NULL ) current = current->prev;
-    DownloadArchiveFiles( current );
+    do {
+        DownloadArchiveFiles( current );
+       } while( SetAuthorities( current ) > 0 );
     while( current != NULL )
     {
-      /* Print a notification of the installation process to be
-       * performed, identifying the package to be processed.
-       */
-      const char *tarname;
-      if( (tarname = current->Selection()->GetPropVal( tarname_key, NULL )) == NULL )
-       tarname = current->Selection( to_remove )->GetPropVal( tarname_key, value_unknown );
-      dmh_printf( "%s: %s\n", action_name(current->flags & ACTION_MASK), tarname );
-
-      /* Check for any outstanding requirement to invoke the
-       * "self upgrade rites" process, so that we may install an
-       * upgrade for mingw-get itself...
+      /* Processing only those packages with assigned actions...
        */
-      if( init_rites_pending )
-       /*
-        * ...discontinuing the check once this has been completed,
-        * since it need not be performed more than once.
-        */
-        init_rites_pending = self_upgrade_rites( tarname );
-
-      if( (current->flags & ACTION_REMOVE) == ACTION_REMOVE )
+      if( (current->flags & ACTION_MASK) != 0 )
       {
-       /* The selected package has been marked for removal, either explicitly,
-        * or as an implicit prerequisite for upgrade; search the installed system
-        * manifest, to identify the specific version (if any) to be removed.
-        *
-        * FIXME: This implementation is a stub, to be rewritten when the system
-        * manifest structure has been specified and implemented.
+       /* Print a notification of the installation process to be
+        * performed, identifying the package to be processed.
         */
-       if( current->Selection( to_remove ) != NULL )
-         dmh_printf( " FIXME:pkgRemove<stub>:not removing %s\n",
-             current->Selection( to_remove )->GetPropVal( tarname_key, value_unknown )
-           );
-      }
-
-      if( (current->flags & ACTION_INSTALL) == ACTION_INSTALL )
-      {
-       /* The selected package has been marked for installation, either explicitly,
-        * or implicitly to complete a package upgrade.
+       const char *tarname;
+       if( (tarname = current->Selection()->GetPropVal( tarname_key, NULL )) == NULL )
+         tarname = current->Selection( to_remove )->GetPropVal( tarname_key, value_unknown );
+       dmh_printf( "%s: %s\n", action_name(current->flags & ACTION_MASK), tarname );
+
+       /* Check for any outstanding requirement to invoke the
+        * "self upgrade rites" process, so that we may install an
+        * upgrade for mingw-get itself...
         */
-       pkgXmlNode *tmp = current->Selection( to_remove );
-       if( pkgOptionSelected( PKG_OPTION_REINSTALL ) )
-         current->selection[ to_remove ] = NULL;
-       pkgInstall( current );
-       current->selection[ to_remove ] = tmp;
+       if( init_rites_pending )
+         /*
+          * ...discontinuing the check once this has been completed,
+          * since it need not be performed more than once.
+          */
+         init_rites_pending = self_upgrade_rites( tarname );
+
+       if( (current->flags & ACTION_REMOVE) == ACTION_REMOVE )
+       {
+         /* The selected package has been marked for removal, either explicitly,
+          * or as an implicit prerequisite for upgrade.
+          */
+         pkgRemove( current );
+       }
+
+       if( (current->flags & ACTION_INSTALL) == ACTION_INSTALL )
+       {
+         /* The selected package has been marked for installation, either explicitly,
+          * or implicitly to complete a package upgrade.
+          */
+         pkgXmlNode *tmp = current->Selection( to_remove );
+         if( pkgOptionSelected( PKG_OPTION_REINSTALL ) )
+           current->selection[ to_remove ] = NULL;
+         pkgInstall( current );
+         current->selection[ to_remove ] = tmp;
+       }
       }
-
-      /* Proceed to next package with scheduled actions.
+      /* Proceed to the next package, if any, with scheduled actions.
        */
       current = current->next;
     }
index 03db49a..ad66629 100644 (file)
@@ -5,7 +5,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2009, 2010, MinGW Project
+ * Copyright (C) 2009, 2010, 2011, MinGW Project
  *
  *
  * Specifications for the internal architecture of package archives,
@@ -34,6 +34,7 @@
 
 EXTERN_C void pkgInstall( pkgActionItem* );
 EXTERN_C void pkgRegister( pkgXmlNode*, pkgXmlNode*, const char*, const char* );
+EXTERN_C void pkgRemove( pkgActionItem* );
 
 class pkgManifest
 {
@@ -46,7 +47,10 @@ class pkgManifest
 
     void AddEntry( const char*, const char* );
     void BindSysRoot( pkgXmlNode*, const char* );
-    void DetachSysRoot( pkgXmlNode* );
+    void DetachSysRoot( const char* );
+
+    inline pkgXmlNode *GetRoot(){ return manifest->GetRoot(); }
+    pkgXmlNode *GetSysRootReference( const char* );
 
   private:
     pkgXmlDocument *manifest;
index 7671c4b..c75d200 100644 (file)
@@ -44,20 +44,23 @@ enum
   end_of_actions
 };
 
-#define ACTION_MASK    0x0F
+#define ACTION_MASK            0x0F
 
-#define ACTION_NONE     (unsigned long)(action_none)
-#define ACTION_REMOVE   (unsigned long)(action_remove)
-#define ACTION_INSTALL  (unsigned long)(action_install)
-#define ACTION_UPGRADE  (unsigned long)(action_upgrade)
-#define ACTION_LIST     (unsigned long)(action_list)
-#define ACTION_SHOW     (unsigned long)(action_show)
-#define ACTION_UPDATE   (unsigned long)(action_update)
+#define ACTION_NONE            (unsigned long)(action_none)
+#define ACTION_REMOVE          (unsigned long)(action_remove)
+#define ACTION_INSTALL         (unsigned long)(action_install)
+#define ACTION_UPGRADE         (unsigned long)(action_upgrade)
+#define ACTION_LIST            (unsigned long)(action_list)
+#define ACTION_SHOW            (unsigned long)(action_show)
+#define ACTION_UPDATE          (unsigned long)(action_update)
 
-#define STRICTLY_GT    (ACTION_MASK + 1)
-#define STRICTLY_LT    (STRICTLY_GT << 1)
+#define STRICTLY_GT            (ACTION_MASK + 1)
+#define STRICTLY_LT            (STRICTLY_GT << 1)
 
-#define ACTION_PRIMARY  (STRICTLY_LT << 1)
+#define ACTION_PRIMARY         (STRICTLY_LT << 1)
+
+#define ACTION_REMOVE_OK       (ACTION_PRIMARY << 1)
+#define ACTION_PREFLIGHT       (ACTION_PRIMARY << 2 | ACTION_REMOVE_OK)
 
 #ifndef EXTERN_C
 # ifdef __cplusplus
diff --git a/src/pkgunst.cpp b/src/pkgunst.cpp
new file mode 100644 (file)
index 0000000..dcf1161
--- /dev/null
@@ -0,0 +1,507 @@
+/*
+ * pkgunst.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2011, MinGW Project
+ *
+ *
+ * Implementation of the primary package removal methods.
+ *
+ *
+ * This is free software.  Permission is granted to copy, modify and
+ * redistribute this software, under the provisions of the GNU General
+ * Public License, Version 3, (or, at your option, any later version),
+ * as published by the Free Software Foundation; see the file COPYING
+ * for licensing details.
+ *
+ * Note, in particular, that this software is provided "as is", in the
+ * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
+ * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
+ * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
+ * MinGW Project, accept liability for any damages, however caused,
+ * arising from the use of this software.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "dmh.h"
+#include "debug.h"
+
+#include "pkginfo.h"
+#include "pkgkeys.h"
+#include "pkgproc.h"
+#include "pkgtask.h"
+
+#define PKGERR_INVALID_MANIFEST( REASON )  \
+  PKGMSG_INVALID_MANIFEST, tarname, REASON
+
+#define PKGMSG_INVALID_MANIFEST        "%s: invalid manifest; %s\n"
+#define PKGMSG_NO_RELEASE_KEY          "no release key assigned"
+#define PKGMSG_RELEASE_KEY_MISMATCH    "release key mismatch"
+
+static const char *request_key = "request";
+
+static __inline__ __attribute__((__always_inline__))
+pkgXmlNode *sysroot_lookup( pkgXmlNode *pkg, const char *tarname )
+{
+  /* A local helper function, to identify the sysroot association
+   * for any package which is to be uninstalled.
+   */
+  pkgSpecs lookup( tarname );
+  return pkg->GetSysRoot( lookup.GetSubSystemName() );
+}
+
+static __inline__ __attribute__((__always_inline__))
+const char *id_lookup( pkgXmlNode *reftag, const char *fallback )
+{
+  /* A local convenience function, to retrieve the value of
+   * the "id" attribute associated with any pkgXmlNode element.
+   */
+  return reftag->GetPropVal( id_key, fallback );
+}
+
+static __inline__ __attribute__((__always_inline__))
+const char *pathname_lookup( pkgXmlNode *reftag, const char *fallback )
+{
+  /* A local convenience function, to retrieve the value of
+   * the "id" attribute associated with any pkgXmlNode element.
+   */
+  return reftag->GetPropVal( pathname_key, fallback );
+}
+
+int pkgActionItem::SetAuthorities( pkgActionItem *current )
+{
+  /* Helper method to either grant or revoke authority for removal of
+   * any package, either permanently when the user requests that it be
+   * removed, or temporarily in preparation for replacement be a newer
+   * version, when scheduled for upgrade.
+   *
+   * This is a multiple pass method, iterating over the entire list
+   * of scheduled actions within each pass, until the entire schedule
+   * of authorities has been established.
+   */
+  if( (flags & ACTION_PREFLIGHT) == 0 )
+  {
+    /* This applies exclusively in the first pass, which is effectively
+     * a "preflight" checking pass only.
+     */
+    while( current != NULL )
+    {
+      /* Each scheduled action is inspected in turn...
+       */
+      pkgXmlNode *ref;
+      if( ((current->flags & ACTION_REMOVE) != 0)
+      &&  ((ref = current->Selection( to_remove )) != NULL)  )
+      {
+       /* ...and, when it specifies a "remove" action relating to a
+        * package which has been identified as "installed"...
+        */
+       const char *tarname = ref->GetPropVal( tarname_key, value_unknown );
+       DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_INIT,
+           dmh_printf( "%s: selected for %s\n",
+             (tarname = ref->GetPropVal( tarname_key, value_unknown )),
+             (current->flags & ACTION_INSTALL) ? "upgrade" : "removal"
+         ));
+
+       /* ...we identify its associated entry in the installed
+        * package manifest...
+        */
+       if( (ref = ref->GetInstallationRecord( tarname )) != NULL )
+       {
+         /* ...and then, having confirmed its validity...
+          */
+         DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_INIT,
+             dmh_printf( "%s: marked for %s\n",
+               ref->GetPropVal( tarname_key, value_unknown ),
+               (current->flags & ACTION_INSTALL) ? "upgrade" : "removal"
+           ));
+
+         /* ...we mark it as a candidate for removal...
+          */
+         ref->SetAttribute( request_key, action_name( ACTION_REMOVE ) );
+
+         /* ...and assign a provisional grant of authority
+          * to proceed with the removal...
+          */
+         current->flags |= ACTION_PREFLIGHT;
+       }
+      }
+      /* ...then, we move on to perform the same "preflight" check
+       * for the next scheduled action, if any.
+       */
+      current = current->next;
+    }
+    /* When we get to here, we have completed the "preflight" checks
+     * for all scheduled actions.  The "current" pointer has advanced,
+     * but the "this" pointer still refers to the head of the actions
+     * list; terminate this "preflight" pass, marking the associated
+     * entry to confirm that it has been completed.
+     */
+    return flags |= ACTION_PREFLIGHT;
+  }
+
+  /* We will get to here only in second and subsequent passes, after
+   * the initial "preflight" checks have been completed.  For actions
+   * which schedule a package removal, (whether explicitly, or as an
+   * implicit prerequisite in preparation for upgrade), the initial
+   * pass will have provisionally authorised removal; in subsequent
+   * passes, we either ratify that authority, or we revoke it in the
+   * event that it would break a dependency of another package.
+   *
+   * FIXME: we need to develop the code to perform the dependency
+   * analysis; for the time being, we simply return zero, ratifying
+   * authority for all removal requests.
+   */
+  return 0;
+}
+
+pkgXmlNode *pkgManifest::GetSysRootReference( const char *key )
+{
+  /* Method to verify that a package manifest includes
+   * a reference to any sysroot which claims it, returning
+   * a pointer to the first such reference found.
+   */
+  if( (this != NULL) && (manifest != NULL) && (key != NULL) )
+  {
+    /* We appear to have a valid manifest, and a valid sysroot
+     * key to match; locate this manifest's first, (and nominally
+     * its only), references section.
+     */
+    pkgXmlNode *grp = manifest->GetRoot()->FindFirstAssociate( reference_key );
+    while( grp != NULL )
+    {
+      /* Having identified a references section, locate the
+       * first sysroot reference contained therein...
+       */
+      pkgXmlNode *ref = grp->FindFirstAssociate( sysroot_key );
+      while( ref != NULL )
+      {
+       /* ...then retrieve the sysroot ID key value,
+        * for comparison with the requested key...
+        */
+       const char *chk;
+       if( ((chk = ref->GetPropVal( id_key, NULL )) != NULL)
+       &&  (strcmp( chk, key ) == 0)                        )
+         /*
+          * ...returning immediately, if a match is found...
+          */
+         return ref;
+
+       /* ...otherwise, repeat check for any further sysroot
+        * references which may be present...
+        */
+       ref = ref->FindNextAssociate( sysroot_key );
+      }
+      /* ...ultimately extending the search into any further
+       * (unlikely) references sections which might be present.
+       */
+      grp = grp->FindNextAssociate( reference_key );
+    }
+  }
+  /* If we fell through the preceding loop, then the expected reference
+   * was not present; return NULL, to report this unexpected result.
+   */
+  return NULL;
+}
+
+void pkgManifest::DetachSysRoot( const char *sysroot )
+{
+  /* Method to remove all references to a specified sysroot
+   * from a package manifest; (note that it would be unusual
+   * for a manifest to refer to a given sysroot more than once,
+   * but we repeat the request until we are sure no more such
+   * references exist...
+   */
+  pkgXmlNode *ref;
+  while( (ref = GetSysRootReference( sysroot )) != NULL )
+    /*
+     * ...deleting each one we do find, as we go.
+     */
+    ref->GetParent()->DeleteChild( ref );
+}
+
+/* Format string used to construct absolute path names from the
+ * "sysroot" relative "pathname" maintained in package manifests;
+ * (shared by "pkg_rmdir()" and "pkg_unlink()" functions).
+ */
+static const char *pkg_path_format = "%s/%s";
+
+static __inline__ __attribute__((__always_inline__))
+int pkg_rmdir( const char *sysroot, const char *pathname )
+{
+  /* Local helper, used to remove directories which become empty
+   * during scheduled package removal; "pathname" is specified from
+   * the package manifest, and is relative to "sysroot", (thus both
+   * must be specified and non-NULL).  Return value is non-zero
+   * when the specified directory is successfully removed, or
+   * zero otherwise.
+   */
+  int retval = 0;
+  if( (sysroot != NULL) && (pathname != NULL) )
+  {
+    /* "sysroot" and "pathname" are both specified.  Construct
+     * the absolute path name, and attempt to "rmdir" it, setting
+     * return value as appropriate; silently ignore failure.
+     */
+    char fullpath[ 1 + snprintf( NULL, 0, pkg_path_format, sysroot, pathname ) ];
+    snprintf( fullpath, sizeof( fullpath ), pkg_path_format, sysroot, pathname );
+
+    DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_TRANSACTIONS,
+       dmh_printf( "  %s: rmdir\n", fullpath )
+      );
+
+    retval = rmdir( fullpath ) == 0;
+  }
+  return retval;
+}
+
+static __inline__ __attribute__((__always_inline__))
+int pkg_unlink( const char *sysroot, const char *pathname )
+{
+  /* Local helper, used to delete files during scheduled package
+   * removal; "pathname" is specified within the package manifest,
+   * and is relative to "sysroot", (thus both must be specified and
+   * non-NULL).  Return value is non-zero when the specified file
+   * is successfully deleted, or zero otherwise.
+   */
+  int retval = 0;
+  if( (sysroot != NULL) && (pathname != NULL) )
+  {
+    char filepath[ 1 + snprintf( NULL, 0, pkg_path_format, sysroot, pathname ) ];
+    snprintf( filepath, sizeof( filepath ), pkg_path_format, sysroot, pathname );
+
+    DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_TRANSACTIONS,
+       dmh_printf( "  %s: unlink file\n", filepath )
+      );
+
+    if( ((retval = unlink( filepath )) != 0) && (errno != ENOENT) )
+      dmh_notify( DMH_WARNING, "%s:unlink failed; %s\n", filepath, strerror( errno ) );
+  }
+  return retval;
+}
+
+EXTERN_C void pkgRemove( pkgActionItem *current )
+{
+  /* Common handler for all package removal tasks...
+   */
+  pkgXmlNode *pkg;
+  if( (pkg = current->Selection( to_remove )) != NULL )
+  {
+    /* We've identified a candidate package for removal;
+     * first, identify the canonical tarname for the package,
+     * and the sysroot with which it is associated.
+     */
+    const char *tarname = pkg->GetPropVal( tarname_key, value_unknown );
+    pkgXmlNode *sysroot = sysroot_lookup( pkg, tarname );
+
+    dmh_printf( " removing %s %s\n", pkg->GetName(), tarname );
+
+    /* Removal of virtual (meta) packages is comparitively simple;
+     * identified by having an associated archive name of "none", they
+     * have no associated archive file, no installed footprint on disk,
+     * and no associated content manifest to process; thus...
+     */
+    if( ! match_if_explicit( pkg->ArchiveName(), value_none ) )
+    {
+      /* ...only in the case of packages identified as "real", (which
+       * we expect to be in a substantial majority), do we need to refer
+       * to any installation manifest, to identify actual disk files to
+       * be removed.
+       */
+      const char *refname;
+      pkgManifest inventory( package_key, tarname );
+      pkgXmlNode *ref, *manifest = inventory.GetRoot();
+
+      /* Perform some sanity checks on the retrieved manifest...
+       */
+      if( ((ref = manifest) == NULL)
+      ||  ((ref = ref->FindFirstAssociate( release_key )) == NULL)  )
+       /*
+        * Mostly "belt-and-braces": this should never happen, because
+        * the manifest constructor should never return a manifest which
+        * lacks a "release" element.  If we see this, there is something
+        * seriously wrong; hopefully we will get a bug report.
+        */
+       dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_NO_RELEASE_KEY ) );
+
+      else if( ((refname = ref->GetPropVal( tarname_key, NULL )) == NULL)
+      ||        (strcmp( tarname, refname ) != 0)                         )
+      {
+       /* Another "belt-and-braces" case: once again, it should never
+        * happen, because the manifest constructor should never return
+        * a manifest with a "tarname" attribute in the "release" element
+        * which doesn't match the "tarname" requested.
+        */
+       dmh_control( DMH_BEGIN_DIGEST );
+       dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_RELEASE_KEY_MISMATCH ) );
+       if( refname != NULL )
+         dmh_notify( DMH_ERROR, "%s: found %s instead\n", tarname, refname );
+       dmh_control( DMH_END_DIGEST );
+      }
+
+      else if( ref->FindNextAssociate( release_key ) != NULL )
+       /*
+        * Yet another "belt-and-braces" case: the constructor should
+        * never create a manifest which does not have exactly one, and
+        * no more than one, "release" element.
+        */
+       dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "too many release keys" ) );
+
+      else if( ((ref = manifest->FindFirstAssociate( reference_key )) == NULL)
+      ||        (ref->FindFirstAssociate( sysroot_key ) == NULL)               )
+       dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "no references" ) );
+
+      else
+      { /* We have a manifest which we may likely be able to process;
+        * before proceeding, perform a few sanity checks, and report
+        * any anomalies which may be recoverable.
+        */
+       const char *sysname = id_lookup( sysroot, NULL );
+       if( inventory.GetSysRootReference( sysname ) == NULL )
+       {
+         /* This indicates that the manifest file itself is lacking
+          * a reference to the sysroot.  The sysroot record indicates
+          * that such a reference should be present; diagnose the
+          * anomaly, and proceed anyway.
+          *
+          * FIXME: we should probably make this an error condition,
+          * which will suppress processing unless it is overridden by
+          * a user specified option.
+          */
+         dmh_notify( DMH_WARNING,
+             "%s: unreferenced in %s\n", sysname, id_lookup( manifest, value_unknown )
+           );
+       }
+       /* Now, we've validated the manifest, and confirmed that it
+        * correctly records its association with the current sysroot,
+        * (or we've reported the inconsistency; we may proceed with
+        * removal of the associated files.
+        */
+       if( (manifest = manifest->FindFirstAssociate( manifest_key )) != NULL )
+       {
+         /* The manifest records file pathnames relative to sysroot;
+          * thus, first identify the pathname prefix which identifies
+          * the absolute locations of the files and directories which
+          * are to be purged.
+          */
+         const char *syspath = pathname_lookup( sysroot, value_unknown );
+
+         /* Read the package manifest...
+          */
+         ref = manifest;
+         while( ref != NULL )
+         {
+           /* ...selecting records identifying installed files...
+            */
+           pkgXmlNode *files = ref->FindFirstAssociate( filename_key );
+           while( files != NULL )
+           {
+             /* ...and delete each in turn...
+              */
+             pkg_unlink( syspath, pathname_lookup( files, NULL ) );
+             /*
+              * ...before moving on to the next in the list.
+              */
+             files = files->FindNextAssociate( filename_key );
+           }
+           /* It should not be, but allow for the possibility that
+            * the manifest is subdivided into multiple sections.
+            */
+           ref = ref->FindNextAssociate( manifest_key );
+         }
+
+         /* Having deleted all files associated with the package,
+          * we attempt to prune any directories, which may have been
+          * created during the installation of this package, from the
+          * file system tree.  We note that we may remove only those
+          * directories which no longer contain any files or other
+          * subdirectories, (i.e. those which are leaf directories
+          * within the file system).  We also note that many of the
+          * directories associated with the package being removed
+          * may also contain files belonging to other packages; thus
+          * we do not consider it to be an error if we are unable to
+          * remove any directory specified in the package manifest.
+          *
+          * Removal of any leaf directory may expose its own parent
+          * as a new leaf, which may then itself become a candidate
+          * for removal; thus we adopt an iterative removal procedure,
+          * restarting with a further iteration after any pass through
+          * the manifest in which any directory is removed.
+          */
+         bool restart;
+         do {
+              /* Process the entire manifest on each iteration;
+               * initially assume that no restart will be required.
+               */
+              ref = manifest; restart = false;
+              while( ref != NULL )
+              {
+                /* Select manifest records which specify directories...
+                 */
+                pkgXmlNode *dir = ref->FindFirstAssociate( dirname_key );
+                while( dir != NULL )
+                {
+                  /* ...attempting to remove each in turn; request a
+                   * restart when at least one such attempt succeeds...
+                   */
+                  restart |= pkg_rmdir( syspath, pathname_lookup( dir, NULL ) );
+                  /*
+                   * ...then move on to the next record, if any.
+                   */
+                  dir = dir->FindNextAssociate( dirname_key );
+                }
+                /* As in the case of file removal, allow for the
+                 * possibility of a multisectional manifest.
+                 */
+                ref = ref->FindNextAssociate( manifest_key );
+              }
+              /* Restart the directory removal process, with a new
+               * iteration through the entire manifest, until no more
+               * listed directories can be removed.
+               */
+            } while( restart );
+
+         /* Finally, disassociate the package manifest from the active sysroot;
+          * this will automatically delete the manifest itself, unless it has a
+          * further association with any other sysroot, (e.g. in an alternative
+          * system map).
+          */
+         inventory.DetachSysRoot( sysname );
+       }
+      }
+    }
+    /* In the case of both real and virtual packages, the final phase of removal
+     * is to expunge the installation record from the associated sysroot element
+     * within the system map; (that is, any record of type "installed" contained
+     * within the sysroot element referenced by the "sysroot" pointer identified
+     * above, with a tarname attribute which matches "tarname").
+     */
+    pkgXmlNode *expunge, *instrec = sysroot->FindFirstAssociate( installed_key );
+    while( (expunge = instrec) != NULL )
+    {
+      /* Consider each installation record in turn, as a possible candidate for
+       * deletion; in any case, always locate the NEXT candidate, BEFORE deleting
+       * a matched record, so we don't destroy our point of reference, whence we
+       * must continue the search.
+       */
+      instrec = instrec->FindNextAssociate( installed_key );
+      if( strcmp( tarname, expunge->GetPropVal( tarname_key, value_unknown )) == 0 )
+      {
+       /* The CURRENT candidate matches the "tarname" criterion for deletion;
+        * we may delete it, also marking the sysroot record as "modified", so
+        * that the change will be committed to disk.
+        */
+       sysroot->DeleteChild( expunge );
+       sysroot->SetAttribute( modified_key, value_yes );
+      }
+    }
+  }
+}
+
+/* $RCSfile$: end of file */