OSDN Git Service

Implement package installer for tar archives.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Tue, 2 Feb 2010 20:19:28 +0000 (20:19 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Tue, 2 Feb 2010 20:19:28 +0000 (20:19 +0000)
15 files changed:
ChangeLog
Makefile.in
src/climain.cpp
src/mkpath.c
src/mkpath.h
src/pkgbase.h
src/pkgdeps.cpp
src/pkgexec.cpp
src/pkginet.cpp
src/pkgkeys.c
src/pkgkeys.h
src/pkgname.cpp
src/pkgproc.h [new file with mode: 0644]
src/sysroot.cpp
src/tarproc.cpp [new file with mode: 0644]

index 070118c..bd05098 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,39 @@
+2010-02-02  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Implement package installer for tar archives.
+
+       * src/pkgexec.cpp (pkgXmlDocument::Execute): Replace existing
+       stub implementation of installer, using methods provided by...
+       * src/pkgproc.h, src/tarproc.cpp: ...these new files, with trap...
+       * src/pkgdeps.cpp (pkgXmlDocument::Schedule) [installed]: Add entry...
+       (pkgActionItem::Selection) [to_remove]: ...for this; use it to detect
+       `install' requests for packages which are aleady installed.
+
+       * Makefile.in (CORE_DLL_OBJECTS): Add tarproc.$(OBJEXT); specify
+       dependencies as appropriate.
+
+       * src/sysroot.cpp (pkgXmlDocument::LoadSystemMap): Don't commit
+       newly created `sysroot' mapping records to disk; defer to...
+       (pkgXmlDocument::UpdateSystemMap): ...this new method.
+       (sigpath): Make it a global variable, with file (static) scope.
+
+       * src/climain.cpp (pkgXmlDocument::UpdateSystemMap): Invoke it.
+
+       * src/pkgkeys.h (download_key, modified_key, source_key): New global
+       string variables; declare them, providing their implementations...
+       * src/pkgkeys.c (download_key, modified_key, source_key): ...here.
+       * src/pkgname.cpp (download_key, source_key): Use them.
+
+       * src/pkginet.cpp (pkgActionItem::ArchivePath): Delete; replace...
+       * src/mkpath.c (pkgArchivePath): ...with this free standing function.
+       * src/mkpath.h (pkgArchivePath): Declare its prototype.
+
+       * src/pkgbase.h (pkgActionItem::ArchivePath): Delete declaration.
+       (pkgXmlDocument): Add a default constructor; implement as `inline'.
+       (pkgXmlDocument::AddDeclaration): Use heap memory to allocate the new
+       declaration object, instead of `auto' variable, to avoid scope error.
+       (pkgXmlDocument::UpdateSystemMap): Declare it.
+
 2010-01-26  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Implement rudimentary dependency resolver.
index 698f0e5..fed0f0b 100644 (file)
@@ -52,7 +52,7 @@ CORE_DLL_OBJECTS = climain.$(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)  xmlfile.$(OBJEXT) keyword.$(OBJEXT) \
+   mkpath.$(OBJEXT)  tarproc.$(OBJEXT) xmlfile.$(OBJEXT) keyword.$(OBJEXT) \
    tinyxml.$(OBJEXT) tinyxmlparser.$(OBJEXT) \
    tinystr.$(OBJEXT) tinyxmlerror.$(OBJEXT) \
    vercmp.$(OBJEXT)  dmh.$(OBJEXT)
@@ -77,6 +77,8 @@ mingw-get-0.dll: $(CORE_DLL_OBJECTS)
 dmh.$(OBJEXT):     dmh.h
 climain.$(OBJEXT): pkgbase.h pkgtask.h tinyxml.h tinystr.h dmh.h
 sysroot.$(OBJEXT): pkgbase.h pkgkeys.h tinyxml.h tinystr.h mkpath.h dmh.h
+tarproc.$(OBJEXT): pkgbase.h pkgproc.h pkginfo.h pkgkeys.h pkgstrm.h
+tarproc.$(OBJEXT): tinyxml.h tinystr.h mkpath.h dmh.h
 
 pkgname.$(OBJEXT): pkgbase.h pkgkeys.h dmh.h
 pkgfind.$(OBJEXT): pkgbase.h pkgkeys.h tinyxml.h tinystr.h
index 62dd6fe..08a87fd 100644 (file)
@@ -96,9 +96,11 @@ EXTERN_C int climain( int argc, char **argv )
       while( --argc )
        dbase.Schedule( (unsigned long)(action), *++argv );
 
-      /* ...and finally, execute all scheduled actions.
+      /* ...finally, execute all scheduled actions, and update the
+       * system map accordingly.
        */
       dbase.ExecuteActions();
+      dbase.UpdateSystemMap();
     }
 
     /* If we get this far, then all actions completed successfully;
index 3f9e141..10153a4 100644 (file)
 
 #endif
 
+const char *pkgArchivePath()
+{
+  /* Specify where downloaded packages are cached,
+   * within the local file system.
+   */
+  return "%R" "var/cache/mingw-get/packages" "%/M/%F";
+}
+
 int mkpath( char *buf, const char *fmt, const char *file, const char *modifier )
 {
   /* A helper function, for constructing package URL strings.
index 79f6790..fe6b155 100644 (file)
@@ -41,4 +41,6 @@ EXTERN_C int mkdir_recursive( const char *, int );
 EXTERN_C int set_output_stream( const char *, int );
 EXTERN_C int mkpath( char *, const char *, const char *, const char * );
 
+EXTERN_C const char *pkgArchivePath();
+
 #endif /* MKPATH_H: $RCSfile$: end of file */
index b5800c6..61fa896 100644 (file)
@@ -215,10 +215,6 @@ class pkgActionItem
       return selection[ mode ];
     }
 
