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