|
|
|
/* This file is part of the KDE libraries
|
|
|
|
Copyright (C) 2000 David Faure <faure@kde.org>
|
|
|
|
Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License version 2 as published by the Free Software Foundation.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
//#include <stdio.h>
|
|
|
|
#include <stdlib.h> // strtol
|
|
|
|
#include <time.h> // time()
|
|
|
|
/*#include <unistd.h>
|
|
|
|
#include <grp.h>
|
|
|
|
#include <pwd.h>*/
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include <tqcstring.h>
|
|
|
|
#include <tqdir.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kmimetype.h>
|
|
|
|
#include <tdetempfile.h>
|
|
|
|
|
|
|
|
#include <kfilterdev.h>
|
|
|
|
#include <kfilterbase.h>
|
|
|
|
|
|
|
|
#include "ktar.h"
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
/////////////////////////// KTar ///////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
class KTar::KTarPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
KTarPrivate() : tarEnd( 0 ), tmpFile( 0 ) {}
|
|
|
|
TQStringList dirList;
|
|
|
|
int tarEnd;
|
|
|
|
KTempFile* tmpFile;
|
|
|
|
TQString mimetype;
|
|
|
|
TQCString origFileName;
|
|
|
|
|
|
|
|
bool fillTempFile(const TQString & filename);
|
|
|
|
bool writeBackTempFile( const TQString & filename );
|
|
|
|
};
|
|
|
|
|
|
|
|
KTar::KTar( const TQString& filename, const TQString & _mimetype )
|
|
|
|
: KArchive( 0 )
|
|
|
|
{
|
|
|
|
m_filename = filename;
|
|
|
|
d = new KTarPrivate;
|
|
|
|
TQString mimetype( _mimetype );
|
|
|
|
bool forced = true;
|
|
|
|
if ( mimetype.isEmpty() ) // Find out mimetype manually
|
|
|
|
{
|
|
|
|
if ( TQFile::exists( filename ) )
|
|
|
|
mimetype = KMimeType::findByFileContent( filename )->name();
|
|
|
|
else
|
|
|
|
mimetype = KMimeType::findByPath( filename, 0, true )->name();
|
|
|
|
kdDebug(7041) << "KTar::KTar mimetype = " << mimetype << endl;
|
|
|
|
|
|
|
|
// Don't move to prepareDevice - the other constructor theoretically allows ANY filter
|
|
|
|
if ( mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around
|
|
|
|
mimetype == "application/x-webarchive" )
|
|
|
|
{
|
|
|
|
// that's a gzipped tar file, so ask for gzip filter
|
|
|
|
mimetype = "application/x-gzip";
|
|
|
|
}
|
|
|
|
else if ( mimetype == "application/x-tbz" ) // that's a bzipped2 tar file, so ask for bz2 filter
|
|
|
|
{
|
|
|
|
mimetype = "application/x-bzip2";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Something else. Check if it's not really gzip though (e.g. for KOffice docs)
|
|
|
|
TQFile file( filename );
|
|
|
|
if ( file.open( IO_ReadOnly ) )
|
|
|
|
{
|
|
|
|
unsigned char firstByte = file.getch();
|
|
|
|
unsigned char secondByte = file.getch();
|
|
|
|
unsigned char thirdByte = file.getch();
|
|
|
|
if ( firstByte == 0037 && secondByte == 0213 )
|
|
|
|
mimetype = "application/x-gzip";
|
|
|
|
else if ( firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h' )
|
|
|
|
mimetype = "application/x-bzip2";
|
|
|
|
else if ( firstByte == 'P' && secondByte == 'K' && thirdByte == 3 )
|
|
|
|
{
|
|
|
|
unsigned char fourthByte = file.getch();
|
|
|
|
if ( fourthByte == 4 )
|
|
|
|
mimetype = "application/x-zip";
|
|
|
|
}
|
|
|
|
else if ( firstByte == 0xfd && secondByte == '7' && thirdByte == 'z' )
|
|
|
|
{
|
|
|
|
unsigned char fourthByte = file.getch();
|
|
|
|
unsigned char fifthByte = file.getch();
|
|
|
|
unsigned char sixthByte = file.getch();
|
|
|
|
if ( fourthByte == 'X' && fifthByte == 'Z' && sixthByte == 0x00 )
|
|
|
|
mimetype = "application/x-xz";
|
|
|
|
}
|
|
|
|
else if ( firstByte == 0x5d && secondByte == 0x00 && thirdByte == 0x00 )
|
|
|
|
{
|
|
|
|
unsigned char fourthByte = file.getch();
|
|
|
|
if ( fourthByte == 0x80 )
|
|
|
|
mimetype = "application/x-lzma";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
forced = false;
|
|
|
|
}
|
|
|
|
d->mimetype = mimetype;
|
|
|
|
|
|
|
|
prepareDevice( filename, mimetype, forced );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTar::prepareDevice( const TQString & filename,
|
|
|
|
const TQString & mimetype, bool /*forced*/ )
|
|
|
|
{
|
|
|
|
if( "application/x-tar" == mimetype )
|
|
|
|
setDevice( TQT_TQIODEVICE(new TQFile( filename )) );
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// The compression filters are very slow with random access.
|
|
|
|
// So instead of applying the filter to the device,
|
|
|
|
// the file is completly extracted instead,
|
|
|
|
// and we work on the extracted tar file.
|
|
|
|
// This improves the extraction speed by the tar ioslave dramatically,
|
|
|
|
// if the archive file contains many files.
|
|
|
|
// This is because the tar ioslave extracts one file after the other and normally
|
|
|
|
// has to walk through the decompression filter each time.
|
|
|
|
// Which is in fact nearly as slow as a complete decompression for each file.
|
|
|
|
d->tmpFile = new KTempFile(locateLocal("tmp", "ktar-"),".tar");
|
|
|
|
kdDebug( 7041 ) << "KTar::prepareDevice creating TempFile: " << d->tmpFile->name() << endl;
|
|
|
|
d->tmpFile->setAutoDelete(true);
|
|
|
|
|
|
|
|
// KTempFile opens the file automatically,
|
|
|
|
// the device must be closed, however, for KArchive.setDevice()
|
|
|
|
TQFile* file = d->tmpFile->file();
|
|
|
|
file->close();
|
|
|
|
setDevice(TQT_TQIODEVICE(file));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
KTar::KTar( TQIODevice * dev )
|
|
|
|
: KArchive( dev )
|
|
|
|
{
|
|
|
|
Q_ASSERT( dev );
|
|
|
|
d = new KTarPrivate;
|
|
|
|
}
|
|
|
|
|
|
|
|
KTar::~KTar()
|
|
|
|
{
|
|
|
|
// mjarrett: Closes to prevent ~KArchive from aborting w/o device
|
|
|
|
if( isOpened() )
|
|
|
|
close();
|
|
|
|
|
|
|
|
if (d->tmpFile)
|
|
|
|
delete d->tmpFile; // will delete the device
|
|
|
|
else if ( !m_filename.isEmpty() )
|
|
|
|
delete device(); // we created it ourselves
|
|
|
|
|
|
|
|
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTar::setOrigFileName( const TQCString & fileName )
|
|
|
|
{
|
|
|
|
if ( !isOpened() || !(mode() & IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
d->origFileName = fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_LONG KTar::readRawHeader(char *buffer) {
|
|
|
|
// Read header
|
|
|
|
TQ_LONG n = device()->readBlock( buffer, 0x200 );
|
|
|
|
if ( n == 0x200 && buffer[0] != 0 ) {
|
|
|
|
// Make sure this is actually a tar header
|
|
|
|
if (strncmp(buffer + 257, "ustar", 5)) {
|
|
|
|
// The magic isn't there (broken/old tars), but maybe a correct checksum?
|
|
|
|
TQCString s;
|
|
|
|
|
|
|
|
int check = 0;
|
|
|
|
for( uint j = 0; j < 0x200; ++j )
|
|
|
|
check += buffer[j];
|
|
|
|
|
|
|
|
// adjust checksum to count the checksum fields as blanks
|
|
|
|
for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
|
|
|
|
check -= buffer[148 + j];
|
|
|
|
check += 8 * ' ';
|
|
|
|
|
|
|
|
s.sprintf("%o", check );
|
|
|
|
|
|
|
|
// only compare those of the 6 checksum digits that mean something,
|
|
|
|
// because the other digits are filled with all sorts of different chars by different tars ...
|
|
|
|
// Some tars right-justify the checksum so it could start in one of three places - we have to check each.
|
|
|
|
if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() )
|
|
|
|
&& strncmp( buffer + 148 + 7 - s.length(), s.data(), s.length() )
|
|
|
|
&& strncmp( buffer + 148 + 8 - s.length(), s.data(), s.length() ) ) {
|
|
|
|
kdWarning(7041) << "KTar: invalid TAR file. Header is: " << TQCString( buffer+257, 5 ) << endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}/*end if*/
|
|
|
|
} else {
|
|
|
|
// reset to 0 if 0x200 because logical end of archive has been reached
|
|
|
|
if (n == 0x200) n = 0;
|
|
|
|
}/*end if*/
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::readLonglink(char *buffer,TQCString &longlink) {
|
|
|
|
TQ_LONG n = 0;
|
|
|
|
TQIODevice *dev = device();
|
|
|
|
// read size of longlink from size field in header
|
|
|
|
// size is in bytes including the trailing null (which we ignore)
|
|
|
|
buffer[ 0x88 ] = 0; // was 0x87, but 0x88 fixes BR #26437
|
|
|
|
char *dummy;
|
|
|
|
const char* p = buffer + 0x7c;
|
|
|
|
while( *p == ' ' ) ++p;
|
|
|
|
int size = (int)strtol( p, &dummy, 8 );
|
|
|
|
|
|
|
|
longlink.resize(size);
|
|
|
|
size--; // ignore trailing null
|
|
|
|
dummy = longlink.data();
|
|
|
|
int offset = 0;
|
|
|
|
while (size > 0) {
|
|
|
|
int chunksize = TQMIN(size, 0x200);
|
|
|
|
n = dev->readBlock( dummy + offset, chunksize );
|
|
|
|
if (n == -1) return false;
|
|
|
|
size -= chunksize;
|
|
|
|
offset += 0x200;
|
|
|
|
}/*wend*/
|
|
|
|
// jump over the rest
|
|
|
|
int skip = 0x200 - (n % 0x200);
|
|
|
|
if (skip < 0x200) {
|
|
|
|
if (dev->readBlock(buffer,skip) != skip) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQ_LONG KTar::readHeader(char *buffer,TQString &name,TQString &symlink) {
|
|
|
|
name.truncate(0);
|
|
|
|
symlink.truncate(0);
|
|
|
|
while (true) {
|
|
|
|
TQ_LONG n = readRawHeader(buffer);
|
|
|
|
if (n != 0x200) return n;
|
|
|
|
|
|
|
|
// is it a longlink?
|
|
|
|
if (strcmp(buffer,"././@LongLink") == 0) {
|
|
|
|
char typeflag = buffer[0x9c];
|
|
|
|
TQCString longlink;
|
|
|
|
readLonglink(buffer,longlink);
|
|
|
|
switch (typeflag) {
|
|
|
|
case 'L': name = TQFile::decodeName(longlink); break;
|
|
|
|
case 'K': symlink = TQFile::decodeName(longlink); break;
|
|
|
|
}/*end switch*/
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}/*end if*/
|
|
|
|
}/*wend*/
|
|
|
|
|
|
|
|
// if not result of longlink, read names directly from the header
|
|
|
|
if (name.isEmpty())
|
|
|
|
// there are names that are exactly 100 bytes long
|
|
|
|
// and neither longlink nor \0 terminated (bug:101472)
|
|
|
|
name = TQFile::decodeName(TQCString(buffer, 101));
|
|
|
|
if (symlink.isEmpty())
|
|
|
|
symlink = TQFile::decodeName(TQCString(buffer + 0x9d, 101));
|
|
|
|
|
|
|
|
return 0x200;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we have created a temporary file, we have
|
|
|
|
* to decompress the original file now and write
|
|
|
|
* the contents to the temporary file.
|
|
|
|
*/
|
|
|
|
bool KTar::KTarPrivate::fillTempFile( const TQString & filename) {
|
|
|
|
if ( ! tmpFile )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
kdDebug( 7041 ) <<
|
|
|
|
"KTar::openArchive: filling tmpFile of mimetype '" << mimetype <<
|
|
|
|
"' ... " << endl;
|
|
|
|
|
|
|
|
bool forced = false;
|
|
|
|
if( "application/x-gzip" == mimetype
|
|
|
|
|| "application/x-bzip2" == mimetype
|
|
|
|
|| "application/x-lzma" == mimetype
|
|
|
|
|| "application/x-xz" == mimetype)
|
|
|
|
forced = true;
|
|
|
|
|
|
|
|
TQIODevice *filterDev = KFilterDev::deviceForFile( filename, mimetype, forced );
|
|
|
|
|
|
|
|
if( filterDev ) {
|
|
|
|
TQFile* file = tmpFile->file();
|
|
|
|
file->close();
|
|
|
|
if ( ! file->open( IO_WriteOnly ) )
|
|
|
|
{
|
|
|
|
delete filterDev;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
TQByteArray buffer(8*1024);
|
|
|
|
if ( ! filterDev->open( IO_ReadOnly ) )
|
|
|
|
{
|
|
|
|
delete filterDev;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
TQ_LONG len = -1;
|
|
|
|
while ( !filterDev->atEnd() && len != 0) {
|
|
|
|
len = filterDev->readBlock(buffer.data(),buffer.size());
|
|
|
|
if ( len < 0 ) { // corrupted archive
|
|
|
|
delete filterDev;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
file->writeBlock(buffer.data(),len);
|
|
|
|
}
|
|
|
|
filterDev->close();
|
|
|
|
delete filterDev;
|
|
|
|
|
|
|
|
file->close();
|
|
|
|
if ( ! file->open( IO_ReadOnly ) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
kdDebug( 7041 ) << "KTar::openArchive: no filterdevice found!" << endl;
|
|
|
|
|
|
|
|
kdDebug( 7041 ) << "KTar::openArchive: filling tmpFile finished." << endl;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::openArchive( int mode )
|
|
|
|
{
|
|
|
|
kdDebug( 7041 ) << "KTar::openArchive" << endl;
|
|
|
|
if ( !(mode & IO_ReadOnly) )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if ( !d->fillTempFile( m_filename ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// We'll use the permission and user/group of d->rootDir
|
|
|
|
// for any directory we emulate (see findOrCreate)
|
|
|
|
//struct stat buf;
|
|
|
|
//stat( m_filename, &buf );
|
|
|
|
|
|
|
|
d->dirList.clear();
|
|
|
|
TQIODevice* dev = device();
|
|
|
|
|
|
|
|
if ( !dev )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// read dir infos
|
|
|
|
char buffer[ 0x200 ];
|
|
|
|
bool ende = false;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
TQString name;
|
|
|
|
TQString symlink;
|
|
|
|
|
|
|
|
// Read header
|
|
|
|
TQ_LONG n = readHeader(buffer,name,symlink);
|
|
|
|
if (n < 0) return false;
|
|
|
|
if (n == 0x200)
|
|
|
|
{
|
|
|
|
bool isdir = false;
|
|
|
|
TQString nm;
|
|
|
|
|
|
|
|
if ( name.right(1) == "/" )
|
|
|
|
{
|
|
|
|
isdir = true;
|
|
|
|
name = name.left( name.length() - 1 );
|
|
|
|
}
|
|
|
|
|
|
|
|
int pos = name.findRev( '/' );
|
|
|
|
if ( pos == -1 )
|
|
|
|
nm = name;
|
|
|
|
else
|
|
|
|
nm = name.mid( pos + 1 );
|
|
|
|
|
|
|
|
// read access
|
|
|
|
buffer[ 0x6b ] = 0;
|
|
|
|
char *dummy;
|
|
|
|
const char* p = buffer + 0x64;
|
|
|
|
while( *p == ' ' ) ++p;
|
|
|
|
int access = (int)strtol( p, &dummy, 8 );
|
|
|
|
|
|
|
|
// read user and group
|
|
|
|
TQString user( buffer + 0x109 );
|
|
|
|
TQString group( buffer + 0x129 );
|
|
|
|
|
|
|
|
// read time
|
|
|
|
buffer[ 0x93 ] = 0;
|
|
|
|
p = buffer + 0x88;
|
|
|
|
while( *p == ' ' ) ++p;
|
|
|
|
int time = (int)strtol( p, &dummy, 8 );
|
|
|
|
|
|
|
|
// read type flag
|
|
|
|
char typeflag = buffer[ 0x9c ];
|
|
|
|
// '0' for files, '1' hard link, '2' symlink, '5' for directory
|
|
|
|
// (and 'L' for longlink filenames, 'K' for longlink symlink targets)
|
|
|
|
// and 'D' for GNU tar extension DUMPDIR
|
|
|
|
if ( typeflag == '5' )
|
|
|
|
isdir = true;
|
|
|
|
|
|
|
|
bool isDumpDir = false;
|
|
|
|
if ( typeflag == 'D' )
|
|
|
|
{
|
|
|
|
isdir = false;
|
|
|
|
isDumpDir = true;
|
|
|
|
}
|
|
|
|
//bool islink = ( typeflag == '1' || typeflag == '2' );
|
|
|
|
//kdDebug(7041) << "typeflag=" << typeflag << " islink=" << islink << endl;
|
|
|
|
|
|
|
|
if (isdir)
|
|
|
|
access |= S_IFDIR; // f*cking broken tar files
|
|
|
|
|
|
|
|
KArchiveEntry* e;
|
|
|
|
if ( isdir )
|
|
|
|
{
|
|
|
|
//kdDebug(7041) << "KTar::openArchive directory " << nm << endl;
|
|
|
|
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// read size
|
|
|
|
buffer[ 0x88 ] = 0; // was 0x87, but 0x88 fixes BR #26437
|
|
|
|
char *dummy;
|
|
|
|
const char* p = buffer + 0x7c;
|
|
|
|
while( *p == ' ' ) ++p;
|
|
|
|
int size = (int)strtol( p, &dummy, 8 );
|
|
|
|
|
|
|
|
// for isDumpDir we will skip the additional info about that dirs contents
|
|
|
|
if ( isDumpDir )
|
|
|
|
{
|
|
|
|
//kdDebug(7041) << "KTar::openArchive " << nm << " isDumpDir" << endl;
|
|
|
|
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
|
|
|
// Let's hack around hard links. Our classes don't support that, so make them symlinks
|
|
|
|
if ( typeflag == '1' )
|
|
|
|
{
|
|
|
|
kdDebug(7041) << "HARD LINK, setting size to 0 instead of " << size << endl;
|
|
|
|
size = 0; // no contents
|
|
|
|
}
|
|
|
|
|
|
|
|
//kdDebug(7041) << "KTar::openArchive file " << nm << " size=" << size << endl;
|
|
|
|
e = new KArchiveFile( this, nm, access, time, user, group, symlink,
|
|
|
|
dev->at(), size );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip contents + align bytes
|
|
|
|
int rest = size % 0x200;
|
|
|
|
int skip = size + (rest ? 0x200 - rest : 0);
|
|
|
|
//kdDebug(7041) << "KTar::openArchive, at()=" << dev->at() << " rest=" << rest << " skipping " << skip << endl;
|
|
|
|
if (! dev->at( dev->at() + skip ) )
|
|
|
|
kdWarning(7041) << "KTar::openArchive skipping " << skip << " failed" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pos == -1 )
|
|
|
|
{
|
|
|
|
if ( nm == "." ) // special case
|
|
|
|
{
|
|
|
|
Q_ASSERT( isdir );
|
|
|
|
if ( isdir )
|
|
|
|
setRootDir( static_cast<KArchiveDirectory *>( e ) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
rootDir()->addEntry( e );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// In some tar files we can find dir/./file => call cleanDirPath
|
|
|
|
TQString path = TQDir::cleanDirPath( name.left( pos ) );
|
|
|
|
// Ensure container directory exists, create otherwise
|
|
|
|
KArchiveDirectory * d = findOrCreate( path );
|
|
|
|
d->addEntry( e );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//tqDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
|
|
|
|
d->tarEnd = dev->at() - n; // Remember end of archive
|
|
|
|
ende = true;
|
|
|
|
}
|
|
|
|
} while( !ende );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Writes back the changes of the temporary file
|
|
|
|
* to the original file.
|
|
|
|
* Must only be called if in IO_WriteOnly mode
|
|
|
|
*/
|
|
|
|
bool KTar::KTarPrivate::writeBackTempFile( const TQString & filename ) {
|
|
|
|
if ( ! tmpFile )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
kdDebug(7041) << "Write temporary file to compressed file" << endl;
|
|
|
|
kdDebug(7041) << filename << " " << mimetype << endl;
|
|
|
|
|
|
|
|
bool forced = false;
|
|
|
|
if( "application/x-gzip" == mimetype
|
|
|
|
|| "application/x-bzip2" == mimetype
|
|
|
|
|| "application/x-lzma" == mimetype
|
|
|
|
|| "application/x-xz" == mimetype)
|
|
|
|
forced = true;
|
|
|
|
|
|
|
|
TQIODevice *dev = KFilterDev::deviceForFile( filename, mimetype, forced );
|
|
|
|
if( dev ) {
|
|
|
|
TQFile* file = tmpFile->file();
|
|
|
|
file->close();
|
|
|
|
if ( ! file->open(IO_ReadOnly) || ! dev->open(IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
file->close();
|
|
|
|
delete dev;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( forced )
|
|
|
|
static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName );
|
|
|
|
TQByteArray buffer(8*1024);
|
|
|
|
TQ_LONG len;
|
|
|
|
while ( ! file->atEnd()) {
|
|
|
|
len = file->readBlock(buffer.data(),buffer.size());
|
|
|
|
dev->writeBlock(buffer.data(),len);
|
|
|
|
}
|
|
|
|
file->close();
|
|
|
|
dev->close();
|
|
|
|
delete dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
kdDebug(7041) << "Write temporary file to compressed file done." << endl;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::closeArchive()
|
|
|
|
{
|
|
|
|
d->dirList.clear();
|
|
|
|
|
|
|
|
// If we are in write mode and had created
|
|
|
|
// a temporary tar file, we have to write
|
|
|
|
// back the changes to the original file
|
|
|
|
if( mode() == IO_WriteOnly)
|
|
|
|
return d->writeBackTempFile( m_filename );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::writeDir( const TQString& name, const TQString& user, const TQString& group )
|
|
|
|
{
|
|
|
|
mode_t perm = 040755;
|
|
|
|
time_t the_time = time(0);
|
|
|
|
return writeDir(name,user,group,perm,the_time,the_time,the_time);
|
|
|
|
#if 0
|
|
|
|
if ( !isOpened() )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::writeDir: You must open the tar file before writing to it\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !(mode() & IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::writeDir: You must open the tar file for writing\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In some tar files we can find dir/./ => call cleanDirPath
|
|
|
|
TQString dirName ( TQDir::cleanDirPath( name ) );
|
|
|
|
|
|
|
|
// Need trailing '/'
|
|
|
|
if ( dirName.right(1) != "/" )
|
|
|
|
dirName += "/";
|
|
|
|
|
|
|
|
if ( d->dirList.contains( dirName ) )
|
|
|
|
return true; // already there
|
|
|
|
|
|
|
|
char buffer[ 0x201 ];
|
|
|
|
memset( buffer, 0, 0x200 );
|
|
|
|
if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
|
|
if ( dirName.length() > 99 )
|
|
|
|
{
|
|
|
|
strcpy( buffer, "././@LongLink" );
|
|
|
|
fillBuffer( buffer, " 0", dirName.length()+1, 'L', user.local8Bit(), group.local8Bit() );
|
|
|
|
device()->writeBlock( buffer, 0x200 );
|
|
|
|
strncpy( buffer, TQFile::encodeName(dirName), 0x200 );
|
|
|
|
buffer[0x200] = 0;
|
|
|
|
// write long name
|
|
|
|
device()->writeBlock( buffer, 0x200 );
|
|
|
|
// not even needed to reclear the buffer, tar doesn't do it
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Write name
|
|
|
|
strncpy( buffer, TQFile::encodeName(dirName), 0x200 );
|
|
|
|
buffer[0x200] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fillBuffer( buffer, " 40755", 0, 0x35, user.local8Bit(), group.local8Bit());
|
|
|
|
|
|
|
|
// Write header
|
|
|
|
device()->writeBlock( buffer, 0x200 );
|
|
|
|
if ( mode() & IO_ReadWrite ) d->tarEnd = device()->at();
|
|
|
|
|
|
|
|
d->dirList.append( dirName ); // contains trailing slash
|
|
|
|
return true; // TODO if wanted, better error control
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::prepareWriting( const TQString& name, const TQString& user, const TQString& group, uint size )
|
|
|
|
{
|
|
|
|
mode_t dflt_perm = 0100644;
|
|
|
|
time_t the_time = time(0);
|
|
|
|
return prepareWriting(name,user,group,size,dflt_perm,
|
|
|
|
the_time,the_time,the_time);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::doneWriting( uint size )
|
|
|
|
{
|
|
|
|
// Write alignment
|
|
|
|
int rest = size % 0x200;
|
|
|
|
if ( mode() & IO_ReadWrite )
|
|
|
|
d->tarEnd = device()->at() + (rest ? 0x200 - rest : 0); // Record our new end of archive
|
|
|
|
if ( rest )
|
|
|
|
{
|
|
|
|
char buffer[ 0x201 ];
|
|
|
|
for( uint i = 0; i < 0x200; ++i )
|
|
|
|
buffer[i] = 0;
|
|
|
|
TQ_LONG nwritten = device()->writeBlock( buffer, 0x200 - rest );
|
|
|
|
return nwritten == 0x200 - rest;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*** Some help from the tar sources
|
|
|
|
struct posix_header
|
|
|
|
{ byte offset
|
|
|
|
char name[100]; * 0 * 0x0
|
|
|
|
char mode[8]; * 100 * 0x64
|
|
|
|
char uid[8]; * 108 * 0x6c
|
|
|
|
char gid[8]; * 116 * 0x74
|
|
|
|
char size[12]; * 124 * 0x7c
|
|
|
|
char mtime[12]; * 136 * 0x88
|
|
|
|
char chksum[8]; * 148 * 0x94
|
|
|
|
char typeflag; * 156 * 0x9c
|
|
|
|
char linkname[100]; * 157 * 0x9d
|
|
|
|
char magic[6]; * 257 * 0x101
|
|
|
|
char version[2]; * 263 * 0x107
|
|
|
|
char uname[32]; * 265 * 0x109
|
|
|
|
char gname[32]; * 297 * 0x129
|
|
|
|
char devmajor[8]; * 329 * 0x149
|
|
|
|
char devminor[8]; * 337 * ...
|
|
|
|
char prefix[155]; * 345 *
|
|
|
|
* 500 *
|
|
|
|
};
|
|
|
|
*/
|
|
|
|
|
|
|
|
void KTar::fillBuffer( char * buffer,
|
|
|
|
const char * mode, int size, time_t mtime, char typeflag,
|
|
|
|
const char * uname, const char * gname )
|
|
|
|
{
|
|
|
|
// mode (as in stat())
|
|
|
|
assert( strlen(mode) == 6 );
|
|
|
|
strcpy( buffer+0x64, mode );
|
|
|
|
buffer[ 0x6a ] = ' ';
|
|
|
|
buffer[ 0x6b ] = '\0';
|
|
|
|
|
|
|
|
// dummy uid
|
|
|
|
strcpy( buffer + 0x6c, " 765 ");
|
|
|
|
// dummy gid
|
|
|
|
strcpy( buffer + 0x74, " 144 ");
|
|
|
|
|
|
|
|
// size
|
|
|
|
TQCString s;
|
|
|
|
s.sprintf("%o", size); // OCT
|
|
|
|
s = s.rightJustify( 11, ' ' );
|
|
|
|
strcpy( buffer + 0x7c, s.data() );
|
|
|
|
buffer[ 0x87 ] = ' '; // space-terminate (no null after)
|
|
|
|
|
|
|
|
// modification time
|
|
|
|
s.sprintf("%lo", static_cast<unsigned long>(mtime) ); // OCT
|
|
|
|
s = s.rightJustify( 11, ' ' );
|
|
|
|
strcpy( buffer + 0x88, s.data() );
|
|
|
|
buffer[ 0x93 ] = ' '; // space-terminate (no null after)
|
|
|
|
|
|
|
|
// spaces, replaced by the check sum later
|
|
|
|
buffer[ 0x94 ] = 0x20;
|
|
|
|
buffer[ 0x95 ] = 0x20;
|
|
|
|
buffer[ 0x96 ] = 0x20;
|
|
|
|
buffer[ 0x97 ] = 0x20;
|
|
|
|
buffer[ 0x98 ] = 0x20;
|
|
|
|
buffer[ 0x99 ] = 0x20;
|
|
|
|
|
|
|
|
/* From the tar sources :
|
|
|
|
Fill in the checksum field. It's formatted differently from the
|
|
|
|
other fields: it has [6] digits, a null, then a space -- rather than
|
|
|
|
digits, a space, then a null. */
|
|
|
|
|
|
|
|
buffer[ 0x9a ] = '\0';
|
|
|
|
buffer[ 0x9b ] = ' ';
|
|
|
|
|
|
|
|
// type flag (dir, file, link)
|
|
|
|
buffer[ 0x9c ] = typeflag;
|
|
|
|
|
|
|
|
// magic + version
|
|
|
|
strcpy( buffer + 0x101, "ustar");
|
|
|
|
strcpy( buffer + 0x107, "00" );
|
|
|
|
|
|
|
|
// user
|
|
|
|
strcpy( buffer + 0x109, uname );
|
|
|
|
// group
|
|
|
|
strcpy( buffer + 0x129, gname );
|
|
|
|
|
|
|
|
// Header check sum
|
|
|
|
int check = 32;
|
|
|
|
for( uint j = 0; j < 0x200; ++j )
|
|
|
|
check += buffer[j];
|
|
|
|
s.sprintf("%o", check ); // OCT
|
|
|
|
s = s.rightJustify( 7, ' ' );
|
|
|
|
strcpy( buffer + 0x94, s.data() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTar::writeLonglink(char *buffer, const TQCString &name, char typeflag,
|
|
|
|
const char *uname, const char *gname) {
|
|
|
|
strcpy( buffer, "././@LongLink" );
|
|
|
|
int namelen = name.length() + 1;
|
|
|
|
fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname );
|
|
|
|
device()->writeBlock( buffer, 0x200 );
|
|
|
|
int offset = 0;
|
|
|
|
while (namelen > 0) {
|
|
|
|
int chunksize = TQMIN(namelen, 0x200);
|
|
|
|
memcpy(buffer, name.data()+offset, chunksize);
|
|
|
|
// write long name
|
|
|
|
device()->writeBlock( buffer, 0x200 );
|
|
|
|
// not even needed to reclear the buffer, tar doesn't do it
|
|
|
|
namelen -= chunksize;
|
|
|
|
offset += 0x200;
|
|
|
|
}/*wend*/
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::prepareWriting(const TQString& name, const TQString& user,
|
|
|
|
const TQString& group, uint size, mode_t perm,
|
|
|
|
time_t atime, time_t mtime, time_t ctime) {
|
|
|
|
return KArchive::prepareWriting(name,user,group,size,perm,atime,mtime,ctime);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::prepareWriting_impl(const TQString &name, const TQString &user,
|
|
|
|
const TQString &group, uint size, mode_t perm,
|
|
|
|
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
|
|
|
|
if ( !isOpened() )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::prepareWriting: You must open the tar file before writing to it\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !(mode() & IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::prepareWriting: You must open the tar file for writing\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In some tar files we can find dir/./file => call cleanDirPath
|
|
|
|
TQString fileName ( TQDir::cleanDirPath( name ) );
|
|
|
|
|
|
|
|
/*
|
|
|
|
// Create toplevel dirs
|
|
|
|
// Commented out by David since it's not necessary, and if anybody thinks it is,
|
|
|
|
// he needs to implement a findOrCreate equivalent in writeDir.
|
|
|
|
// But as KTar and the "tar" program both handle tar files without
|
|
|
|
// dir entries, there's really no need for that
|
|
|
|
TQString tmp ( fileName );
|
|
|
|
int i = tmp.findRev( '/' );
|
|
|
|
if ( i != -1 )
|
|
|
|
{
|
|
|
|
TQString d = tmp.left( i + 1 ); // contains trailing slash
|
|
|
|
if ( !m_dirList.contains( d ) )
|
|
|
|
{
|
|
|
|
tmp = tmp.mid( i + 1 );
|
|
|
|
writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
char buffer[ 0x201 ];
|
|
|
|
memset( buffer, 0, 0x200 );
|
|
|
|
if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
|
|
|
|
// provide converted stuff we need lateron
|
|
|
|
TQCString encodedFilename = TQFile::encodeName(fileName);
|
|
|
|
TQCString uname = user.local8Bit();
|
|
|
|
TQCString gname = group.local8Bit();
|
|
|
|
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
|
|
if ( fileName.length() > 99 )
|
|
|
|
writeLonglink(buffer,encodedFilename,'L',uname,gname);
|
|
|
|
|
|
|
|
// Write (potentially truncated) name
|
|
|
|
strncpy( buffer, encodedFilename, 99 );
|
|
|
|
buffer[99] = 0;
|
|
|
|
// zero out the rest (except for what gets filled anyways)
|
|
|
|
memset(buffer+0x9d, 0, 0x200 - 0x9d);
|
|
|
|
|
|
|
|
TQCString permstr;
|
|
|
|
permstr.sprintf("%o",perm);
|
|
|
|
permstr = permstr.rightJustify(6, ' ');
|
|
|
|
fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname);
|
|
|
|
|
|
|
|
// Write header
|
|
|
|
return device()->writeBlock( buffer, 0x200 ) == 0x200;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::writeDir(const TQString& name, const TQString& user,
|
|
|
|
const TQString& group, mode_t perm,
|
|
|
|
time_t atime, time_t mtime, time_t ctime) {
|
|
|
|
return KArchive::writeDir(name,user,group,perm,atime,mtime,ctime);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::writeDir_impl(const TQString &name, const TQString &user,
|
|
|
|
const TQString &group, mode_t perm,
|
|
|
|
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
|
|
|
|
if ( !isOpened() )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::writeDir: You must open the tar file before writing to it\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !(mode() & IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::writeDir: You must open the tar file for writing\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In some tar files we can find dir/./ => call cleanDirPath
|
|
|
|
TQString dirName ( TQDir::cleanDirPath( name ) );
|
|
|
|
|
|
|
|
// Need trailing '/'
|
|
|
|
if ( dirName.right(1) != "/" )
|
|
|
|
dirName += "/";
|
|
|
|
|
|
|
|
if ( d->dirList.contains( dirName ) )
|
|
|
|
return true; // already there
|
|
|
|
|
|
|
|
char buffer[ 0x201 ];
|
|
|
|
memset( buffer, 0, 0x200 );
|
|
|
|
if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
|
|
|
|
// provide converted stuff we need lateron
|
|
|
|
TQCString encodedDirname = TQFile::encodeName(dirName);
|
|
|
|
TQCString uname = user.local8Bit();
|
|
|
|
TQCString gname = group.local8Bit();
|
|
|
|
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
|
|
if ( dirName.length() > 99 )
|
|
|
|
writeLonglink(buffer,encodedDirname,'L',uname,gname);
|
|
|
|
|
|
|
|
// Write (potentially truncated) name
|
|
|
|
strncpy( buffer, encodedDirname, 99 );
|
|
|
|
buffer[99] = 0;
|
|
|
|
// zero out the rest (except for what gets filled anyways)
|
|
|
|
memset(buffer+0x9d, 0, 0x200 - 0x9d);
|
|
|
|
|
|
|
|
TQCString permstr;
|
|
|
|
permstr.sprintf("%o",perm);
|
|
|
|
permstr = permstr.rightJustify(6, ' ');
|
|
|
|
fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname);
|
|
|
|
|
|
|
|
// Write header
|
|
|
|
device()->writeBlock( buffer, 0x200 );
|
|
|
|
if ( mode() & IO_ReadWrite ) d->tarEnd = device()->at();
|
|
|
|
|
|
|
|
d->dirList.append( dirName ); // contains trailing slash
|
|
|
|
return true; // TODO if wanted, better error control
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::writeSymLink(const TQString &name, const TQString &target,
|
|
|
|
const TQString &user, const TQString &group,
|
|
|
|
mode_t perm, time_t atime, time_t mtime, time_t ctime) {
|
|
|
|
return KArchive::writeSymLink(name,target,user,group,perm,atime,mtime,ctime);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KTar::writeSymLink_impl(const TQString &name, const TQString &target,
|
|
|
|
const TQString &user, const TQString &group,
|
|
|
|
mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
|
|
|
|
if ( !isOpened() )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::writeSymLink: You must open the tar file before writing to it\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !(mode() & IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
kdWarning(7041) << "KTar::writeSymLink: You must open the tar file for writing\n";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
device()->flush();
|
|
|
|
|
|
|
|
// In some tar files we can find dir/./file => call cleanDirPath
|
|
|
|
TQString fileName ( TQDir::cleanDirPath( name ) );
|
|
|
|
|
|
|
|
char buffer[ 0x201 ];
|
|
|
|
memset( buffer, 0, 0x200 );
|
|
|
|
if ( mode() & IO_ReadWrite ) device()->at(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
|
|
|
|
// provide converted stuff we need lateron
|
|
|
|
TQCString encodedFilename = TQFile::encodeName(fileName);
|
|
|
|
TQCString encodedTarget = TQFile::encodeName(target);
|
|
|
|
TQCString uname = user.local8Bit();
|
|
|
|
TQCString gname = group.local8Bit();
|
|
|
|
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
|
|
if (target.length() > 99)
|
|
|
|
writeLonglink(buffer,encodedTarget,'K',uname,gname);
|
|
|
|
if ( fileName.length() > 99 )
|
|
|
|
writeLonglink(buffer,encodedFilename,'L',uname,gname);
|
|
|
|
|
|
|
|
// Write (potentially truncated) name
|
|
|
|
strncpy( buffer, encodedFilename, 99 );
|
|
|
|
buffer[99] = 0;
|
|
|
|
// Write (potentially truncated) symlink target
|
|
|
|
strncpy(buffer+0x9d, encodedTarget, 99);
|
|
|
|
buffer[0x9d+99] = 0;
|
|
|
|
// zero out the rest
|
|
|
|
memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d);
|
|
|
|
|
|
|
|
TQCString permstr;
|
|
|
|
permstr.sprintf("%o",perm);
|
|
|
|
permstr = permstr.rightJustify(6, ' ');
|
|
|
|
fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname);
|
|
|
|
|
|
|
|
// Write header
|
|
|
|
bool retval = device()->writeBlock( buffer, 0x200 ) == 0x200;
|
|
|
|
if ( mode() & IO_ReadWrite ) d->tarEnd = device()->at();
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KTar::virtual_hook( int id, void* data ) {
|
|
|
|
switch (id) {
|
|
|
|
case VIRTUAL_WRITE_SYMLINK: {
|
|
|
|
WriteSymlinkParams *params = reinterpret_cast<WriteSymlinkParams *>(data);
|
|
|
|
params->retval = writeSymLink_impl(*params->name,*params->target,
|
|
|
|
*params->user,*params->group,params->perm,
|
|
|
|
params->atime,params->mtime,params->ctime);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case VIRTUAL_WRITE_DIR: {
|
|
|
|
WriteDirParams *params = reinterpret_cast<WriteDirParams *>(data);
|
|
|
|
params->retval = writeDir_impl(*params->name,*params->user,
|
|
|
|
*params->group,params->perm,
|
|
|
|
params->atime,params->mtime,params->ctime);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case VIRTUAL_PREPARE_WRITING: {
|
|
|
|
PrepareWritingParams *params = reinterpret_cast<PrepareWritingParams *>(data);
|
|
|
|
params->retval = prepareWriting_impl(*params->name,*params->user,
|
|
|
|
*params->group,params->size,params->perm,
|
|
|
|
params->atime,params->mtime,params->ctime);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
KArchive::virtual_hook( id, data );
|
|
|
|
}/*end switch*/
|
|
|
|
}
|
|
|
|
|