-    /* Method specifying where downloaded packages are stored.
-     */
-    const char* ArchivePath();
-
     /* Method for processing all scheduled actions.
      */
     void Execute();
@@ -232,6 +228,7 @@ class pkgXmlDocument : public TiXmlDocument
   public:
     /* Constructors...
      */
+    inline pkgXmlDocument(){}
     inline pkgXmlDocument( const char* name )
     {
       /* tinyxml has a similar constructor, but unlike wxXmlDocument,
@@ -266,8 +263,7 @@ class pkgXmlDocument : public TiXmlDocument
       /* Not a standard method of either wxXmlDocumemnt or TiXmlDocument;
        * this is a convenience method for setting up a new XML database.
        */
-      TiXmlDeclaration decl( version, encoding, standalone );
-      LinkEndChild( &decl );
+      LinkEndChild( new TiXmlDeclaration( version, encoding, standalone ) );
     }
     inline void SetRoot( TiXmlNode* root )
     {
@@ -310,6 +306,11 @@ class pkgXmlDocument : public TiXmlDocument
      */
     void LoadSystemMap();
 
+    /* Complementary method, to update the saved sysroot data associated
+     * with the active system map.
+     */
+    void UpdateSystemMap();
+
     /* Method to locate the XML database entry for a named package.
      */
     pkgXmlNode* FindPackageByName( const char*, const char* = NULL );
index 72205f3..6ba5df7 100644 (file)
@@ -298,7 +298,7 @@ void pkgXmlDocument::Schedule( unsigned long action, const char* name )
             * ...i.e. here we have identified a release
             * which is currently installed...
             */
-           installed = release;
+           latest.SelectPackage( installed = release, to_remove );
 
          if( latest.SelectIfMostRecentFit( release ) == release )
            /*
index e65f034..3b57869 100644 (file)
@@ -31,6 +31,7 @@
 #include "pkgkeys.h"
 #include "pkginfo.h"
 #include "pkgtask.h"
+#include "pkgproc.h"
 
 EXTERN_C const char *action_name( unsigned long index )
 {
@@ -386,7 +387,18 @@ void pkgActionItem::Execute()
         * FIXME: Once more, this is a stub, to be extended to provide the working
         * installer implementation.
         */
-       dmh_printf( " installing %s\n", current->Selection()->GetPropVal( tarname_key, "<unknown>" ));
+       //dmh_printf( " installing %s\n", current->Selection()->GetPropVal( tarname_key, "<unknown>" ));
+       if( current->Selection( to_remove ) == NULL )
+       {
+         pkgTarArchiveInstaller package( current->Selection() );
+         if( package.IsOk() )
+           package.Process();
+       }
+       else
+         dmh_notify( DMH_ERROR,
+             "package %s is already installed\n",
+             current->Selection()->GetPropVal( tarname_key, "<unknown>" )
+           );
       }
 
       /* Proceed to next package with scheduled actions.
index ec081bc..6934d13 100644 (file)
@@ -95,14 +95,6 @@ class pkgInternetAgent
  */
 static pkgInternetAgent pkgDownloadAgent;
 
