mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00
859 lines
30 KiB
C++
859 lines
30 KiB
C++
/* 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 "ktar.h"
|
|
|
|
#include <stdlib.h> // strtol
|
|
#include <time.h> // time()
|
|
#include <assert.h>
|
|
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QFile>
|
|
#include <kdebug.h>
|
|
#include <kmimetype.h>
|
|
#include <ktemporaryfile.h>
|
|
|
|
#include <kfilterdev.h>
|
|
#include <kfilterbase.h>
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////// KTar ///////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
// Mime types of known filters
|
|
static const char application_gzip[] = "application/x-gzip";
|
|
static const char application_bzip[] = "application/x-bzip";
|
|
static const char application_lzma[] = "application/x-lzma";
|
|
static const char application_xz[] = "application/x-xz";
|
|
|
|
class KTar::KTarPrivate
|
|
{
|
|
public:
|
|
KTarPrivate(KTar *parent)
|
|
: q(parent),
|
|
tarEnd( 0 ),
|
|
tmpFile( 0 )
|
|
{
|
|
}
|
|
|
|
KTar *q;
|
|
QStringList dirList;
|
|
qint64 tarEnd;
|
|
KTemporaryFile* tmpFile;
|
|
QString mimetype;
|
|
QByteArray origFileName;
|
|
|
|
bool fillTempFile(const QString & fileName);
|
|
bool writeBackTempFile( const QString & fileName );
|
|
void fillBuffer( char * buffer, const char * mode, qint64 size, time_t mtime,
|
|
char typeflag, const char * uname, const char * gname );
|
|
void writeLonglink(char *buffer, const QByteArray &name, char typeflag,
|
|
const char *uname, const char *gname);
|
|
qint64 readRawHeader(char *buffer);
|
|
bool readLonglink(char *buffer, QByteArray &longlink);
|
|
qint64 readHeader(char *buffer, QString &name, QString &symlink);
|
|
};
|
|
|
|
KTar::KTar( const QString& fileName, const QString & _mimetype )
|
|
: KArchive( fileName ), d(new KTarPrivate(this))
|
|
{
|
|
d->mimetype = _mimetype;
|
|
}
|
|
|
|
KTar::KTar( QIODevice * dev )
|
|
: KArchive( dev ), d(new KTarPrivate(this))
|
|
{
|
|
Q_ASSERT( dev );
|
|
}
|
|
|
|
// Only called when a filename was given
|
|
bool KTar::createDevice(QIODevice::OpenMode mode)
|
|
{
|
|
if (d->mimetype.isEmpty()) {
|
|
// Find out mimetype manually
|
|
|
|
KMimeType::Ptr mime;
|
|
if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
|
|
// Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
|
|
// we can still do the right thing here.
|
|
mime = KMimeType::findByFileContent(fileName());
|
|
if (mime == KMimeType::defaultMimeTypePtr()) {
|
|
// Unable to determine mimetype from contents, get it from file name
|
|
mime = KMimeType::findByPath(fileName(), 0, true);
|
|
}
|
|
} else {
|
|
mime = KMimeType::findByPath(fileName(), 0, true);
|
|
}
|
|
|
|
//kDebug(7041) << mode << mime->name();
|
|
|
|
if (mime->is(QString::fromLatin1("application/x-compressed-tar")) || mime->is(QString::fromLatin1(application_gzip))) {
|
|
// gzipped tar file (with possibly invalid file name), ask for gzip filter
|
|
d->mimetype = QString::fromLatin1(application_gzip);
|
|
} else if (mime->is(QString::fromLatin1("application/x-bzip-compressed-tar")) || mime->is(QString::fromLatin1(application_bzip))) {
|
|
// bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
|
|
d->mimetype = QString::fromLatin1(application_bzip);
|
|
} else if (mime->is(QString::fromLatin1("application/x-lzma-compressed-tar")) || mime->is(QString::fromLatin1(application_lzma))) {
|
|
// lzma compressed tar file (with possibly invalid file name), ask for xz filter
|
|
d->mimetype = QString::fromLatin1(application_lzma);
|
|
} else if (mime->is(QString::fromLatin1("application/x-xz-compressed-tar")) || mime->is(QString::fromLatin1(application_xz))) {
|
|
// xz compressed tar file (with possibly invalid name), ask for xz filter
|
|
d->mimetype = QString::fromLatin1(application_xz);
|
|
}
|
|
}
|
|
|
|
if (d->mimetype == QLatin1String("application/x-tar")) {
|
|
return KArchive::createDevice(mode);
|
|
} else if (mode == QIODevice::WriteOnly) {
|
|
if (!KArchive::createDevice(mode))
|
|
return false;
|
|
if (!d->mimetype.isEmpty()) {
|
|
// Create a compression filter on top of the KSaveFile device that KArchive created.
|
|
//kDebug(7041) << "creating KFilterDev for" << d->mimetype;
|
|
QIODevice *filterDev = KFilterDev::device(device(), d->mimetype);
|
|
Q_ASSERT(filterDev);
|
|
setDevice(filterDev);
|
|
}
|
|
return true;
|
|
} else {
|
|
// The compression filters are very slow with random access.
|
|
// So instead of applying the filter to the device,
|
|
// the file is completely 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.
|
|
|
|
Q_ASSERT(!d->tmpFile);
|
|
d->tmpFile = new KTemporaryFile();
|
|
d->tmpFile->setPrefix(QLatin1String("ktar-"));
|
|
d->tmpFile->setSuffix(QLatin1String(".tar"));
|
|
d->tmpFile->open();
|
|
//kDebug(7041) << "creating tempfile:" << d->tmpFile->fileName();
|
|
|
|
setDevice(d->tmpFile);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
KTar::~KTar()
|
|
{
|
|
// mjarrett: Closes to prevent ~KArchive from aborting w/o device
|
|
if( isOpen() )
|
|
close();
|
|
|
|
delete d->tmpFile;
|
|
delete d;
|
|
}
|
|
|
|
void KTar::setOrigFileName( const QByteArray & fileName ) {
|
|
if ( !isOpen() || !(mode() & QIODevice::WriteOnly) )
|
|
{
|
|
kWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
|
|
return;
|
|
}
|
|
d->origFileName = fileName;
|
|
}
|
|
|
|
qint64 KTar::KTarPrivate::readRawHeader( char *buffer ) {
|
|
// Read header
|
|
qint64 n = q->device()->read( buffer, 0x200 );
|
|
// we need to test if there is a prefix value because the file name can be null
|
|
// and the prefix can have a value and in this case we don't reset n.
|
|
if ( n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 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?
|
|
|
|
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 * ' ';
|
|
|
|
QByteArray s = QByteArray::number( check, 8 ); // octal
|
|
|
|
// 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() ) ) {
|
|
kWarning(7041) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
|
|
<< "instead of ustar. Reading from wrong pos in file?"
|
|
<< "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );
|
|
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::KTarPrivate::readLonglink(char *buffer,QByteArray &longlink) {
|
|
qint64 n = 0;
|
|
//kDebug() << "reading longlink from pos " << q->device()->pos();
|
|
QIODevice *dev = q->device();
|
|
// read size of longlink from size field in header
|
|
// size is in bytes including the trailing null (which we ignore)
|
|
qint64 size = QByteArray( buffer + 0x7c, 12 ).trimmed().toLongLong( 0, 8 /*octal*/ );
|
|
|
|
size--; // ignore trailing null
|
|
longlink.resize(size);
|
|
qint64 offset = 0;
|
|
while (size > 0) {
|
|
int chunksize = qMin(size, 0x200LL);
|
|
n = dev->read( longlink.data() + offset, chunksize );
|
|
if (n == -1) return false;
|
|
size -= chunksize;
|
|
offset += 0x200;
|
|
}/*wend*/
|
|
// jump over the rest
|
|
const int skip = 0x200 - (n % 0x200);
|
|
if (skip <= 0x200) {
|
|
if (dev->read(buffer,skip) != skip)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
qint64 KTar::KTarPrivate::readHeader( char *buffer, QString &name, QString &symlink ) {
|
|
name.truncate(0);
|
|
symlink.truncate(0);
|
|
while (true) {
|
|
qint64 n = readRawHeader(buffer);
|
|
if (n != 0x200) return n;
|
|
|
|
// is it a longlink?
|
|
if (strcmp(buffer,"././@LongLink") == 0) {
|
|
char typeflag = buffer[0x9c];
|
|
QByteArray longlink;
|
|
readLonglink(buffer,longlink);
|
|
switch (typeflag) {
|
|
case 'L': name = QFile::decodeName(longlink); break;
|
|
case 'K': symlink = QFile::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 = QFile::decodeName(QByteArray(buffer, 100));
|
|
if (symlink.isEmpty())
|
|
symlink = QFile::decodeName(QByteArray(buffer + 0x9d /*?*/, 100));
|
|
|
|
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 QString & fileName) {
|
|
if ( ! tmpFile )
|
|
return true;
|
|
|
|
//kDebug(7041) << "filling tmpFile of mimetype" << mimetype;
|
|
|
|
bool forced = false;
|
|
if ( QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype )
|
|
forced = true;
|
|
|
|
QIODevice *filterDev = KFilterDev::deviceForFile( fileName, mimetype, forced );
|
|
|
|
if( filterDev ) {
|
|
QFile* file = tmpFile;
|
|
Q_ASSERT(file->isOpen());
|
|
Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
|
|
file->seek(0);
|
|
QByteArray buffer;
|
|
buffer.resize(8*1024);
|
|
if ( ! filterDev->open( QIODevice::ReadOnly ) )
|
|
{
|
|
delete filterDev;
|
|
return false;
|
|
}
|
|
qint64 len = -1;
|
|
while ( !filterDev->atEnd() && len != 0 ) {
|
|
len = filterDev->read(buffer.data(),buffer.size());
|
|
if ( len < 0 ) { // corrupted archive
|
|
delete filterDev;
|
|
return false;
|
|
}
|
|
if ( file->write(buffer.data(), len) != len ) { // disk full
|
|
delete filterDev;
|
|
return false;
|
|
}
|
|
}
|
|
filterDev->close();
|
|
delete filterDev;
|
|
|
|
file->flush();
|
|
file->seek(0);
|
|
Q_ASSERT(file->isOpen());
|
|
Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
|
|
} else {
|
|
kDebug(7041) << "no filterdevice found!";
|
|
}
|
|
|
|
//kDebug( 7041 ) << "filling tmpFile finished.";
|
|
return true;
|
|
}
|
|
|
|
bool KTar::openArchive( QIODevice::OpenMode mode ) {
|
|
|
|
if ( !(mode & QIODevice::ReadOnly) )
|
|
return true;
|
|
|
|
if ( !d->fillTempFile( 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( fileName(), &buf );
|
|
|
|
d->dirList.clear();
|
|
QIODevice* dev = device();
|
|
|
|
if ( !dev )
|
|
return false;
|
|
|
|
// read dir information
|
|
char buffer[ 0x200 ];
|
|
bool ende = false;
|
|
do
|
|
{
|
|
QString name;
|
|
QString symlink;
|
|
|
|
// Read header
|
|
qint64 n = d->readHeader( buffer, name, symlink );
|
|
if (n < 0) return false;
|
|
if (n == 0x200)
|
|
{
|
|
bool isdir = false;
|
|
|
|
if ( name.endsWith( QLatin1Char( '/' ) ) )
|
|
{
|
|
isdir = true;
|
|
name.truncate( name.length() - 1 );
|
|
}
|
|
|
|
QByteArray prefix = QByteArray(buffer + 0x159, 155);
|
|
if (prefix[0] != '\0') {
|
|
name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name);
|
|
}
|
|
|
|
int pos = name.lastIndexOf( QLatin1Char('/') );
|
|
QString nm = ( pos == -1 ) ? name : 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
|
|
QString user = QString::fromLocal8Bit( buffer + 0x109 );
|
|
QString group = QString::fromLocal8Bit( 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)
|
|
// 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
|
|
// to the next file in the archive and 'g' for Global extended header
|
|
|
|
if ( typeflag == '5' )
|
|
isdir = true;
|
|
|
|
bool isDumpDir = false;
|
|
if ( typeflag == 'D' )
|
|
{
|
|
isdir = false;
|
|
isDumpDir = true;
|
|
}
|
|
//kDebug(7041) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' );
|
|
|
|
if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header
|
|
// Skip it for now. TODO: implement reading of extended header, as per http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html
|
|
(void)dev->read( buffer, 0x200 );
|
|
continue;
|
|
}
|
|
|
|
if (isdir)
|
|
access |= S_IFDIR; // f*cking broken tar files
|
|
|
|
KArchiveEntry* e;
|
|
if ( isdir )
|
|
{
|
|
//kDebug(7041) << "directory" << nm;
|
|
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
|
|
}
|
|
else
|
|
{
|
|
// read size
|
|
QByteArray sizeBuffer( buffer + 0x7c, 12 );
|
|
qint64 size = sizeBuffer.trimmed().toLongLong( 0, 8 /*octal*/ );
|
|
//kDebug(7041) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
|
|
|
|
// for isDumpDir we will skip the additional info about that dirs contents
|
|
if ( isDumpDir )
|
|
{
|
|
//kDebug(7041) << nm << "isDumpDir";
|
|
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' )
|
|
{
|
|
kDebug(7041) << "Hard link, setting size to 0 instead of" << size;
|
|
size = 0; // no contents
|
|
}
|
|
|
|
//kDebug(7041) << "file" << nm << "size=" << size;
|
|
e = new KArchiveFile( this, nm, access, time, user, group, symlink,
|
|
dev->pos(), size );
|
|
}
|
|
|
|
// Skip contents + align bytes
|
|
qint64 rest = size % 0x200;
|
|
qint64 skip = size + (rest ? 0x200 - rest : 0);
|
|
//kDebug(7041) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
|
|
if (! dev->seek( dev->pos() + skip ) )
|
|
kWarning(7041) << "skipping" << skip << "failed";
|
|
}
|
|
|
|
if ( pos == -1 )
|
|
{
|
|
if (nm == QLatin1String(".")) { // 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 cleanPath
|
|
QString path = QDir::cleanPath( name.left( pos ) );
|
|
// Ensure container directory exists, create otherwise
|
|
KArchiveDirectory * d = findOrCreate( path );
|
|
d->addEntry( e );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
|
|
d->tarEnd = dev->pos() - 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 write mode, not in read mode
|
|
*/
|
|
bool KTar::KTarPrivate::writeBackTempFile( const QString & fileName )
|
|
{
|
|
if ( !tmpFile )
|
|
return true;
|
|
|
|
//kDebug(7041) << "Write temporary file to compressed file" << fileName << mimetype;
|
|
|
|
bool forced = false;
|
|
if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype ||
|
|
QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype)
|
|
forced = true;
|
|
|
|
// #### TODO this should use KSaveFile to avoid problems on disk full
|
|
// (KArchive uses KSaveFile by default, but the temp-uncompressed-file trick
|
|
// circumvents that).
|
|
|
|
QIODevice *dev = KFilterDev::deviceForFile( fileName, mimetype, forced );
|
|
if( dev ) {
|
|
QFile* file = tmpFile;
|
|
if ( !dev->open(QIODevice::WriteOnly) )
|
|
{
|
|
file->close();
|
|
delete dev;
|
|
return false;
|
|
}
|
|
if ( forced )
|
|
static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName );
|
|
file->seek(0);
|
|
QByteArray buffer;
|
|
buffer.resize(8*1024);
|
|
qint64 len;
|
|
while ( !file->atEnd()) {
|
|
len = file->read(buffer.data(), buffer.size());
|
|
dev->write(buffer.data(),len); // TODO error checking
|
|
}
|
|
file->close();
|
|
dev->close();
|
|
delete dev;
|
|
}
|
|
|
|
//kDebug(7041) << "Write temporary file to compressed file done.";
|
|
return true;
|
|
}
|
|
|
|
bool KTar::closeArchive() {
|
|
d->dirList.clear();
|
|
|
|
bool ok = true;
|
|
|
|
// If we are in readwrite mode and had created
|
|
// a temporary tar file, we have to write
|
|
// back the changes to the original file
|
|
if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
|
|
ok = d->writeBackTempFile( fileName() );
|
|
delete d->tmpFile;
|
|
d->tmpFile = 0;
|
|
setDevice(0);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool KTar::doFinishWriting( qint64 size ) {
|
|
// Write alignment
|
|
int rest = size % 0x200;
|
|
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
|
|
d->tarEnd = device()->pos() + (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;
|
|
qint64 nwritten = device()->write( 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::KTarPrivate::fillBuffer( char * buffer,
|
|
const char * mode, qint64 size, time_t mtime, char typeflag,
|
|
const char * uname, const char * gname ) {
|
|
// mode (as in stpos())
|
|
assert( strlen(mode) == 6 );
|
|
memcpy( buffer+0x64, mode, 6 );
|
|
buffer[ 0x6a ] = ' ';
|
|
buffer[ 0x6b ] = '\0';
|
|
|
|
// dummy uid
|
|
strcpy( buffer + 0x6c, " 765 ");
|
|
// dummy gid
|
|
strcpy( buffer + 0x74, " 144 ");
|
|
|
|
// size
|
|
QByteArray s = QByteArray::number( size, 8 ); // octal
|
|
s = s.rightJustified( 11, '0' );
|
|
memcpy( buffer + 0x7c, s.data(), 11 );
|
|
buffer[ 0x87 ] = ' '; // space-terminate (no null after)
|
|
|
|
// modification time
|
|
s = QByteArray::number( static_cast<qulonglong>(mtime), 8 ); // octal
|
|
s = s.rightJustified( 11, '0' );
|
|
memcpy( buffer + 0x88, s.data(), 11 );
|
|
buffer[ 0x93 ] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
|
|
|
|
// 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 = QByteArray::number( check, 8 ); // octal
|
|
s = s.rightJustified( 6, '0' );
|
|
memcpy( buffer + 0x94, s.constData(), 6 );
|
|
}
|
|
|
|
void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag,
|
|
const char *uname, const char *gname) {
|
|
strcpy( buffer, "././@LongLink" );
|
|
qint64 namelen = name.length() + 1;
|
|
fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname );
|
|
q->device()->write( buffer, 0x200 ); // TODO error checking
|
|
qint64 offset = 0;
|
|
while (namelen > 0) {
|
|
int chunksize = qMin(namelen, 0x200LL);
|
|
memcpy(buffer, name.data()+offset, chunksize);
|
|
// write long name
|
|
q->device()->write( buffer, 0x200 ); // TODO error checking
|
|
// not even needed to reclear the buffer, tar doesn't do it
|
|
namelen -= chunksize;
|
|
offset += 0x200;
|
|
}/*wend*/
|
|
}
|
|
|
|
bool KTar::doPrepareWriting(const QString &name, const QString &user,
|
|
const QString &group, qint64 size, mode_t perm,
|
|
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
|
|
if ( !isOpen() )
|
|
{
|
|
kWarning(7041) << "You must open the tar file before writing to it\n";
|
|
return false;
|
|
}
|
|
|
|
if ( !(mode() & QIODevice::WriteOnly) )
|
|
{
|
|
kWarning(7041) << "You must open the tar file for writing\n";
|
|
return false;
|
|
}
|
|
|
|
// In some tar files we can find dir/./file => call cleanPath
|
|
QString fileName ( QDir::cleanPath( 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
|
|
QString tmp ( fileName );
|
|
int i = tmp.lastIndexOf( '/' );
|
|
if ( i != -1 )
|
|
{
|
|
QString 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() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
|
|
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
// provide converted stuff we need later on
|
|
const QByteArray encodedFileName = QFile::encodeName(fileName);
|
|
const QByteArray uname = user.toLocal8Bit();
|
|
const QByteArray gname = group.toLocal8Bit();
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
if ( fileName.length() > 99 )
|
|
d->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);
|
|
|
|
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
|
|
permstr = permstr.rightJustified(6, '0');
|
|
d->fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname);
|
|
|
|
// Write header
|
|
return device()->write( buffer, 0x200 ) == 0x200;
|
|
}
|
|
|
|
bool KTar::doWriteDir(const QString &name, const QString &user,
|
|
const QString &group, mode_t perm,
|
|
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
|
|
if ( !isOpen() )
|
|
{
|
|
kWarning(7041) << "You must open the tar file before writing to it\n";
|
|
return false;
|
|
}
|
|
|
|
if ( !(mode() & QIODevice::WriteOnly) )
|
|
{
|
|
kWarning(7041) << "You must open the tar file for writing\n";
|
|
return false;
|
|
}
|
|
|
|
// In some tar files we can find dir/./ => call cleanPath
|
|
QString dirName ( QDir::cleanPath( name ) );
|
|
|
|
// Need trailing '/'
|
|
if ( !dirName.endsWith( QLatin1Char( '/' ) ) )
|
|
dirName += QLatin1Char( '/' );
|
|
|
|
if ( d->dirList.contains( dirName ) )
|
|
return true; // already there
|
|
|
|
char buffer[ 0x201 ];
|
|
memset( buffer, 0, 0x200 );
|
|
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
|
|
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
// provide converted stuff we need lateron
|
|
QByteArray encodedDirname = QFile::encodeName(dirName);
|
|
QByteArray uname = user.toLocal8Bit();
|
|
QByteArray gname = group.toLocal8Bit();
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
if ( dirName.length() > 99 )
|
|
d->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);
|
|
|
|
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
|
|
permstr = permstr.rightJustified(6, ' ');
|
|
d->fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname);
|
|
|
|
// Write header
|
|
device()->write( buffer, 0x200 );
|
|
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
|
|
d->tarEnd = device()->pos();
|
|
|
|
d->dirList.append( dirName ); // contains trailing slash
|
|
return true; // TODO if wanted, better error control
|
|
}
|
|
|
|
bool KTar::doWriteSymLink(const QString &name, const QString &target,
|
|
const QString &user, const QString &group,
|
|
mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
|
|
if ( !isOpen() )
|
|
{
|
|
kWarning(7041) << "You must open the tar file before writing to it\n";
|
|
return false;
|
|
}
|
|
|
|
if ( !(mode() & QIODevice::WriteOnly) )
|
|
{
|
|
kWarning(7041) << "You must open the tar file for writing\n";
|
|
return false;
|
|
}
|
|
|
|
// In some tar files we can find dir/./file => call cleanPath
|
|
QString fileName ( QDir::cleanPath( name ) );
|
|
|
|
char buffer[ 0x201 ];
|
|
memset( buffer, 0, 0x200 );
|
|
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
|
|
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
|
|
|
|
// provide converted stuff we need lateron
|
|
QByteArray encodedFileName = QFile::encodeName(fileName);
|
|
QByteArray encodedTarget = QFile::encodeName(target);
|
|
QByteArray uname = user.toLocal8Bit();
|
|
QByteArray gname = group.toLocal8Bit();
|
|
|
|
// If more than 100 chars, we need to use the LongLink trick
|
|
if (target.length() > 99)
|
|
d->writeLonglink(buffer,encodedTarget,'K',uname,gname);
|
|
if ( fileName.length() > 99 )
|
|
d->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);
|
|
|
|
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
|
|
permstr = permstr.rightJustified(6, ' ');
|
|
d->fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname);
|
|
|
|
// Write header
|
|
bool retval = device()->write( buffer, 0x200 ) == 0x200;
|
|
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
|
|
d->tarEnd = device()->pos();
|
|
return retval;
|
|
}
|
|
|