From: Keith M. <kei...@us...> - 2009-11-23 20:44:35
|
Update of /cvsroot/mingw/mingw-get/src In directory sfp-cvsdas-1.v30.ch3.sourceforge.com:/tmp/cvs-serv25968/src Modified Files: climain.cpp Added Files: keyword.c pkgbind.cpp pkgfind.cpp pkginet.cpp pkgname.cpp pkgstrm.cpp pkgstrm.h Log Message: Add package download and repository synchronisation machinery. --- NEW FILE: pkgbind.cpp --- /* * pkgbind.cpp * * $Id: pkgbind.cpp,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: pkgbind.cpp,v $: end of file */ Index: climain.cpp =================================================================== RCS file: /cvsroot/mingw/mingw-get/src/climain.cpp,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** climain.cpp 16 Nov 2009 21:54:30 -0000 1.1 --- climain.cpp 23 Nov 2009 20:44:23 -0000 1.2 *************** *** 73,77 **** free( (void *)(dfile) ); - #if 0 /* Merge all package lists, as specified in the "repository" * section of the "profile", into the XML database tree... --- 73,76 ---- *************** *** 83,86 **** --- 82,86 ---- 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 --- NEW FILE: pkgfind.cpp --- /* * pkgfind.cpp * * $Id: pkgfind.cpp,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: pkgfind.cpp,v $: end of file */ --- NEW FILE: pkgname.cpp --- /* * pkgname.cpp * * $Id: pkgname.cpp,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: pkgname.cpp,v $: end of file */ --- NEW FILE: pkginet.cpp --- /* * pkginet.cpp * * $Id: pkginet.cpp,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: pkginet.cpp,v $: end of file */ --- NEW FILE: keyword.c --- /* * keyword.c * * $Id: keyword.c,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: keyword.c,v $: end of file */ --- NEW FILE: pkgstrm.cpp --- /* * pkgstrm.cpp * * $Id: pkgstrm.cpp,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: pkgstrm.cpp,v $: end of file */ --- NEW FILE: pkgstrm.h --- #ifndef PKGSTRM_H /* * pkgstrm.h * * $Id: pkgstrm.h,v 1.1 2009/11/23 20:44:25 keithmarshall Exp $ * * Written by Keith Marshall <kei...@us...> * 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: pkgstrm.h,v $: end of file */ |