-const char *pkgActionItem::ArchivePath()
-{
-  /* Specify where downloaded packages are cached,
-   * within the local file system.
-   */
-  return "%R" "var/cache/mingw-get/packages" "%/M/%F";
-}
-
 class pkgInternetStreamingAgent
 {
   /* Another locally implemented class; each individual file download
@@ -289,7 +281,7 @@ void pkgActionItem::DownloadArchiveFiles( pkgActionItem *current )
        * the required archive from a suitable internet mirror host.
        */
       const char *package_name = current->Selection()->ArchiveName();
-      pkgInternetStreamingAgent download( package_name, current->ArchivePath() );
+      pkgInternetStreamingAgent download( package_name, pkgArchivePath() );
 
       /* Check if the required archive is already available locally...
        */
index c0c18a2..e6e2dc2 100644 (file)
@@ -29,6 +29,7 @@ const char *alias_key             =   "alias";
 const char *application_key        =   "application";
 const char *catalogue_key          =   "catalogue";
 const char *component_key          =   "component";
+const char *download_key           =   "download";
 const char *download_host_key      =   "download-host";
 const char *eq_key                 =   "eq";
 const char *ge_key                 =   "ge";
@@ -39,6 +40,7 @@ const char *issue_key             =   "issue";
 const char *le_key                 =   "le";
 const char *lt_key                 =   "lt";
 const char *mirror_key             =   "mirror";
+const char *modified_key           =   "modified";
 const char *name_key               =   "name";
 const char *package_key                    =   "package";
 const char *package_collection_key  =  "package-collection";
@@ -48,6 +50,7 @@ const char *profile_key                   =   "profile";
 const char *release_key                    =   "release";
 const char *repository_key         =   "repository";
 const char *requires_key           =   "requires";
+const char *source_key             =   "source";
 const char *subsystem_key          =   "subsystem";
 const char *sysmap_key             =   "system-map";
 const char *sysroot_key                    =   "sysroot";
index 53229b8..4b64605 100644 (file)
@@ -40,6 +40,7 @@ EXTERN_C_DECL const char *alias_key;
 EXTERN_C_DECL const char *application_key;
 EXTERN_C_DECL const char *catalogue_key;
 EXTERN_C_DECL const char *component_key;
+EXTERN_C_DECL const char *download_key;
 EXTERN_C_DECL const char *download_host_key;
 EXTERN_C_DECL const char *eq_key;
 EXTERN_C_DECL const char *ge_key;
@@ -50,6 +51,7 @@ EXTERN_C_DECL const char *issue_key;
 EXTERN_C_DECL const char *le_key;
 EXTERN_C_DECL const char *lt_key;
 EXTERN_C_DECL const char *mirror_key;
+EXTERN_C_DECL const char *modified_key;
 EXTERN_C_DECL const char *name_key;
 EXTERN_C_DECL const char *package_key;
 EXTERN_C_DECL const char *package_collection_key;
@@ -59,6 +61,7 @@ EXTERN_C_DECL const char *profile_key;
 EXTERN_C_DECL const char *release_key;
 EXTERN_C_DECL const char *repository_key;
 EXTERN_C_DECL const char *requires_key;
+EXTERN_C_DECL const char *source_key;
 EXTERN_C_DECL const char *subsystem_key;
 EXTERN_C_DECL const char *sysmap_key;
 EXTERN_C_DECL const char *sysroot_key;
index c5f11d1..cef35ef 100644 (file)
@@ -107,7 +107,7 @@ const char *pkgXmlNode::SourceArchiveName()
    * not represent a "release", or if it does not have a contained
    * "source" element specifying a "tarname" property.
    */
-  return pkgArchiveName( this, "source", 0 );
+  return pkgArchiveName( this, source_key, 0 );
 }
 
 const char *pkgXmlNode::ArchiveName()
@@ -124,7 +124,7 @@ const char *pkgXmlNode::ArchiveName()
    * alternative specification within a "download" element; if
    * unresolved to either of these, returns NULL.
    */
-  return pkgArchiveName( this, "download", 1 );
+  return pkgArchiveName( this, download_key, 1 );
 }
 
 /* $RCSfile$: end of file */
