OSDN Git Service

Add package download and repository synchronisation machinery.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Mon, 23 Nov 2009 20:44:25 +0000 (20:44 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Mon, 23 Nov 2009 20:44:25 +0000 (20:44 +0000)
ChangeLog
Makefile.in
src/climain.cpp
src/keyword.c [new file with mode: 0644]
src/pkgbind.cpp [new file with mode: 0644]
src/pkgfind.cpp [new file with mode: 0644]
src/pkginet.cpp [new file with mode: 0644]
src/pkgname.cpp [new file with mode: 0644]
src/pkgstrm.cpp [new file with mode: 0644]
src/pkgstrm.h [new file with mode: 0644]
xml/profile.xml [new file with mode: 0644]

index e11fc5b..0a9dbb2 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2009-11-23  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Add package download and repository synchronisation machinery.
+
+       * src/pkgstrm.h: New header file.
+
+       * src/pkgbind.cpp, src/pkginet.cpp, src/pkgstrm.cpp,
+       src/pkgfind.cpp, src/pkgname.cpp, src/keyword.c: New files.
+       * Makefile.in (CORE_DLL_OBJECTS): Add build goals for them.
+
+       * xml: New directory.
+
+       * src/climain.cpp (climain): Establish repository bindings from...
+       * xml/profile.xml: ...this new configuration file.
+
 2009-11-16  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Add XML database bindings and preliminary action executive for CLI.
index a8aaaab..2c1e377 100644 (file)
@@ -44,13 +44,14 @@ OBJEXT = @OBJEXT@
 EXEEXT = @EXEEXT@
 
 LDFLAGS = @LDFLAGS@
-LIBS = @LIBS@
+LIBS = -Wl,-Bstatic -lz -lbz2 -llzma -Wl,-Bdynamic -lwininet
 
 CORE_DLL_OBJECTS = climain.$(OBJEXT) \
-   pkgexec.$(OBJEXT) pkginfo.$(OBJEXT) pkgspec.$(OBJEXT) \
+   pkgbind.$(OBJEXT) pkginet.$(OBJEXT) pkgstrm.$(OBJEXT) pkgname.$(OBJEXT) \
+   pkgexec.$(OBJEXT) pkgfind.$(OBJEXT) pkginfo.$(OBJEXT) pkgspec.$(OBJEXT) \
+   mkpath.$(OBJEXT)  xmlfile.$(OBJEXT) keyword.$(OBJEXT) \
    tinyxml.$(OBJEXT) tinyxmlparser.$(OBJEXT) \
    tinystr.$(OBJEXT) tinyxmlerror.$(OBJEXT) \
-   mkpath.$(OBJEXT)  xmlfile.$(OBJEXT) \
    vercmp.$(OBJEXT)  dmh.$(OBJEXT)
 
 %.$(OBJEXT): %.c
@@ -71,9 +72,15 @@ mingw-get-0.dll: $(CORE_DLL_OBJECTS)
        $(CXX) -shared -o $@ $(CXXFLAGS) $(LDFLAGS) $+ $(LIBS)
 
 dmh.$(OBJEXT):     dmh.h
-climain.$(OBJEXT): pkgbase.h pkgtask.h dmh.h
-pkgexec.$(OBJEXT): pkgbase.h pkgtask.h mkpath.h dmh.h
-pkgspec.$(OBJEXT): pkgbase.h pkginfo.h vercmp.h
+climain.$(OBJEXT): pkgbase.h pkgtask.h tinyxml.h tinystr.h dmh.h
+
+pkgname.$(OBJEXT): pkgbase.h dmh.h
+pkgfind.$(OBJEXT): pkgbase.h tinyxml.h tinystr.h
+pkgbind.$(OBJEXT): pkgbase.h tinyxml.h tinystr.h dmh.h
+pkgexec.$(OBJEXT): pkgbase.h pkgtask.h tinyxml.h tinystr.h mkpath.h dmh.h
+pkgspec.$(OBJEXT): pkgbase.h pkginfo.h tinyxml.h tinystr.h vercmp.h
+pkginet.$(OBJEXT): pkgbase.h pkgtask.h pkgstrm.h mkpath.h dmh.h
+pkgstrm.$(OBJEXT): pkgstrm.h
 
 # Dependencies for stand alone pkginfo tool;
 # (the pkginfo object is also required by the core DLL)...
@@ -83,10 +90,10 @@ pkginfo.$(OBJEXT): pkginfo.l pkginfo.h
 
 # TinyXML dependencies...
 #
-tinyxml.$(OBJEXT): tinyxml.h
-tinyxmlerror.$(OBJEXT): tinyxml.h
-tinyxmlparser.$(OBJEXT): tinyxml.h
-tinystr.$(OBJEXT): tinyxml.h
+tinyxml.$(OBJEXT): tinyxml.h tinystr.h
+tinyxmlerror.$(OBJEXT): tinyxml.h tinystr.h
+tinyxmlparser.$(OBJEXT): tinyxml.h tinystr.h
+tinystr.$(OBJEXT): tinystr.h
 
 clean:
        rm -f *.$(OBJEXT) *.dll pkginfo$(EXEEXT) mingw-get$(EXEEXT)
index 63ee2d0..9738e88 100644 (file)
@@ -72,7 +72,6 @@ EXTERN_C int climain( int argc, char **argv )
      */
     free( (void *)(dfile) );
 
-#if 0
     /* Merge all package lists, as specified in the "repository"
      * section of the "profile", into the XML database tree...
      */
@@ -82,6 +81,7 @@ EXTERN_C int climain( int argc, char **argv )
        */
       dmh_notify( DMH_FATAL, "%s: invalid application profile\n", dbase.Value() );
 
+#if 0
     /* Now schedule the specified action for each additionally
      * specified command line argument, (each of which is assumed
      * to represent a package name...
diff --git a/src/keyword.c b/src/keyword.c
new file mode 100644 (file)
index 0000000..8b7c11b
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * keyword.c
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Implementation of "has_keyword()" function; this is used to check
+ * for the presence of a specified keyword with a wihtespace separated
+ * list, appearing as an XML property string.
+ *
+ *
+ * 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 <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define FALSE  0
+#define TRUE   !FALSE
+
+static inline
+char *safe_strdup( const char *src )
+{
+  /* Duplicate a "C" string into dynamically allocated memory,
+   * safely handling a NULL source reference.
+   */
+  return src ? strdup( src ) : NULL;
+}
+
+int has_keyword( const char *keywords, const char *wanted )
+{
+  /* Check the given "keywords" list for the presence of
+   * the "wanted" keyword.
+   */
+  char *inspect;
+  if( (inspect = safe_strdup( keywords )) != NULL )
+  {
+    /* We've found a non-empty list of keywords to inspect;
+     * initialise a pointer to the first entry for matching...
+     */
+    char *match = inspect;
+    while( *match )
+    {
+      /* We haven't yet checked all of the available keywords;
+       * locate the end of the current inspection reference...
+       */
+      char *brk = match;
+      while( *brk && ! isspace( *brk ) )
+       ++brk;
+
+      /* ...and append a NUL terminator.
+       */
+      if( *brk )
+       *brk++ = '\0';
+
+      /* Check the currently selected alias...
+       */
+      if( strcmp( match, wanted ) == 0 )
+      {
+       /* ...and if it's a match, then immediately release the
+        * scratch-pad memory we used for the keyword comparisons,
+        * and return "true".
+        */
+       free( (void *)(inspect) );
+       return TRUE;
+      }
+
+      /* Otherwise, proceed to check the next keyword, if any.
+       */
+      match = brk;
+    }
+
+    /* If we get to here, then all assigned aliases have been
+     * checked, without finding a match; the scratch-pad memory
+     * remains allocated, so release it, before falling through
+     * to return "false".
+     */
+    free( (void *)(inspect) );
+  }
+  /* Return "false" in all cases where no matching name can be found.
+   */
+  return FALSE;
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkgbind.cpp b/src/pkgbind.cpp
new file mode 100644 (file)
index 0000000..b2431fc
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * pkgbind.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Implementation of repository binding for the pkgXmlDocument class.
+ *
+ *
+ * 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 "dmh.h"
+#include "pkgbase.h"
+
+pkgXmlNode *pkgXmlDocument::BindRepositories()
+{
+  /* Identify the repositories specified in the application profile,
+   * and merge their associated package distribution lists into the
+   * active XML database, which is bound to the profile.
+   */
+  pkgXmlNode *dbase = GetRoot();
+
+  /* Before blindly proceeding, perform a sanity check...
+   * Verify that this XML database defines an application profile,
+   * and that the associated application is "mingw-get"...
+   */
+  if( (strcmp( dbase->GetName(), "profile" ) == 0)
+  &&  (strcmp( dbase->GetPropVal( "application", "?" ), "mingw-get") == 0) )
+  {
+    /* Sanity check passed...
+     * Walk the XML data tree, selecting "repository" specifications...
+     */
+    pkgXmlNode *repository = dbase->FindFirstAssociate( "repository" );
+    while( repository != NULL )
+    {
+      /* For each "repository" specified, identify its "catalogues"...
+       *
+       * FIXME: this requires the "package-lists" to be individually
+       * specified within the locally defined "repository" elements;
+       * it should allow for deduction of these, from a specifically
+       * named "repository-index" file identified via the repository
+       * URI template, and hosted by the download server itself.
+       */
+      pkgXmlNode *catalogue = repository->FindFirstAssociate( "package-list" );
+      while( catalogue != NULL )
+      {
+       /* ...and for each named "catalogue"...
+        */
+       const char *dfile, *dname = catalogue->GetPropVal( "catalogue", NULL );
+       if( (dname != NULL) && ((dfile = xmlfile( dname )) != NULL) )
+       {
+         /* Check for a locally cached copy of the "package-list" file...
+          */
+         if( access( dfile, F_OK ) != 0 )
+           /*
+            * When no local copy is available...
+            * Force a "sync", to fetch a copy from the public host.
+            */
+           SyncRepository( dname, repository );
+
+         /* We SHOULD now have a locally cached copy of the package-list;
+          * attempt to merge it into the active profile database...
+          */
+         pkgXmlDocument merge( dfile );
+         if( merge.IsOk() )
+         {
+           /* We successfully loaded the XML catalogue; refer to its
+            * root element...
+            */
+           dmh_printf( "Bind repository: %s\n", merge.Value() );
+           pkgXmlNode *pkglist;
+           if( (pkglist = merge.GetRoot()) != NULL )
+           {
+             /* ...read it, selecting each of the "package-collection"
+              * records contained within it...
+              */
+             pkglist = pkglist->FindFirstAssociate( "package-collection" );
+             while( pkglist != NULL )
+             {
+               /* ...and append a copy of each to the active profile...
+                */
+               dbase->LinkEndChild( pkglist->Clone() );
+
+               /* Move on to the next "package-collection" (if any)
+                * within the current catalogue...
+                */
+               pkglist = pkglist->FindNextAssociate( "package-collection" );
+             }
+           }
+         }
+         else
+           dmh_notify( DMH_WARNING, "Bind repository: FAILED: %s\n", dfile );
+
+         /* However we handled it, the XML file's path name in "dfile" was
+          * allocated on the heap; we lose its reference on termination of
+          * this loop, so we must free it to avoid a memory leak.
+          */
+         free( (void *)(dfile) );
+       }
+
+       /* A repository may comprise an arbitrary collection of software
+        * catalogues; move on, to process the next catalogue (if any) in
+        * the current repository collection.
+        */
+       catalogue = catalogue->FindNextAssociate( "package-list" );
+      }
+
+      /* Similarly, a complete distribution may draw from an arbitrary set
+       * of distinct repositories; move on, to process the next repository
+       * specified (if any).
+       */
+      repository = repository->FindNextAssociate( "repository" );
+    }
+
+    /* On successful completion, return a pointer to the root node
+     * of the active XML profile.
+     */
+    return dbase;
+  }
+
+  /* Fall through on total failure to interpret the profile, returning
+   * NULL to indicate failure.
+   */
+  return NULL;
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkgfind.cpp b/src/pkgfind.cpp
new file mode 100644 (file)
index 0000000..2ddf8a1
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * pkgfind.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Implementation of search routines for locating specified records
+ * within the XML package-collection database.
+ *
+ *
+ * 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 <string.h>
+
+#include "pkgbase.h"
+
+static inline
+bool pkgHasMatchingName( pkgXmlNode *pkg, const char *wanted )
+{
+  /* Helper to locate a package specification by package name;
+   * returns "true" if the XML node under consideration defines
+   * a "package", having the "wanted" name; else "false".
+   */
+  return pkg->IsElementOfType( "package" )
+    /*
+     * subject to the canonical name of the package matching
+     * the "wanted" name, or any assigned package name alias...
+     */
+    &&( (strcmp( wanted, pkg->GetPropVal( "name", "" )) == 0)
+        || (has_keyword( pkg->GetPropVal( "alias", NULL ), wanted ) != 0)
+      );
+}
+
+pkgXmlNode *
+pkgXmlDocument::FindPackageByName( const char *name, const char *subsystem )
+{
+  pkgXmlNode *dir = GetRoot()->GetChildren();
+  /*
+   * Working from the root of the package directory tree...
+   * search all "package-collection" XML nodes, to locate a package
+   * by "name"; return a pointer to the XML node which contains the
+   * specification for the package, or NULL if no such package.
+   */
+  while( dir != NULL )
+  {
+    /* Select only "package-collection" elements...
+     */
+    if( dir->IsElementOfType( "package-collection" )
+    &&  match_if_explicit( subsystem, dir->GetPropVal( "subsystem", NULL )) )
+    {
+      /* ...inspect the content of each...
+       */
+      pkgXmlNode *pkg = dir->GetChildren();
+      while( pkg != NULL )
+      {
+       /* ...returning immediately, if we find a "package"
+        * element with the required "name" property...
+        */
+       if( pkgHasMatchingName( pkg, name ) )
+         return pkg;
+
+       /* ...otherwise, continue searching among any further
+        * entries in the current "package-collection"...
+        */
+       pkg = pkg->GetNext();
+      }
+    }
+    /* ...and ultimately, in any further "package-collection" elements
+     * which may be present.
+     */
+    dir = dir->GetNext();
+  }
+
+  /* If we get to here, we didn't find the required "package";
+   * return NULL, to indicate failure.
+   */
+  return NULL;
+}
+
+static
+pkgXmlNode* pkgFindNextAssociate( pkgXmlNode* pkg, const char* tagname )
+{
+  /* Core implementation for both pkgXmlNode::FindFirstAssociate
+   * and pkgXmlNode::FindNextAssociate methods.  This helper starts
+   * at the node specified by "pkg", examining it, and if necessary,
+   * each of its siblings in turn, until one of an element type
+   * matching "tagname" is found.
+   */
+  while( pkg != NULL )
+  {
+    /* We still have this "pkg" node, not yet examined...
+     */
+    if( pkg->IsElementOfType( tagname ) )
+      /*
+       * ...it matches our search criterion; return it...
+       */
+      return pkg;
+
+    /* The current "pkg" node didn't match our criterion;
+     * move on, to examine its next sibling, if any...
+     */
+    pkg = pkg->GetNext();
+  }
+
+  /* We ran out of siblings to examine, without finding any
+   * to match our criterion; return nothing...
+   */
+  return NULL;
+}
+
+pkgXmlNode*
+pkgXmlNode::FindFirstAssociate( const char* tagname )
+{
+  /* For the node on which this method is invoked,
+   * return the first, if any, of its immediate children,
+   * which is an element of the type specified by "tagname"...
+   */
+  return this ? pkgFindNextAssociate( GetChildren(), tagname ) : NULL;
+}
+
+pkgXmlNode*
+pkgXmlNode::FindNextAssociate( const char* tagname )
+{
+  /* Invoked on any node returned by "FindFirstAssociate",
+   * or on any node already returned by "FindNextAssociate",
+   * return the next sibling node, if any, which is an element
+   * of the type specified by "tagname"...
+   */
+  return this ? pkgFindNextAssociate( GetNext(), tagname ) : NULL;
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkginet.cpp b/src/pkginet.cpp
new file mode 100644 (file)
index 0000000..b021a0b
--- /dev/null
@@ -0,0 +1,496 @@
+/*
+ * pkginet.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Implementation of the package download machinery for mingw-get.
+ *
+ *
+ * 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 WIN32_LEAN_AND_MEAN
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wininet.h>
+#include <errno.h>
+
+#include "dmh.h"
+#include "mkpath.h"
+
+#include "pkgbase.h"
+#include "pkgtask.h"
+
+class pkgInternetAgent
+{
+  /* A minimal, locally implemented class, instantiated ONCE as a
+   * global object, to ensure that wininet's global initialisation is
+   * completed at the proper time, without us doing it explicitly.
+   */
+  private:
+    HINTERNET SessionHandle;
+
+  public:
+    inline pkgInternetAgent():SessionHandle( NULL )
+    {
+      /* Constructor...
+       */
+      if( InternetAttemptConnect( 0 ) == ERROR_SUCCESS )
+       SessionHandle = InternetOpen
+         ( "MinGW Installer", INTERNET_OPEN_TYPE_PRECONFIG,
+            NULL, NULL, 0
+         );
+    }
+    inline ~pkgInternetAgent()
+    {
+      /* Destructor...
+       */
+      if( SessionHandle != NULL )
+       Close( SessionHandle );
+    }
+
+    /* Remaining methods are simple inline wrappers for the
+     * wininet functions we plan to use...
+     */
+    inline HINTERNET OpenURL( const char *URL )
+    {
+      return InternetOpenUrl( SessionHandle, URL, NULL, 0, 0, 0 );
+    }
+    inline int Read( HINTERNET dl, char *buf, size_t max, DWORD *count )
+    {
+      return InternetReadFile( dl, buf, max, count );
+    }
+    inline int Close( HINTERNET id )
+    {
+      return InternetCloseHandle( id );
+    }
+};
+
+/* This is the one and only instantiation of an object of this class.
+ */
+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
+   * gets its own instance of this, either as-is for basic data transfer,
+   * or as a specialised derivative of this base class.
+   */
+  protected:
+    const char *filename;
+    const char *dest_template;
+
+    char *dest_file;
+    HINTERNET dl_host;
+    int dl_status;
+
+  private:
+    virtual int TransferData( int );
+
+  public:
+    pkgInternetStreamingAgent( const char*, const char* );
+    virtual ~pkgInternetStreamingAgent();
+
+    virtual int Get( const char* );
+    inline const char *DestFile(){ return dest_file; }
+};
+
+pkgInternetStreamingAgent::pkgInternetStreamingAgent
+( const char *local_name, const char *dest_specification )
+{
+  /* Constructor for the pkgInternetStreamingAgent class.
+   */
+  filename = local_name;
+  dest_template = dest_specification;
+  dest_file = (char *)(malloc( mkpath( NULL, dest_template, filename, NULL ) ));
+  if( dest_file != NULL )
+    mkpath( dest_file, dest_template, filename, NULL );
+}
+
+pkgInternetStreamingAgent::~pkgInternetStreamingAgent()
+{
+  /* Destructor needs to free the heap memory allocated by the
+   * constructor, for storage of "dest_file" name.
+   */
+  free( (void *)(dest_file) );
+}
+
+int pkgInternetStreamingAgent::TransferData( int fd )
+{
+  /* In the case of this base class implementation,
+   * we simply read the file's data from the Internet source,
+   * and write a verbatim copy to the destination file.
+   */
+  char buf[8192]; DWORD count, tally = 0;
+  do { dl_status = pkgDownloadAgent.Read( dl_host, buf, sizeof( buf ), &count );
+       dmh_printf( "\rdownloading: %s: %I32d b", filename, tally += count );
+       write( fd, buf, count );
+     } while( dl_status && (count > 0) );
+  dmh_printf( "\rdownloading: %s: %I32d b\n", filename, tally );
+  return dl_status;
+}
+
+static const char *get_host_info
+( pkgXmlNode *ref, const char *property, const char *fallback = NULL )
+{
+  /* Helper function to retrieve host information from the XML catalogue.
+   *
+   * Call with property = "url", to retrieve the URL template to pass as
+   * "fmt" argument to mkpath(), or with property = "mirror", to retrieve
+   * the substitution text for the "modifier" argument.
+   */
+  const char *uri = NULL;
+  while( ref != NULL )
+  {
+    /* Starting from the "ref" package entry in the catalogue...
+     */
+    pkgXmlNode *host = ref->FindFirstAssociate( "download-host" );
+    while( host != NULL )
+    {
+      /* Examine its associate tags; if we find one of type
+       * "download-host", with the requisite property, then we
+       * immediately return that property value...
+       */
+      if( (uri = host->GetPropVal( property, NULL )) != NULL )
+       return uri;
+
+      /* Otherwise, we look for any other candidate tags
+       * associated with the same catalogue entry...
+       */
+      host = host->FindNextAssociate( "download-host" );
+    }
+    /* Failing an immediate match, extend the search to the
+     * ancestors of the initial reference entry...
+     */
+    ref = ref->GetParent();
+  }
+  /* ...and ultimately, if no match is found, we return the
+   * specified "fallback" property value.
+   */
+  return fallback;
+}
+
+static inline
+int set_transit_path( const char *path, const char *file, char *buf = NULL )
+{
+  /* Helper to define the transitional path name for downloaded files,
+   * used to save the file data while the download is in progress.
+   */
+  static const char *transit_dir = "/.in-transit";
+  return mkpath( buf, path, file, transit_dir );
+}
+
+int pkgInternetStreamingAgent::Get( const char *from_url )
+{
+  /* Set up a "transit-file" to receive the downloaded content.
+   */
+  char transit_file[set_transit_path( dest_template, filename )];
+  int fd; set_transit_path( dest_template, filename, transit_file );
+
+  if( (fd = set_output_stream( transit_file, 0644 )) >= 0 )
+  {
+    /* The "transit-file" is ready to receive incoming data...
+     * Configure and invoke the download handler to copy the data
+     * from the appropriate host URL, to this "transit-file".
+     */
+    if( (dl_host = pkgDownloadAgent.OpenURL( from_url )) != NULL )
+    {
+      /* With the download transaction fully specified, we may
+       * request processing of the file transfer...
+       */
+      dl_status = TransferData( fd );
+
+      /* We are done with the URL handle; close it.
+       */
+      pkgDownloadAgent.Close( dl_host );
+    }
+
+    /* Always close the "transit-file", whether the download
+     * was successful, or not...
+     */
+    close( fd );
+    if( dl_status )
+      /*
+       * When successful, we move the "transit-file" to its
+       * final downloaded location...
+       */
+      rename( transit_file, dest_file );
+    else
+    {
+      /* ...otherwise, report failure...
+       */
+      dmh_notify( DMH_ERROR, "%s: download failed\n", from_url );
+
+      /* ...and discard the incomplete "transit-file".
+       */
+      unlink( transit_file );
+    }
+  }
+
+  /* Report success or failure to the caller...
+   */
+  return dl_status;
+}
+
+void pkgActionItem::DownloadArchiveFiles( pkgActionItem *current )
+{
+  /* Update the local package cache, to ensure that all packages needed
+   * to complete the current set of scheduled actions are present; if any
+   * are missing, invoke an Internet download agent to fetch them.  This
+   * requires us to walk the action list...
+   */
+  while( current != NULL )
+  {
+    /* ...while we haven't run off the end...
+     */
+    if( (current->flags & ACTION_INSTALL) == ACTION_INSTALL )
+    {
+      /* For all packages specified in the current action list,
+       * for which an "install" action is scheduled, and for which
+       * no associated archive file is present in the local archive
+       * cache, place an Internet download agent on standby to fetch
+       * the required archive from a suitable internet mirror host.
+       */
+      const char *package_name = current->selection->ArchiveName();
+      pkgInternetStreamingAgent download( package_name, current->ArchivePath() );
+
+      /* Check if the required archive is already available locally...
+       */
+      if( (access( download.DestFile(), R_OK ) != 0) && (errno == ENOENT) )
+      {
+       /* ...if not, ask the download agent to fetch it...
+        */
+       const char *url_template = get_host_info( current->selection, "uri" );
+       if( url_template != NULL )
+       {
+         /* ...from the URL constructed from the template specified in
+          * the package repository catalogue (configuration database)...
+          */
+         const char *mirror = get_host_info( current->selection, "mirror" );
+         char package_url[mkpath( NULL, url_template, package_name, mirror )];
+         mkpath( package_url, url_template, package_name, mirror );
+//       dmh_printf( "requesting %s ...\n", package_url );
+         download.Get( package_url );
+       }
+       else
+         /* Cannot download; the repository catalogue didn't specify a
+          * template, from which to construct a download URL...
+          */
+         dmh_notify( DMH_ERROR,
+             "%s: no URL specified for download\n", package_name
+           );
+      }
+    }
+    /* Repeat download action, for any additional packages specified
+     * in the current "actions" list.
+     */
+    current = current->next;
+  }
+}
+
+#define DATA_CACHE_PATH                "%R" "var/cache/mingw-get/data"
+#define WORKING_DATA_PATH      "%R" "var/lib/mingw-get/data"
+
+/* Internet servers host package catalogues in lzma compressed format;
+ * we will decompress them "on the fly", as we download them.  To achieve
+ * this, we will use a variant of the pkgInternetStreamingAgent, using a
+ * specialised TransferData method; additionally, this will incorporate
+ * a special derivative of a pkgLzmaArchiveStream, with its GetRawData
+ * method adapted to stream data from an internet URI, instead of
+ * reading from a local file.
+ *
+ * To derive the pkgInternetLzmaStreamingAgent, we need to include the
+ * specialised declarations of a pkgArchiveStream, in order to make the
+ * declaration of pkgLzmaArchiveStream available as our base class.
+ */
+#define  PKGSTRM_H_SPECIAL  1
+#include "pkgstrm.h"
+
+class pkgInternetLzmaStreamingAgent :
+public pkgInternetStreamingAgent, public pkgLzmaArchiveStream
+{
+  /* Specialisation of the pkgInternetStreamingAgent base class,
+   * providing decompressed copies of LZMA encoded files downloaded
+   * from the Internet; (the LZMA decompression capability is derived
+   * from the pkgLzmaArchiveStream base class).
+   */
+  public:
+    /* We need a specialised constructor...
+     */
+    pkgInternetLzmaStreamingAgent( const char*, const char* );
+
+  private:
+    /* Specialisation requires overrides for each of this pair of
+     * methods, (the first from the pkgLzmaArchiveStream base class;
+     * the second from pkgInternetStreamingAgent).
+     */
+    virtual int GetRawData( int, uint8_t*, size_t );
+    virtual int TransferData( int );
+};
+
+/* This specialisation of the pkgInternetStreamingAgent class needs its
+ * own constructor, simply to invoke the constructors for the base classes,
+ * (since neither is instantiated by a default constructor).
+ */
+pkgInternetLzmaStreamingAgent::pkgInternetLzmaStreamingAgent
+( const char *local_name, const char *dest_specification ):
+pkgInternetStreamingAgent( local_name, dest_specification ),
+pkgLzmaArchiveStream( -1 ){}
+
+int pkgInternetLzmaStreamingAgent::GetRawData( int fd, uint8_t *buf, size_t max )
+{
+  /* Fetch raw (compressed) data from the Internet host, and load it into
+   * the decompression filter's input buffer, whence the TransferData routine
+   * may retrieve it, via the filter, as an uncompressed stream.
+   */
+  DWORD count;
+  dl_status = pkgDownloadAgent.Read( dl_host, (char *)(buf), max, &count );
+  return (int)(count);
+}
+
+int pkgInternetLzmaStreamingAgent::TransferData( int fd )
+{
+  /* In this case, we read the file's data from the Internet source,
+   * stream it through the lzma decompression filter, and write a copy
+   * of the resultant decompressed data to the destination file.
+   */
+  char buf[8192]; DWORD count;
+  do { count = pkgLzmaArchiveStream::Read( buf, sizeof( buf ) );
+       write( fd, buf, count );
+     } while( dl_status && (count > 0) );
+  return dl_status;
+}
+
+static const char *serial_number( const char *catalogue )
+{
+  /* Local helper function to retrieve issue numbers from any repository
+   * package catalogue; returns the result as a duplicate of the internal
+   * string, allocated on the heap (courtesy of the strdup() function).
+   */
+  const char *issue;
+  pkgXmlDocument src( catalogue );
+
+  if(   src.IsOk()
+  &&  ((issue = src.GetRoot()->GetPropVal( "issue", NULL )) != NULL)  )
+    /*
+     * Found an issue number; return a copy...
+     */
+    return strdup( issue );
+
+  /* If we get to here, we couldn't get a valid issue number;
+   * whatever the reason, return NULL to indicate failure.
+   */
+  return NULL;
+}
+
+void pkgXmlDocument::SyncRepository( const char *name, pkgXmlNode *repository )
+{
+  /* Fetch a named package catalogue from a specified Internet repository.
+   *
+   * Package catalogues are XML files; the master copy on the Internet host
+   * must be stored in lzma compressed format, and named to comply with the
+   * convention "%F.xml.lzma", in which "%F" represents the value of the
+   * "name" argument passed to this pkgXmlDocument class method.
+   */ 
+  const char *url_template;
+  if( (url_template = repository->GetPropVal( "uri", NULL )) != NULL )
+  {
+    /* Initialise a streaming agent, to manage the catalogue download;
+     * (note that we must include the "%/M" placeholder in the template
+     * for the local name, to accommodate the name of the intermediate
+     * "in-transit" directory used by the streaming agent).
+     */
+    pkgInternetLzmaStreamingAgent download( name, DATA_CACHE_PATH "%/M/%F.xml" );
+    {
+      /* Construct the full URI for the master catalogue, and stream it to
+       * a locally cached, decompressed copy of the XML file.
+       */
+      const char *mirror = repository->GetPropVal( "mirror", NULL );
+      char catalogue_url[mkpath( NULL, url_template, name, mirror )];
+      mkpath( catalogue_url, url_template, name, mirror );
+      download.Get( catalogue_url );
+    }
+
+    /* We will only replace our current working copy of this catalogue,
+     * (if one already exists), with the copy we just downloaded, if this
+     * downloaded copy bears an issue number indicating that it is more
+     * recent than the working copy.
+     */
+    const char *repository_version, *working_version;
+    if( (repository_version = serial_number( download.DestFile() )) != NULL )
+    {
+      /* Identify the location for the working copy, (if it exists).
+       */
+      const char *working_copy_path_name = WORKING_DATA_PATH "/%F.xml";
+      char working_copy[mkpath( NULL, working_copy_path_name, name, NULL )];
+      mkpath( working_copy, working_copy_path_name, name, NULL );
+
+      /* Compare issue serial numbers...
+       */
+      if( ((working_version = serial_number( working_copy )) == NULL)
+      ||  ((strcmp( repository_version, working_version )) > 0)        )
+      {
+       /* In these circumstances, we couldn't identify an issue number
+        * for the working copy of the catalogue; (maybe there is no such
+        * catalogue, or maybe it doesn't specify a valid issue number);
+        * in either case, we promote the downloaded copy in its place.
+        *
+        * FIXME: we assume that the working file and the downloaded copy
+        * are stored on the same physical file system device, so we may
+        * replace the former by simply deleting it, and renaming the
+        * latter with its original path name; we make no provision for
+        * replacing the working version by physical data copying.
+        */
+       unlink( working_copy );
+       rename( download.DestFile(), working_copy );
+      }
+
+      /* The issue numbers, returned by the serial_number() function, were
+       * allocated on the heap; free them to avoid leaking memory!
+       */
+      free( (void *)(repository_version) );
+      /*
+       * The working copy issue number may be represented by a NULL pointer;
+       * while it may be safe to call free on this, it just *seems* wrong, so
+       * we check it first, to be certain.
+       */
+      if( working_version != NULL )
+       free( (void *)(working_version) );
+    }
+
+    /* If the downloaded copy of the catalogue is still in the download cache,
+     * we have chosen to keep a previous working copy, so we have no further
+     * use for the downloaded copy; discard it, noting that we don't need to
+     * confirm its existence because this will fail silently, if it is no
+     * longer present.
+     */
+    unlink( download.DestFile() );
+  }
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkgname.cpp b/src/pkgname.cpp
new file mode 100644 (file)
index 0000000..02bffd2
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * pkgname.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Implementation for the non-inherited components of the pkgXmlNode
+ * class, as declared in file "pkgdesc.h"; fundamentally, these are
+ * the accessors for package "tarname" properties, as specified in
+ * XML nodes identified as "release" elements.
+ *
+ *
+ * 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 <string.h>
+
+#include "dmh.h"
+#include "pkgbase.h"
+
+static
+const char *pkgArchiveName( pkgXmlNode *rel, const char *tag, unsigned opt )
+{
+  /* Local helper to establish actual release file names...
+   * applicable only to XML "release" elements.
+   */
+  if( ! rel->IsElementOfType( "release" ) )
+  {
+    dmh_control( DMH_BEGIN_DIGEST );
+    dmh_notify( DMH_ERROR, "internal package specification error\n" );
+    dmh_notify( DMH_ERROR, "can't get 'tarname' for non-release element\n" );
+    dmh_notify( DMH_ERROR, "please report this to the package maintainer\n" );
+    dmh_control( DMH_END_DIGEST );
+    return NULL;
+  }
+
+  /* Given a package release specification...
+   * determine the archive name for the tarball to be processed; this
+   * is retrieved from a child XML element with name specified by "tag";
+   * by default, if "opt" is non-zero, it is the canonical "tarname"
+   * assigned to the release element itself, unless an alternative
+   * specification is provided; if "opt" is zero, no default is
+   * assumed, and the return value is NULL if no alternative
+   * specification is provided.
+   */
+  unsigned matched = 0;
+  pkgXmlNode *dl = rel->GetChildren();
+  while( dl != NULL )
+  {
+    /* Visit all children of the release specification element,
+     * checking for the presence of an expected specification...
+     */
+    if( dl->IsElementOfType( tag ) )
+    {
+      /* Found one; ensure it is the only one...
+       */
+      if( matched++ )
+       /*
+        * ...else emit a warning, and ignore this one...
+        */
+       dmh_notify( DMH_WARNING, "%s: archive name reassignment ignored\n",
+           rel->GetPropVal( "tarname", "<unknown>" )
+       );
+      else
+       /* ...ok; this is the first "tag" specification,
+        * accept it as the non-default source of the release's
+        * "tarname" property.
+        */
+       rel = dl;
+    }
+    /* Continue, until all children have been visited.
+     */
+    dl = dl->GetNext();
+  }
+  /* "rel" now points to the XML element having the appropriate
+   * "tarname" specification; return a pointer to it's value.
+   */
+  return (opt || matched) ? rel->GetPropVal( "tarname", NULL ) : NULL;
+}
+
+const char *pkgXmlNode::SourceArchiveName()
+{
+  /* Applicable only for XML nodes designated as "release".
+   *
+   * Retrieve the source tarball name, if specified, from the
+   * "tarname" property of the contained "source" element, within
+   * an XML node designated as a "release" element.
+   *
+   * Returns a pointer to the text of the "tarname" property of the
+   * contained "source" element, or NULL if the containing node does
+   * not represent a "release", or if it does not have a contained
+   * "source" element specifying a "tarname" property.
+   */
+  return pkgArchiveName( this, "source", 0 );
+}
+
+const char *pkgXmlNode::ArchiveName()
+{
+  /* Applicable only for XML nodes designated as "release".
+   *
+   * Retrieve the actual tarball name, if specified, from the
+   * "tarname" property of a contained "download" element, within
+   * an XML node designated as a "release" element.
+   *
+   * Returns a pointer to the text of the "tarname" property of the
+   * contained "download" element, or to the "tarname" property of
+   * the containing "release" element, if it does not contain an
+   * alternative specification within a "download" element; if
+   * unresolved to either of these, returns NULL.
+   */
+  return pkgArchiveName( this, "download", 1 );
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkgstrm.cpp b/src/pkgstrm.cpp
new file mode 100644 (file)
index 0000000..0829dc7
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ * pkgstrm.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Implementation of the streaming data filters, which will be used
+ * for reading package archives in any supported compression format;
+ * currently supported formats are:--
+ *
+ *   raw    (uncompressed)
+ *   gzip   (compressed)
+ *   bzip2  (compressed)
+ *   lzma   (compressed)
+ *   xz     (compressed)
+ *
+ *
+ * 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 <unistd.h>
+#include <fcntl.h>
+
+#ifndef O_BINARY
+/*
+ * MS-Windows nuisances...
+ * Files are expected to be either explicitly text or binary;
+ * (UNIX makes no such specific distinction).  We want to force
+ * treatment of all files as binary; define a "no-op" substitute
+ * for the appropriate MS-Windows attribute, for when we compile
+ * on UNIX, so we may henceforth just use it unconditionally.
+ */
+# ifdef _O_BINARY
+#  define O_BINARY _O_BINARY
+# else
+#  define O_BINARY  0
+# endif
+#endif
+
+/* We need to enable PKGSTRM_H_SPECIAL awareness, when we compile this...
+ */
+#define  PKGSTRM_H_SPECIAL  1
+#include "pkgstrm.h"
+
+/*****
+ *
+ * Class Implementation: pkgArchiveStream
+ *
+ * This class uses a default constructor and default virtual destructor.
+ * We never instantiate objects of this class directly; all derived classes
+ * provide their own specialised constructors and destructors, together with
+ * a mandatory specialised "Read" method.
+ *
+ * We do, however, provide one generic "GetRawData" method, which derived
+ * classes may adopt, or may override, as necessary...
+ *
+ */
+int pkgArchiveStream::GetRawData( int fd, uint8_t *buf, size_t max )
+{
+  /* Generic helper function for reading a compressed data stream into
+   * its decompressing filter's input buffer.  The default implementation
+   * assumes a file stream, and simply invokes a read() request; however,
+   * we segregate this function, to facilitate an override to handle
+   * other input streaming capabilities.
+   */
+  return read( fd, buf, max );
+}
+
+/*****
+ *
+ * Class Implementation: pkgRawArchiveStream
+ *
+ * This is the simplest archive stream class, suitable for archives
+ * which have been stored WITHOUT compression...
+ *
+ */
+pkgRawArchiveStream::pkgRawArchiveStream( const char *filename )
+{
+  /* The constructor has little to to, but to open the archive file
+   * and associate a file descriptor with the resultant data stream.
+   */
+  fd = open( filename, O_RDONLY | O_BINARY );
+}
+
+pkgRawArchiveStream::~pkgRawArchiveStream()
+{
+  /* The destructor needs only to close the data stream.
+   */
+  close( fd );
+}
+
+int pkgRawArchiveStream::Read( char *buf, size_t max )
+{
+  /* While the stream reader simply transfers the requested number
+   * of bytes from the stream, to the caller's buffer.
+   */
+  return read( fd, buf, max );
+}
+
+/*****
+ *
+ * Class Implementation: pkgGzipArchiveStream
+ *
+ * This class creates an input streaming interface, suitable for
+ * reading archives which have been stored with gzip compression.
+ * The implementation is based on the use of libz.a, which allows
+ * for a similar implementation to that of pkgRawArchiveStream.
+ *
+ */
+pkgGzipArchiveStream::pkgGzipArchiveStream( const char *filename )
+{
+  /* Once more, the constructor has little to do but open the stream;
+   * in this case, the method is analogous to C's fopen().
+   */
+  stream = gzopen( filename, "rb" );
+}
+
+pkgGzipArchiveStream::~pkgGzipArchiveStream()
+{
+  /* Another destructor, with little to do but close the stream; the
+   * gzclose() call suffices for the purpose.
+   */
+  gzclose( stream );
+}
+
+int pkgGzipArchiveStream::Read( char *buf, size_t max )
+{
+  /* The reader is again served by a single function call, to transfer
+   * the requested volume of decompressed data from the raw input file
+   * to the caller's buffer.
+   */
+  return gzread( stream, buf, max );
+}
+
+/*****
+ *
+ * Class Implementation: pkgBzipArchiveStream
+ *
+ * This class creates an input streaming interface, suitable for
+ * reading archives which have been stored with bzip2 compression.
+ * The implementation is based on the use of libbz2.a, which again
+ * allows for a fairly simple implementation, which is also quite
+ * analogous to that of pkgRawArchiveStream.
+ *
+ */
+pkgBzipArchiveStream::pkgBzipArchiveStream( const char *filename )
+{
+  /* The constructor carries a marginal additional overhead, in
+   * that it must first open a regular file, before associating
+   * a bzip2 control structure with it; subsequent stream access
+   * is directed exclusively through that control structure.
+   */
+  FILE *streamfile = fopen( filename, "rb" );
+  stream = BZ2_bzReadOpen( &bzerror, streamfile, 0, 0, 0, 0 );
+}
+
+pkgBzipArchiveStream::~pkgBzipArchiveStream()
+{
+  /* For the destructor, it is again just a matter of closing
+   * the bzip2 stream; (this also takes care of closing the
+   * associated file stream).
+   */
+  BZ2_bzReadClose( &bzerror, stream );
+}
+
+int pkgBzipArchiveStream::Read( char *buf, size_t max )
+{
+  /* Once again, reading is a simple matter of transferring
+   * the requisite number of bytes to the caller's buffer.
+   */
+  return BZ2_bzRead( &bzerror, stream, buf, max );
+}
+
+/*****
+ *
+ * Class Implementation: pkgLzmaArchiveStream
+ *
+ * This class creates an input streaming interface, suitable for
+ * reading archives which have been stored with lzma compression;
+ * based on the use of liblzma.a, this implements an adaptation of
+ * Lasse Collin's "xzdec" code, as configured for use as an lzma
+ * decompressor.
+ *
+ */
+static inline
+uint64_t memlimit()
+{
+  /* Naively cap the memory available to lzma and xz decoders.
+   *
+   * FIXME: libarchive appears to use this; however, Lasse Collin
+   * provides a more sophisticated method for xz, based on actual
+   * physical memory footprint; we should adopt it.
+   */
+  return 1ULL << 23 + 1ULL << 21;
+}
+
+static
+void lzma_stream_initialise( lzma_stream *stream )
+{
+  /* This simple helper provides a static template, which is
+   * used to define initial state for lzma and xz decoders.
+   */
+  static const lzma_stream stream_template = LZMA_STREAM_INIT;
+  *stream = stream_template;
+  /*
+   * ...mark the input buffer as initially empty.
+   */
+  stream->avail_in = 0;
+}
+
+pkgLzmaArchiveStream::pkgLzmaArchiveStream( const char *filename )
+{
+  /* The constructor must first open a file stream...
+   */
+  if( (fd = open( filename, O_RDONLY | O_BINARY )) >= 0 )
+  {
+    /* ...then set up the lzma decoder, in appropriately
+     * initialised state...
+     */
+    lzma_stream_initialise( &stream );
+    status = lzma_alone_decoder( &stream, memlimit() );
+  }
+}
+
+pkgLzmaArchiveStream::pkgLzmaArchiveStream( int fileno ):fd( fileno )
+{
+  /* ...then set up the lzma decoder, in appropriately
+   * initialised state...
+   */
+  lzma_stream_initialise( &stream );
+  status = lzma_alone_decoder( &stream, memlimit() );
+}
+
+pkgLzmaArchiveStream::~pkgLzmaArchiveStream()
+{
+  /* The destructor frees memory resources allocated to the decoder,
+   * and closes the input stream file descriptor.
+   *
+   * FIXME: The lzma_alone_decoder may indicate end-of-stream, before
+   * the physical input data stream is exhausted.  For now, we silently
+   * ignore any such residual data; (it is likely to be garbage anyway).
+   * Should we handle it any more explicitly?
+   */
+  lzma_end( &stream );
+  close( fd );
+}
+
+int pkgLzmaArchiveStream::Read( char *buf, size_t max )
+{
+  /* Read an lzma compressed data stream; store up to "max" bytes of
+   * decompressed data into "buf".
+   * 
+   * Start by directing the decoder to use "buf", initially marking it 
+   * as "empty".
+   */
+  stream.next_out = (uint8_t *)(buf);
+  stream.avail_out = max;
+
+  while( (stream.avail_out > 0) && (status == LZMA_OK) )
+  {
+    /* "buf" hasn't been filled yet, and the decoder continues to say
+     * that more data may be available.
+     */
+    if( stream.avail_in == 0 )
+    {
+      /* We exhausted the current content of the raw input buffer;
+       * top it up again.
+       */
+      stream.next_in = streambuf;
+      if( (stream.avail_in = GetRawData( fd, streambuf, BUFSIZ )) < 0 )
+      {
+       /* FIXME: an I/O error occurred here: need to handle it!!!
+        */
+      }
+    }
+
+    /* Run the decoder, to decompress as much as possible of the data
+     * currently in the raw input buffer, filling available space in
+     * "buf"; go round again, in case we exhausted the raw input data
+     * before we ran out of available space in "buf".
+     */
+    status = lzma_code( &stream, LZMA_RUN );
+  }
+
+  /* When we get to here, we either filled "buf" completely, or we
+   * completely exhausted the raw input stream; in either case, we
+   * return the actual number of bytes stored in "buf", (i.e. its
+   * total size, less any residual free space).
+   */
+  return max - stream.avail_out;
+}
+
+/*****
+ *
+ * Class Implementation: pkgXzArchiveStream
+ *
+ * This class creates an input streaming interface, suitable for
+ * reading archives which have been stored with xz compression;
+ * again based on the use of liblzma.a, this implements a further
+ * adaptation of Lasse Collin's "xzdec" code, as configured for
+ * use as an xz decompressor.
+ *
+ */
+pkgXzArchiveStream::pkgXzArchiveStream( const char *filename )
+{
+  /* The constructor must first open a file stream...
+   */
+  if( (fd = open( filename, O_RDONLY | O_BINARY )) >= 0 )
+  {
+    /* ...then set up the lzma decoder, in appropriately
+     * initialised state...
+     */
+    lzma_stream_initialise( &stream );
+    status = lzma_stream_decoder( &stream, memlimit(), LZMA_CONCATENATED );
+
+    /* Finally, recognising that with LZMA_CONCATENATED data,
+     * we will eventually need to switch the decoder from its
+     * initial LZMA_RUN state to LZMA_FINISH, we must provide
+     * a variable to specify the active state, (which we may
+     * initialise for the LZMA_RUN state).
+     */
+    opmode = LZMA_RUN;
+  }
+}
+
+pkgXzArchiveStream::~pkgXzArchiveStream()
+{
+  /* This destructor frees memory resources allocated to the decoder,
+   * and closes the input stream file descriptor; unlike the preceding
+   * case of the lzma_alone_decoder, the lzma_stream_decoder guarantees
+   * that there is no trailing garbage remaining from the input stream.
+   */
+  lzma_end( &stream );
+  close( fd );
+}
+
+int pkgXzArchiveStream::Read( char *buf, size_t max )
+{
+  /* Read an xz compressed data stream; store up to "max" bytes of
+   * decompressed data into "buf".
+   * 
+   * Start by directing the decoder to use "buf", initially marking it 
+   * as "empty".
+   */
+  stream.next_out = (uint8_t *)(buf);
+  stream.avail_out = max;
+
+  while( (stream.avail_out > 0) && (status == LZMA_OK) )
+  {
+    /* "buf" hasn't been filled yet, and the decoder continues to say
+     * that more data may be available.
+     */
+    if( stream.avail_in == 0 )
+    {
+      /* We exhausted the current content of the raw input buffer;
+       * top it up again.
+       */
+      stream.next_in = streambuf;
+      if( (stream.avail_in = GetRawData( fd, streambuf, BUFSIZ )) < 0 )
+      {
+       /* FIXME: an I/O error occurred here: need to handle it!!!
+        */
+      }
+
+      else if( stream.avail_in < BUFSIZ )
+      {
+       /* A short read indicates end-of-input...
+        * Unlike the case of the lzma_alone_decoder, (as used for
+        * decompressing lzma streams), the lzma_stream_decoder, (when
+        * initialised for LZMA_CONCATENATED data, as we use here), may
+        * run lzma_code in either LZMA_RUN or LZMA_FINISH mode; the
+        * normal mode is LZMA_RUN, but we switch to LZMA_FINISH
+        * when we have exhausted the input stream.
+        */
+       opmode = LZMA_FINISH;
+      }
+    }
+
+    /* Run the decoder, to decompress as much as possible of the data
+     * currently in the raw input buffer, filling available space in
+     * "buf"; as noted above, "opmode" will be LZMA_RUN, until we have
+     * exhausted the input stream, when it becomes LZMA_FINISH.
+     */
+    status = lzma_code( &stream, opmode );
+
+    /* We need to go round again, in case we exhausted the raw input
+     * data before we ran out of available space in "buf", except...
+     */
+    if( (status == LZMA_OK) && (opmode == LZMA_FINISH) )
+      /*
+       * ...when we've already achieved the LZMA_FINISH state,
+       * this becomes unnecessary, so we break the cycle.
+       */
+      break;
+  }
+
+  /* When we get to here, we either filled "buf" completely, or we
+   * completely exhausted the raw input stream; in either case, we
+   * return the actual number of bytes stored in "buf", (i.e. its
+   * total size, less any residual free space).
+   */
+  return max - stream.avail_out;
+}
+
+/*****
+ *
+ * Auxiliary function: pkgOpenArchiveStream()
+ *
+ * NOTE: Keep this AFTER the class specialisations, so that their derived
+ * class declarations are visible for object instantiation here!
+ *
+ */
+#include <string.h>
+#include <strings.h>
+
+extern "C" pkgArchiveStream* pkgOpenArchiveStream( const char* filename )
+{
+  /* Naive decompression filter selection, based on file name extension.
+   *
+   * FIXME: adopt more proactive selection method, (similar to that used
+   * by libarchive, perhaps), based on magic patterns within the file.
+   *
+   * NOTE: MS-Windows may use UNICODE file names, but distributed package
+   * archives almost certainly do not.  For our purposes, use of the POSIX
+   * Portable Character Set should suffice; we offer no concessions for
+   * any usage beyond this.
+   */
+  char *ext = strrchr( filename, '.' );
+  if( ext != NULL )
+  {
+    if( strcasecmp( ext, ".gz" ) == 0 )
+      /*
+       * We expect this input stream to be "gzip" compressed,
+       * so we return the appropriate decompressor.
+       */
+      return new pkgGzipArchiveStream( filename );
+
+    else if( strcasecmp( ext, ".bz2" ) == 0 )
+      /*
+       * We expect this input stream to be "bzip2" compressed,
+       * so again, we return the appropriate decompressor.
+       */
+      return new pkgBzipArchiveStream( filename );
+
+    else if( strcasecmp( ext, ".lzma" ) == 0 )
+      /*
+       * We expect this input stream to be "lzma" compressed,
+       * so again, we return the appropriate decompressor.
+       */
+      return new pkgLzmaArchiveStream( filename );
+
+    else if( strcasecmp( ext, ".xz" ) == 0 )
+      /*
+       * We expect this input stream to be "xz" compressed,
+       * so again, we return the appropriate decompressor.
+       */
+      return new pkgXzArchiveStream( filename );
+  }
+
+  /* If we get to here, then we didn't recognise any of the standard
+   * compression indicating file name extensions; fall through, to
+   * process the stream as raw (uncompressed) data.
+   */
+  return new pkgRawArchiveStream( filename );
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkgstrm.h b/src/pkgstrm.h
new file mode 100644 (file)
index 0000000..7a97778
--- /dev/null
@@ -0,0 +1,166 @@
+#ifndef PKGSTRM_H
+/*
+ * pkgstrm.h
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, MinGW Project
+ *
+ *
+ * Declaration of the streaming API, for reading package 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.
+ *
+ */
+#define PKGSTRM_H  1
+
+#include <stdint.h>
+
+class pkgArchiveStream
+{
+  /* Abstract base class...
+   * All archive streaming classes are be derived from this.
+   */
+  public:
+    pkgArchiveStream(){}
+    virtual int Read( char*, size_t ) = 0;
+    virtual ~pkgArchiveStream(){}
+
+  protected:
+    virtual int GetRawData( int, uint8_t*, size_t );
+};
+
+#ifdef PKGSTRM_H_SPECIAL
+/*
+ * Specialisations of the generic base class...
+ * Most clients don't need to be specifically aware of these;
+ * those that do must #define PKGSTRM_H_SPECIAL, before they
+ * #include pkgstrm.h
+ *
+ */
+class pkgRawArchiveStream : public pkgArchiveStream
+{
+  /* A regular (uncompressed) data stream...
+   */
+  protected:
+    int fd;
+
+  public:
+    pkgRawArchiveStream( int );
+    pkgRawArchiveStream( const char* );
+    virtual ~pkgRawArchiveStream();
+
+    virtual int Read( char*, size_t );
+};
+
+/* Compressed data stream classes...
+ */
+#include <zlib.h>
+#include <bzlib.h>
+#ifdef __GNUC__
+/*
+ * lzma.h is broken w.r.t. static vs. dynamic linking; it always
+ * declares all functions with the dllimport attribute, making it
+ * impossible to link with a static liblzma.a, either by using GNU
+ * ld's -Bstatic option in the presence of co-existing liblzma.a
+ * static and liblzma.dll.a import libraries, or in the case where
+ * the import library is not installed.  To work around this defect,
+ * we MUST declare LZMA_API_STATIC before we include lzma.h.  This
+ * DOES NOT in any way interfere with GNU ld's default preference
+ * for dynamic linking; this will still be the effective linking
+ * method if the import library is present, and the -Bstatic
+ * option is not specified.
+ */
+# define LZMA_API_STATIC  1
+#endif
+#include <lzma.h>
+
+class pkgGzipArchiveStream : public pkgArchiveStream
+{
+  /* A stream compressed using the "gzip" algorithm...
+   */
+  protected:
+    gzFile stream;
+
+  public:
+    pkgGzipArchiveStream( int );
+    pkgGzipArchiveStream( const char* );
+    virtual ~pkgGzipArchiveStream();
+
+    virtual int Read( char*, size_t );
+};
+
+class pkgBzipArchiveStream : public pkgArchiveStream
+{
+  /* A stream compressed using the "bzip2" algorithm...
+   */
+  protected:
+    BZFILE *stream;
+    int bzerror;
+
+  public:
+    pkgBzipArchiveStream( int );
+    pkgBzipArchiveStream( const char* );
+    virtual ~pkgBzipArchiveStream();
+
+    virtual int Read( char*, size_t );
+};
+
+class pkgLzmaArchiveStream : public pkgArchiveStream
+{
+  /* A stream compressed using the "lzma" algorithm...
+   */
+  protected:
+    int fd;
+    lzma_stream stream;
+    uint8_t streambuf[BUFSIZ];
+    int status;
+
+  public:
+    pkgLzmaArchiveStream( int );
+    pkgLzmaArchiveStream( const char* );
+    virtual ~pkgLzmaArchiveStream();
+
+    virtual int Read( char*, size_t );
+};
+
+class pkgXzArchiveStream : public pkgArchiveStream
+{
+  /* A stream compressed using the "xz" algorithm...
+   */
+  protected:
+    int fd;
+    lzma_stream stream;
+    uint8_t streambuf[BUFSIZ];
+    lzma_action opmode;
+    int status;
+
+  public:
+    pkgXzArchiveStream( int );
+    pkgXzArchiveStream( const char* );
+    virtual ~pkgXzArchiveStream();
+
+    virtual int Read( char*, size_t );
+};
+
+#endif /* PKGSTRM_H_SPECIAL */
+
+/* A generic helper function, to open an archive stream using
+ * the appropriate specialised stream class...
+ */
+extern "C" pkgArchiveStream *pkgOpenArchiveStream( const char* );
+
+#endif /* PKGSTRM_H: $RCSfile$: end of file */
diff --git a/xml/profile.xml b/xml/profile.xml
new file mode 100644 (file)
index 0000000..b294bb4
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+
+<!-- profile.xml -->
+
+<profile project="MinGW" application="mingw-get">
+  <!--
+    $Id$
+
+    Written by Keith Marshall  <keithmarshall@users.sourceforge.net>
+    Copyright (C) 2009, MinGW Project
+
+
+    Master configuration profile for mingw-get.
+
+    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.
+  -->
+
+  <repository uri="http://prdownloads.sourceforge.net/mingw/%F.xml.lzma?download">
+    <!--
+      The "repository" specification identifies the URI where package
+      list catalogues may be downloaded; each catalogue download URI is
+      identified by substituting the catalogue name for the "%F" field
+      in the uri specification.
+
+      FIXME: package lists specified here will inhibit searching of any
+      master index maintained on the repository server.  At present, the
+      master index search facility is unsupported, so only these locally
+      specified package lists will be loaded; remove them when the index
+      search feature becomes available, to enable master index search.
+    -->
+    <package-list catalogue="mingw-base" />
+  </repository>
+
+  <system-map id="default">
+    <!--
+      The system map specifies the installation paths for each managed
+      subsystem.  Multiple system maps are supported, provided each is
+      given a unique "id" attribute; each specifies an "installation",
+      comprising a collection of subsystems, each of which in turn is
+      associated with a specific "sysroot path".
+      
+      Each individual "sysroot path" defines one installation of one
+      specific subsystem; parallel installations may be supported by
+      assigning distinct paths to two or more sysroot specifications
+      for the same subsystem; each such sysroot specification must
+      then be assigned to a distinct system-map.
+
+      Any single sysroot definition may be shared by any number of
+      system-maps, simply by duplicating that definition within each;
+      however, each system-map may contain only one sysroot definition
+      for each individual subsystem.
+
+      Only one system map may be active at any time.  Unless otherwise
+      specified by user selection, the first encountered is accepted as
+      default, irrespective of its actual "id" attribute value.
+    -->
+    <sysroot subsystem="mingw32" path="c:/mingw" />
+    <sysroot subsystem="MSYS" path="c:/msys/1.0" />
+  </system-map>
+
+</profile>
+<!-- $RCSfile$: end of file -->