+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.
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
$(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)...
# 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)
*/
free( (void *)(dfile) );
-#if 0
/* Merge all package lists, as specified in the "repository"
* section of the "profile", into the XML database tree...
*/
*/
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...
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+#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 */
--- /dev/null
+<?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 -->