diff --git a/src/pkgproc.h b/src/pkgproc.h
new file mode 100644 (file)
index 0000000..0a90d30
--- /dev/null
@@ -0,0 +1,180 @@
+#ifndef PKGPROC_H
+/*
+ * pkgproc.h
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, 2010, MinGW Project
+ *
+ *
+ * Specifications for the internal architecture of package archives,
+ * and the public interface for the package archive processing routines,
+ * used to implement the package installer and uninstaller.
+ *
+ *
+ * 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.
+ *
+ */
+#define PKGPROC_H  1
+
+#include "pkgbase.h"
+#include "pkgstrm.h"
+
+class pkgArchiveProcessor
+{
+  /* A minimal generic abstract base class, from which we derive
+   * processing tools for handling arbitrary package architectures.
+   */
+  public:
+    pkgArchiveProcessor(){}
+    virtual ~pkgArchiveProcessor(){}
+
+    virtual bool IsOk() = 0;
+    virtual int Process() = 0;
+
+  protected:
+    /* Pointers to the sysroot management records and installation
+     * path template for a managed package; note that 'tarname' does
+     * not explicitly refer only to tar archives; it is simply the
+     * canonical name of the archive file, as recorded in the XML
+     * 'tarname' property of the package identifier record.
+     */
+    pkgXmlNode *sysroot;
+    const char *sysroot_path;
+    const char *tarname;
+    const char *pkgfile;
+};
+
+/* Our standard package format specifies the use of tar archives;
+ * the following specialisation of pkgArchiveProcessor provides the
+ * tools we need for processing such archives.
+ *
+ */
+union tar_archive_header
+{
+  /* Layout specification for the tar archive header records,
+   * one of which is associated with each individual data entity
+   * stored within a tar archive.
+   */
+  char aggregate[512];                 /* aggregate size is always 512 bytes */
+  struct tar_header_field_layout
+  {
+    char name[100];                    /* archive entry path name */
+    char mode[8];                      /* unix access mode for archive entry */
+    char uid[8];                       /* user id of archive entry's owner */
+    char gid[8];                       /* group id of archive entry's owner */
+    char size[12];                     /* size of archive entry in bytes */
+    char mtime[12];                    /* last modification time of entry */
+    char chksum[8];                    /* checksum for this header block */
+    char typeflag[1];                  /* type of this archive entry */
+    char linkname[100];                        /* if a link, name of linked file */
+    char magic[6];                     /* `magic' signature for the archive */
+    char version[2];                   /* specification conformance code */
+    char uname[32];                    /* user name of archive entry's owner */
+    char gname[32];                    /* group name of entry's owner */
+    char devmajor[8];                  /* device entity major number */
+    char devminor[8];                  /* device entity minor number */
+    char prefix[155];                  /* prefix to extend "name" field */
+  } field;
+};
+
+/* Type descriptors, as used in the `typeflag' field of the above
+ * tar archive header records; (note: this is not a comprehensive
+ * list, but covers all of, and more than, our requirements).
+ */
+#define TAR_ENTITY_TYPE_FILE           '0'
+#define TAR_ENTITY_TYPE_LINK           '1'
+#define TAR_ENTITY_TYPE_SYMLINK                '2'
+#define TAR_ENTITY_TYPE_CHRDEV         '3'
+#define TAR_ENTITY_TYPE_BLKDEV         '4'
+#define TAR_ENTITY_TYPE_DIRECTORY      '5'
+
+/* Some older style tar archives may use '\0' as an alternative to '0',
+ * to identify an archive entry representing a regular file.
+ */
+#define TAR_ENTITY_TYPE_ALTFILE                '\0'
+
+class pkgTarArchiveProcessor : public pkgArchiveProcessor
+{
+  /* An abstract base class, from which various tar archive
+   * processing tools, (including installers and uninstallers),
+   * are derived; this class implements the generic methods,
+   * which are shared by all such tools.
+   */
+  public:
+    /* Constructor and destructor...
+     */
+    pkgTarArchiveProcessor( pkgXmlNode* );
+    virtual ~pkgTarArchiveProcessor();
+
+    inline bool IsOk(){ return stream->IsReady(); }
+    virtual int Process();
+
+  protected:
+    /* Class data...
+     */
+    pkgArchiveStream *stream;
+    union tar_archive_header header;
+
+    /* Internal archive processing methods...
+     * These are divided into two categories: those for which the
+     * abstract base class furnishes a generic implementation...
+     */
+    virtual int GetArchiveEntry();
+    virtual int ProcessEntityData( int );
+
+    /* ...those for which each specialisation is expected to
+     * furnish its own task specific implementation...
+     */
+    virtual int ProcessDirectory( const char* ) = 0;
+    virtual int ProcessDataStream( const char* ) = 0;
+
+    /* ...and those which would normally be specialised, but
+     * for which we currently provide a generic stub...
+     */
+    virtual int ProcessLinkedEntity( const char* );
+};
+
+class pkgTarArchiveInstaller : public pkgTarArchiveProcessor
+{
+  public:
+    /* Constructor and destructor...
+     */
+    pkgTarArchiveInstaller( pkgXmlNode* );
+    virtual ~pkgTarArchiveInstaller(){}
+
+  private:
+    /* Specialised implementations of the archive processing methods...
+     */
+    virtual int ProcessDirectory( const char* );
+    virtual int ProcessDataStream( const char* );
+};
+
+class pkgTarArchiveUninstaller : public pkgTarArchiveProcessor
+{
+  public:
+    /* Constructor and destructor...
+     */
+    pkgTarArchiveUninstaller( pkgXmlNode* );
+    virtual ~pkgTarArchiveUninstaller(){};
+
+  private:
+    /* Specialised implementations of the archive processing methods...
+     */
+    virtual int ProcessDirectory( const char* );
+    virtual int ProcessDataStream( const char* );
+};
+
+#endif /* PKGPROC_H: $RCSfile$: end of file */
index 21aaa01..7cf2090 100644 (file)
@@ -111,6 +111,8 @@ static bool samepath( const char *tstpath, const char *refpath )
   return (*tstpath == *refpath);
 }
 
+static const char *sigpath = "%R" "var/lib/mingw-get/data/%F.xml";
+
 void pkgXmlDocument::LoadSystemMap()
 {
   /* Load an initial, or a replacement, system map into the
@@ -192,7 +194,6 @@ fprintf( stderr, "Bind subsystem %s: sysroot = %s\n",
              /* This sysroot has not yet been registered...
               */
              int retry = 0;
-             const char *sigpath = "%R" "var/lib/mingw-get/data/%F.xml";
 
              while( retry < 16 )
              {
@@ -255,30 +256,21 @@ fprintf( stderr, "Bind subsystem %s: sysroot = %s\n",
                  /* ...we have exhausted all possible hash references,
                   * finding no existing mapping database for this sysroot...
                   * The current hashed file name has not yet been assigned,
-                  * so initialise it as a new database for this sysroot.
+                  * so create a new entry in the internal XML database,
+                  * marking it as "modified", so that it will be written
+                  * to disk, when the system map is updated.
                   *
                   * FIXME: perhaps we should not do this arbitrarily for
                   * any non-default system root.
                   */
-                 check.AddDeclaration( "1.0", "UTF-8", "yes" );
-
-                 /* Initialise the root element for this new database.
-                  */
-                 pkgXmlNode root( sysroot_key );
-                 root.SetAttribute( id_key, sig );
-                 root.SetAttribute( pathname_key, path );
-                 check.SetRoot( &root );
+                 pkgXmlNode *record = new pkgXmlNode( sysroot_key );
+                 record->SetAttribute( modified_key, yes_value );
+                 record->SetAttribute( id_key, sig );
+                 record->SetAttribute( pathname_key, path );
+                 dbase->AddChild( record );
 
-                 /* Link a copy of it as the corresponding sysroot
-                  * entry in the internal database.
+                 /* Finally, force termination of the sysroot search.
                   */
-                 dbase->AddChild( root.Clone() );
-
-                 /* Commit the initial state of this sysroot database
-                  * to a disk file, for future reference, and terminate
-                  * the retry loop at the end of this cycle.
-                  */
-                 check.Save( sigfile );
                  retry = 16;
                }
 
@@ -319,6 +311,48 @@ fprintf( stderr, "Bind subsystem %s: sysroot = %s\n",
   }
 }
 
+void pkgXmlDocument::UpdateSystemMap()
+{
+  /* Inspect all sysroot records in the current system map;
+   * save copies of any marked with the 'modified' attribute
+   * to the appropriate disk files.
+   */
+  pkgXmlNode *entry = GetRoot()->FindFirstAssociate( sysroot_key );
+
+  while( entry != NULL )
+  {
+    /* We found a sysroot record...
+     * evaluate and clear its 'modified' attribute...
+     */
+    const char *modified = entry->GetPropVal( modified_key, no_value );
+    entry->RemoveAttribute( modified_key );
+
+    if(  (strcmp( modified, yes_value ) == 0)
+    &&  ((modified = entry->GetPropVal( id_key, NULL )) != NULL)  )
+    {
+      /* The 'modified' attribute for this record was set,
+       * and the 'id' attribute is valid; establish the path
+       * name for the file in which to save the record.
+       */
+      char mapfile[mkpath( NULL, sigpath, modified, NULL )];
+      mkpath( mapfile, sigpath, modified, NULL );
+
+      /* Create a copy of the sysroot record, as the content of
+       * a new freestanding XML document, and write it out to the
+       * nominated record file.
+       */
+      pkgXmlDocument map;
+      map.AddDeclaration( "1.0", "UTF-8", yes_value );
+      map.SetRoot( entry->Clone() );
+      map.Save( mapfile );
+    }
+
+    /* Repeat for the next sysroot record, if any...
+     */
+    entry = entry->FindNextAssociate( sysroot_key );
+  }
+}
+
 pkgXmlNode* pkgXmlNode::GetSysRoot( const char *subsystem )
 {
   /* Retrieve the installation records for the system root associated
diff --git a/src/tarproc.cpp b/src/tarproc.cpp
new file mode 100644 (file)
index 0000000..9b8f2aa
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * tarproc.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, 2010, MinGW Project
+ *
+ *
+ * Implementation of package archive processing methods, for reading
+ * and extracting content from tar archives.
+ *
+ *
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "dmh.h"
+#include "mkpath.h"
+
+#include "pkginfo.h"
+#include "pkgkeys.h"
+#include "pkgproc.h"
+
+pkgTarArchiveProcessor::pkgTarArchiveProcessor( pkgXmlNode *pkg )
+{
+  /* Constructor to associate a package tar archive with its
+   * nominated sysroot and respective installation directory path,
+   * and prepare it for processing, using an appropriate streaming
+   * decompression filter; (choice of filter is based on archive
+   * file name extension; file names are restricted to the
+   * POSIX Portable Character Set).
+   *
+   * First, we anticipate an invalid initialisation state...
+   */
+  sysroot = NULL;
+  sysroot_path = NULL;
+  stream = NULL;
+
+  /* The 'pkg' XML database entry must be non-NULL, must
+   * represent a package release, and must specify a canonical
+   * tarname to identify the package...
+   */
+  if(  (pkg != NULL) && pkg->IsElementOfType( release_key )
+  &&  ((tarname = pkg->GetPropVal( tarname_key, NULL )) != NULL)  )
+  {
+    /* When these pre-conditions are satisfied, we may proceed
+     * to identify and locate the sysroot record with which this
+     * package is to be associated...
+     */
+    pkgSpecs lookup( pkgfile = tarname );
+    if( (sysroot = pkg->GetSysRoot( lookup.GetSubSystemName() )) != NULL )
+    {
+      /* Having located the requisite sysroot record, we may
+       * retrieve its specified installation path prefix...
+       */
+      const char *prefix;
+      if( (prefix = sysroot->GetPropVal( pathname_key, NULL )) != NULL )
+      {
+       /* ...and incorporate it into a formatting template
+        * for use in deriving the full path names for files
+        * which are installed from this package.
+        */
+       const char *template_format = "%F%%/M/%%F";
+       char template_text[mkpath( NULL, template_format, prefix, NULL )];
+       mkpath( template_text, template_format, prefix, NULL );
+       sysroot_path = strdup( template_text );
+      }
+    }
+    /* Some older packages don't use the canonical tarname
+     * for the archive file name; identify the real file name
+     * associated with such packages...
+     */
+    pkgfile = pkg->ArchiveName();
+
+    /* Finally, initialise the data stream which we will use
+     * for reading the package content.
+     */
+    const char *archive_path_template = pkgArchivePath();
+    char archive_path_name[mkpath( NULL, archive_path_template, pkgfile, NULL )];
+    mkpath( archive_path_name, archive_path_template, pkgfile, NULL );
+    stream = pkgOpenArchiveStream( archive_path_name );
+  }
+}
+
+pkgTarArchiveProcessor::~pkgTarArchiveProcessor()
+{
+  /* Destructor must release the heap memory allocated in the
+   * constructor, (by strdup), clean up the decompression filter
+   * state, and close the archive data stream.
+   */
+  free( (void *)(sysroot_path) );
+  delete stream;
+}
+
+int pkgTarArchiveProcessor::ProcessLinkedEntity( const char *pathname )
+{
+  /* FIXME: Win32 links need special handling; for hard links, we
+   * may be able to create them directly, with >= Win2K and NTFS;
+   * for symlinks on *all* Win32 variants, and for hard links on
+   * FAT32 or Win9x, we need to make physical copies of the source
+   * file, at the link target location.
+   *
+   * For now, we simply ignore links.
+   */
+  dmh_printf(
+      "FIXME:ProcessLinkedEntity<stub>:Ignoring link: %s --> %s\n",
+       pathname, header.field.linkname
+    );
+  return 0;
+}
+
+static
+uint64_t compute_octval( const char *p, size_t len )
+# define octval( FIELD ) compute_octval( FIELD, sizeof( FIELD ) )
+{
+  /* Helper to convert the ASCII representation of octal values,
+   * (as recorded within tar archive header fields), to their actual
+   * numeric values, ignoring leading or trailing garbage.
+   */
+  uint64_t value = 0LL;
+
+  while( (len > 0) && ((*p < '0') || (*p > '7')) )
+  {
+    /* Step over leading garbage.
+     */
+    ++p; --len;
+  }
+  while( (len > 0) && (*p >= '0') && (*p < '8') )
+  {
+    /* Accumulate octal digits; (each represents exactly three
+     * bits in the accumulated value), until we either exhaust
+     * the width of the field, or we encounter trailing junk.
+     */
+    value = (value << 3) + *p++ - '0'; --len;
+  }
+  return value;
+}
+
+int pkgTarArchiveProcessor::GetArchiveEntry()
+{
+  /* Read header for next available entry in the tar archive;
+   * check for end-of-archive mark, (all zero header); verify
+   * checksum for active entry.
+   */
+  char *buf = header.aggregate;
+  size_t count = stream->Read( buf, sizeof( header ) );
+
+  if( count < sizeof( header ) )
+  {
+    /* Failed to read a complete header; return error code.
+     */
+    return -1;
+  }
+
+  while( count-- )
+    /*
+     * Outer loop checks for an all zero header...
+     */
+    if( *buf++ != '\0' )
+    {
+      /* Any non-zero byte transfers control to an inner loop,
+       * to rescan the entire header, accumulating its checksum...
+       */
+      uint64_t sum = 0;
+      for( buf = header.aggregate, count = sizeof( header ); count--; ++buf )
+      {
+       if( (buf < header.field.chksum) || (buf >= header.field.typeflag) )
+         /*
+          * ...counting the actual binary value of each byte,
+          * in all but the checksum field itself...
+          */
+         sum += *buf;
+       else
+         /* ...while treating each byte within the checksum field as
+          * having an effective value equivalent to ASCII <space>.
+          */
+         sum += 0x20;
+      }
+      /* After computing the checksum for a non-zero header,
+       * verify it against the value recorded in the checksum field;
+       * return +1 for a successful match, or -2 for failure.
+       */
+      return (sum == octval( header.field.chksum )) ? 1 : -2;
+    }
+
+  /* If we get to here, then the inner loop was never entered;
+   * the outer loop has completed, confirming an all zero header;
+   * return zero, to indicate end of archive.
+   */
+  return 0;
+}
+
+int pkgTarArchiveProcessor::Process()
+{
+  /* Generic method for reading tar archives, and extracting their
+   * content; loops over each archive entry in turn...
+   */
+  while( GetArchiveEntry() > 0 )
+  {
+    /* Found an archive entry; map it to an equivalent file system
+     * path name, within the designated sysroot hierarchy.
+     */
+    char *prefix = *header.field.prefix ? header.field.prefix : NULL;
+    char pathname[mkpath( NULL, sysroot_path, header.field.name, prefix )];
+    mkpath( pathname, sysroot_path, header.field.name, prefix );
+
+    /* Direct further processing to the appropriate handler; (this
+     * is specific to the archive entry classification)...
+     */
+    switch( *header.field.typeflag )
+    {
+      int status;
+      
+      case TAR_ENTITY_TYPE_DIRECTORY:
+        /*
+        * We may need to take some action in respect of directories;
+        * e.g. we may need to create a directory, or even a sequence
+        * of directories, to establish a location within the sysroot
+        * hierarchy...
+        *
+        */
+       status = ProcessDirectory( pathname );
+       break;
+
+      case TAR_ENTITY_TYPE_LINK:
+      case TAR_ENTITY_TYPE_SYMLINK:
+       /*
+        * Links ultimately represent file system entities in
+        * our sysroot hierarchy, but we need special processing
+        * to handle them correctly...
+        *
+        */
+       status = ProcessLinkedEntity( pathname );
+       break;
+
+      case TAR_ENTITY_TYPE_FILE:
+      case TAR_ENTITY_TYPE_ALTFILE:
+       /*
+        * These represent regular files; the file content is
+        * embedded within the archive stream, so we need to be
+        * prepared to read or copy it, as appropriate...
+        *
+        */
+       ProcessDataStream( pathname );
+       break;
+
+      default:
+       /* FIXME: we make no provision for handling any other
+        * type of archive entry; we should provide some more
+        * robust error handling, but for now we simply emit
+        * a diagnostic, and return an error condition code...
+        *
+        */
+       dmh_notify( DMH_ERROR,
+           "unexpected archive entry classification: type %d\n",
+           (int)(*header.field.typeflag)
+         );
+       return -1;
+    }
+  }
+  /* If we didn't bail out before getting to here, then the archive
+   * was processed successfully; return the success code.
+   */
+  return 0;
+}
+
+int pkgTarArchiveProcessor::ProcessEntityData( int fd )
+{
+  /* Generic method for reading past the data associated with
+   * a specific header within a tar archive; if given a negative
+   * value for `fd', it will simply skip over the data, otherwise
+   * `fd' is assumed to represent a descriptor for an opened file
+   * stream, to which the data will be copied (extracted).
+   */
+   int status = 0;
+
+  /* Initialise a counter for the length of the data content, and
+   * specify the default size for the transfer buffer in which to
+   * process it; make the initial size of the transfer buffer 16
+   * times the header size.
+   */
+  uint64_t bytes_to_copy = octval( header.field.size );
+  size_t block_size = sizeof( header ) << 4;
+
+  /* While we still have unread data, and no processing error...
+   */
+  while( (bytes_to_copy > 0) && (status == 0) )
+  {
+    /* Adjust the requested size for the transfer buffer, shrinking
+     * it by 50% at each step, until it is smaller than the remaining
+     * data length, but never smaller than the header record length.
+     */
+    while( (bytes_to_copy < block_size) && (block_size > sizeof( header )) )
+      block_size >>= 1;
+
+    /* Allocate a transfer buffer of the requested size, and populate
+     * it, by reading data from the archive; (since the transfer buffer
+     * is never smaller than the header length, this will also capture
+     * any additional padding bytes, which may be required to keep the
+     * data length equal to an exact multiple of the header length).
+     */
+    char buffer[block_size];
+    if( stream->Read( buffer, block_size ) < (int)(block_size) )
+      /*
+       * Failure to fully populate the transfer buffer, (i.e. a short
+       * read), indicates a corrupt archive; bail out immediately.
+       */
+      return -1;
+
+    /* When the number of actual data bytes expected is fewer than the
+     * total number of bytes in the transfer buffer...
+     */
+    if( bytes_to_copy < block_size )
+      /*
+       * ...then we have reached the end of the data for the current
+       * archived entity; adjust the block size to reflect the number
+       * of actual data bytes present in the transfer buffer...
+       */
+      block_size = bytes_to_copy;
+
+    /* With the number of actual data bytes present now accurately
+     * reflected by the block size, we save that data to the stream
+     * specified for archive extraction, (if any).
+     */
+    if( (fd >= 0) && (write( fd, buffer, block_size ) != (int)(block_size)) )
+      /*
+       * An extraction error occurred; set the status code to
+       * indicate failure.
+       */
+      status = -2;
+
+    /* Adjust the count of remaining unprocessed data bytes, and begin
+     * a new processing cycle, to capture any which may be present.
+     */
+    bytes_to_copy -= block_size;
+  }
+
+  /* Finally, when all data for the current archive entry has been
+   * processed, we return to the caller with an appropriate completion
+   * status code.
+   */
+  return status;
+}
+
+
+/* Here, we implement the methods for installing software from
+ * packages which are distributed in the form of tar archives.
+ *
+ */
+#include <utime.h>
+
+static int commit_saved_entity( const char *pathname, time_t mtime )
+{
+  /* Helper to set the access and modification times for a file,
+   * after extraction from an archive, to match the specified "mtime";
+   * (typically "mtime" is as recorded within the archive).
+   */
+  struct utimbuf timestamp;
+
+  timestamp.actime = timestamp.modtime = mtime;
+  return utime( pathname, &timestamp );
+}
+
+pkgTarArchiveInstaller::
+pkgTarArchiveInstaller( pkgXmlNode *pkg ):pkgTarArchiveProcessor( pkg )
+{
+  /* Constructor: having set up the pkgTarArchiveProcessor base class,
+   * we add a package installation record to the sysroot entry in the
+   * XML database, and mark that sysroot entry as 'modified'.
+   */
+  if( (tarname != NULL) && (sysroot != NULL) )
+  {
+    sysroot->SetAttribute( modified_key, yes_value );
+    pkgXmlNode *installed = new pkgXmlNode( installed_key );
+    installed->SetAttribute( tarname_key, tarname );
+    if( pkgfile != tarname )
+    {
+      pkgXmlNode *download = new pkgXmlNode( download_key );
+      download->SetAttribute( tarname_key, pkgfile );
+      installed->AddChild( download );
+    }
+    sysroot->AddChild( installed );
+  }
+}
+
+int pkgTarArchiveInstaller::ProcessDirectory( const char *pathname )
+{
+#if DEBUGLEVEL < 5
+  int status;
+
+  if( (status = mkdir_recursive( pathname, 0755 )) != 0 )
+    dmh_notify( DMH_ERROR, "cannot create directory `%s'\n", pathname );
+#else
+  int status = 0;
+
+  dmh_printf(
+      "FIXME:ProcessDirectory<stub>:not executing: mkdir -p %s\n",
+       pathname
+    );
+#endif
+  return status;
+}
+
+int pkgTarArchiveInstaller::ProcessDataStream( const char *pathname )
+{
+#if DEBUGLEVEL < 5
+  int fd = set_output_stream( pathname, octval( header.field.mode ) );
+  int status = ProcessEntityData( fd );
+  if( fd >= 0 )
+  {
+    close( fd );
+    if( status == 0 )
+      commit_saved_entity( pathname, octval( header.field.mtime ) );
+
+    else
+    {
+      unlink( pathname );
+      dmh_notify( DMH_ERROR, "%s: extraction failed\n", pathname );
+    }
+  }
+  return status;
+#else
+  dmh_printf(
+      "FIXME:ProcessDataStream<stub>:not extracting: %s\n",
+      pathname
+    );
+  return ProcessEntityData( -1 );
+#endif
+}
+
+/* $RCSfile$: end of file */