fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kio / kio / ktar.cpp
blob9c91b25f265ea6986c8208e38cfd7b65369373a1
1 /* This file is part of the KDE libraries
2 Copyright (C) 2000 David Faure <faure@kde.org>
3 Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include "ktar.h"
22 #include <stdlib.h> // strtol
23 #include <time.h> // time()
24 #include <assert.h>
26 #include <QtCore/QDir>
27 #include <QtCore/QFile>
28 #include <kdebug.h>
29 #include <kmimetype.h>
30 #include <ktemporaryfile.h>
32 #include <kfilterdev.h>
33 #include <kfilterbase.h>
35 #include <kstandarddirs.h>
37 ////////////////////////////////////////////////////////////////////////
38 /////////////////////////// KTar ///////////////////////////////////
39 ////////////////////////////////////////////////////////////////////////
41 class KTar::KTarPrivate
43 public:
44 KTarPrivate(KTar *parent)
45 : q(parent),
46 tarEnd( 0 ),
47 tmpFile( 0 )
51 KTar *q;
52 QStringList dirList;
53 qint64 tarEnd;
54 KTemporaryFile* tmpFile;
55 QString mimetype;
56 QByteArray origFileName;
58 bool fillTempFile(const QString & fileName);
59 bool writeBackTempFile( const QString & fileName );
60 void fillBuffer( char * buffer, const char * mode, qint64 size, time_t mtime,
61 char typeflag, const char * uname, const char * gname );
62 void writeLonglink(char *buffer, const QByteArray &name, char typeflag,
63 const char *uname, const char *gname);
64 qint64 readRawHeader(char *buffer);
65 bool readLonglink(char *buffer, QByteArray &longlink);
66 qint64 readHeader(char *buffer, QString &name, QString &symlink);
69 KTar::KTar( const QString& fileName, const QString & _mimetype )
70 : KArchive( fileName ), d(new KTarPrivate(this))
72 d->mimetype = _mimetype;
75 KTar::KTar( QIODevice * dev )
76 : KArchive( dev ),d(new KTarPrivate(this))
78 Q_ASSERT( dev );
81 // Only called when the a filename was given
82 bool KTar::createDevice( QIODevice::OpenMode mode )
84 Q_UNUSED( mode );
85 if ( d->mimetype.isEmpty() ) // Find out mimetype manually
87 if ( mode != QIODevice::WriteOnly && QFile::exists( fileName() ) )
88 d->mimetype = KMimeType::findByFileContent( fileName() )->name();
89 else
90 d->mimetype = KMimeType::findByPath( fileName(), 0, true )->name();
91 kDebug(7041) << "mimetype=" << d->mimetype;
93 if ( d->mimetype == "application/x-compressed-tar"
94 || d->mimetype == "application/x-tgz" // old deprecated name
95 || d->mimetype == "application/x-targz" // old deprecated name
96 || d->mimetype == "application/x-webarchive" )
98 // that's a gzipped tar file, so ask for gzip filter
99 d->mimetype = "application/x-gzip";
101 else if ( d->mimetype == "application/x-bzip-compressed-tar" ) // that's a bzipped2 tar file, so ask for bz2 filter
103 d->mimetype = "application/x-bzip";
105 else
107 // Something else. Check if it's not really gzip though (e.g. for old-style KOffice files)
108 QFile file( fileName() );
109 if ( file.open( QIODevice::ReadOnly ) )
111 char firstByte, secondByte, thirdByte;
112 if ( file.getChar( &firstByte ) &&
113 file.getChar( &secondByte ) &&
114 file.getChar( &thirdByte ) ) {
115 if ( firstByte == 0037 && static_cast<uchar>(secondByte) == static_cast<uchar>(0213) )
116 d->mimetype = "application/x-gzip";
117 else if ( firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h' )
118 d->mimetype = "application/x-bzip";
119 else if ( firstByte == 'P' && secondByte == 'K' && thirdByte == 3 )
121 char fourthByte;
122 if ( file.getChar(&fourthByte) && fourthByte == 4 )
123 d->mimetype = "application/zip";
127 file.close();
131 if( d->mimetype == "application/x-tar" )
133 return KArchive::createDevice( mode );
135 else
137 // The compression filters are very slow with random access.
138 // So instead of applying the filter to the device,
139 // the file is completely extracted instead,
140 // and we work on the extracted tar file.
141 // This improves the extraction speed by the tar ioslave dramatically,
142 // if the archive file contains many files.
143 // This is because the tar ioslave extracts one file after the other and normally
144 // has to walk through the decompression filter each time.
145 // Which is in fact nearly as slow as a complete decompression for each file.
147 Q_ASSERT(!d->tmpFile);
148 d->tmpFile = new KTemporaryFile();
149 d->tmpFile->setPrefix("ktar-");
150 d->tmpFile->setSuffix(".tar");
151 d->tmpFile->open();
152 kDebug( 7041 ) << "creating tempfile:" << d->tmpFile->fileName();
154 setDevice( d->tmpFile );
155 return true;
159 KTar::~KTar()
161 // mjarrett: Closes to prevent ~KArchive from aborting w/o device
162 if( isOpen() )
163 close();
165 delete d->tmpFile;
166 delete d;
169 void KTar::setOrigFileName( const QByteArray & fileName ) {
170 if ( !isOpen() || !(mode() & QIODevice::WriteOnly) )
172 kWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
173 return;
175 d->origFileName = fileName;
178 qint64 KTar::KTarPrivate::readRawHeader( char *buffer ) {
179 // Read header
180 qint64 n = q->device()->read( buffer, 0x200 );
181 if ( n == 0x200 && buffer[0] != 0 ) {
182 // Make sure this is actually a tar header
183 if (strncmp(buffer + 257, "ustar", 5)) {
184 // The magic isn't there (broken/old tars), but maybe a correct checksum?
186 int check = 0;
187 for( uint j = 0; j < 0x200; ++j )
188 check += buffer[j];
190 // adjust checksum to count the checksum fields as blanks
191 for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
192 check -= buffer[148 + j];
193 check += 8 * ' ';
195 QByteArray s = QByteArray::number( check, 8 ); // octal
197 // only compare those of the 6 checksum digits that mean something,
198 // because the other digits are filled with all sorts of different chars by different tars ...
199 // Some tars right-justify the checksum so it could start in one of three places - we have to check each.
200 if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() )
201 && strncmp( buffer + 148 + 7 - s.length(), s.data(), s.length() )
202 && strncmp( buffer + 148 + 8 - s.length(), s.data(), s.length() ) ) {
203 kWarning(7041) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
204 << "instead of ustar. Reading from wrong pos in file?"
205 << "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );
206 return -1;
208 }/*end if*/
209 } else {
210 // reset to 0 if 0x200 because logical end of archive has been reached
211 if (n == 0x200) n = 0;
212 }/*end if*/
213 return n;
216 bool KTar::KTarPrivate::readLonglink(char *buffer,QByteArray &longlink) {
217 qint64 n = 0;
218 //kDebug() << "reading longlink from pos " << device()->pos();
219 QIODevice *dev = q->device();
220 // read size of longlink from size field in header
221 // size is in bytes including the trailing null (which we ignore)
222 qint64 size = QByteArray( buffer + 0x7c, 12 ).trimmed().toLongLong( 0, 8 /*octal*/ );
224 size--; // ignore trailing null
225 longlink.resize(size);
226 qint64 offset = 0;
227 while (size > 0) {
228 int chunksize = qMin(size, 0x200LL);
229 n = dev->read( longlink.data() + offset, chunksize );
230 if (n == -1) return false;
231 size -= chunksize;
232 offset += 0x200;
233 }/*wend*/
234 // jump over the rest
235 const int skip = 0x200 - (n % 0x200);
236 if (skip < 0x200) {
237 if (dev->read(buffer,skip) != skip)
238 return false;
240 return true;
243 qint64 KTar::KTarPrivate::readHeader( char *buffer, QString &name, QString &symlink ) {
244 name.truncate(0);
245 symlink.truncate(0);
246 while (true) {
247 qint64 n = readRawHeader(buffer);
248 if (n != 0x200) return n;
250 // is it a longlink?
251 if (strcmp(buffer,"././@LongLink") == 0) {
252 char typeflag = buffer[0x9c];
253 QByteArray longlink;
254 readLonglink(buffer,longlink);
255 switch (typeflag) {
256 case 'L': name = QFile::decodeName(longlink); break;
257 case 'K': symlink = QFile::decodeName(longlink); break;
258 }/*end switch*/
259 } else {
260 break;
261 }/*end if*/
262 }/*wend*/
264 // if not result of longlink, read names directly from the header
265 if (name.isEmpty())
266 // there are names that are exactly 100 bytes long
267 // and neither longlink nor \0 terminated (bug:101472)
268 name = QFile::decodeName(QByteArray(buffer, 100));
269 if (symlink.isEmpty())
270 symlink = QFile::decodeName(QByteArray(buffer + 0x9d /*?*/, 100));
272 return 0x200;
276 * If we have created a temporary file, we have
277 * to decompress the original file now and write
278 * the contents to the temporary file.
280 bool KTar::KTarPrivate::fillTempFile( const QString & fileName) {
281 if ( ! tmpFile )
282 return true;
284 kDebug( 7041 ) << "filling tmpFile of mimetype" << mimetype;
286 bool forced = false;
287 if( "application/x-gzip" == mimetype
288 || "application/x-bzip" == mimetype)
289 forced = true;
291 QIODevice *filterDev = KFilterDev::deviceForFile( fileName, mimetype, forced );
293 if( filterDev ) {
294 QFile* file = tmpFile;
295 Q_ASSERT(file->isOpen());
296 Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
297 file->seek(0);
298 QByteArray buffer;
299 buffer.resize(8*1024);
300 if ( ! filterDev->open( QIODevice::ReadOnly ) )
302 delete filterDev;
303 return false;
305 qint64 len = -1;
306 while ( !filterDev->atEnd() && len != 0 ) {
307 len = filterDev->read(buffer.data(),buffer.size());
308 if ( len < 0 ) { // corrupted archive
309 delete filterDev;
310 return false;
312 if ( file->write(buffer.data(), len) != len ) { // disk full
313 delete filterDev;
314 return false;
317 filterDev->close();
318 delete filterDev;
320 file->flush();
321 file->seek(0);
322 Q_ASSERT(file->isOpen());
323 Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
325 else
326 kDebug( 7041 ) << "no filterdevice found!";
328 //kDebug( 7041 ) << "filling tmpFile finished.";
329 return true;
332 bool KTar::openArchive( QIODevice::OpenMode mode ) {
334 if ( !(mode & QIODevice::ReadOnly) )
335 return true;
337 if ( !d->fillTempFile( fileName() ) )
338 return false;
340 // We'll use the permission and user/group of d->rootDir
341 // for any directory we emulate (see findOrCreate)
342 //struct stat buf;
343 //stat( fileName(), &buf );
345 d->dirList.clear();
346 QIODevice* dev = device();
348 if ( !dev )
349 return false;
351 // read dir infos
352 char buffer[ 0x200 ];
353 bool ende = false;
356 QString name;
357 QString symlink;
359 // Read header
360 qint64 n = d->readHeader( buffer, name, symlink );
361 if (n < 0) return false;
362 if (n == 0x200)
364 bool isdir = false;
366 if ( name.endsWith( QLatin1Char( '/' ) ) )
368 isdir = true;
369 name.truncate( name.length() - 1 );
372 int pos = name.lastIndexOf( '/' );
373 QString nm = ( pos == -1 ) ? name : name.mid( pos + 1 );
375 // read access
376 buffer[ 0x6b ] = 0;
377 char *dummy;
378 const char* p = buffer + 0x64;
379 while( *p == ' ' ) ++p;
380 int access = (int)strtol( p, &dummy, 8 );
382 // read user and group
383 QString user( buffer + 0x109 );
384 QString group( buffer + 0x129 );
386 // read time
387 buffer[ 0x93 ] = 0;
388 p = buffer + 0x88;
389 while( *p == ' ' ) ++p;
390 int time = (int)strtol( p, &dummy, 8 );
392 // read type flag
393 char typeflag = buffer[ 0x9c ];
394 // '0' for files, '1' hard link, '2' symlink, '5' for directory
395 // (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
396 // and 'D' for GNU tar extension DUMPDIR
397 if ( typeflag == '5' )
398 isdir = true;
400 bool isDumpDir = false;
401 if ( typeflag == 'D' )
403 isdir = false;
404 isDumpDir = true;
406 //kDebug(7041) << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' );
408 if (isdir)
409 access |= S_IFDIR; // f*cking broken tar files
411 KArchiveEntry* e;
412 if ( isdir )
414 //kDebug(7041) << "directory" << nm;
415 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
417 else
419 // read size
420 QByteArray sizeBuffer( buffer + 0x7c, 12 );
421 qint64 size = sizeBuffer.trimmed().toLongLong( 0, 8 /*octal*/ );
422 //kDebug(7041) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
424 // for isDumpDir we will skip the additional info about that dirs contents
425 if ( isDumpDir )
427 //kDebug(7041) << nm << "isDumpDir";
428 e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
430 else
433 // Let's hack around hard links. Our classes don't support that, so make them symlinks
434 if ( typeflag == '1' )
436 kDebug(7041) << "HARD LINK, setting size to 0 instead of " << size;
437 size = 0; // no contents
440 //kDebug(7041) << "file" << nm << "size=" << size;
441 e = new KArchiveFile( this, nm, access, time, user, group, symlink,
442 dev->pos(), size );
445 // Skip contents + align bytes
446 int rest = size % 0x200;
447 int skip = size + (rest ? 0x200 - rest : 0);
448 //kDebug(7041) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
449 if (! dev->seek( dev->pos() + skip ) )
450 kWarning(7041) << "skipping" << skip << "failed";
453 if ( pos == -1 )
455 if ( nm == "." ) // special case
457 Q_ASSERT( isdir );
458 if ( isdir )
459 setRootDir( static_cast<KArchiveDirectory *>( e ) );
461 else
462 rootDir()->addEntry( e );
464 else
466 // In some tar files we can find dir/./file => call cleanPath
467 QString path = QDir::cleanPath( name.left( pos ) );
468 // Ensure container directory exists, create otherwise
469 KArchiveDirectory * d = findOrCreate( path );
470 d->addEntry( e );
473 else
475 //qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
476 d->tarEnd = dev->pos() - n; // Remember end of archive
477 ende = true;
479 } while( !ende );
480 return true;
484 * Writes back the changes of the temporary file
485 * to the original file.
486 * Must only be called if in QIODevice::WriteOnly mode
488 bool KTar::KTarPrivate::writeBackTempFile( const QString & fileName ) {
489 if ( ! tmpFile )
490 return true;
492 kDebug(7041) << "Write temporary file to compressed file";
493 kDebug(7041) << fileName << " " << mimetype;
495 bool forced = false;
496 if( "application/x-gzip" == mimetype
497 || "application/x-bzip" == mimetype)
498 forced = true;
500 // #### TODO this should use KSaveFile to avoid problems on disk full
501 // (KArchive uses KSaveFile by default, but the temp-uncompressed-file trick
502 // circumvents that).
504 QIODevice *dev = KFilterDev::deviceForFile( fileName, mimetype, forced );
505 if( dev ) {
506 QFile* file = tmpFile;
507 if ( !dev->open(QIODevice::WriteOnly) )
509 file->close();
510 delete dev;
511 return false;
513 if ( forced )
514 static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName );
515 file->seek(0);
516 QByteArray buffer;
517 buffer.resize(8*1024);
518 qint64 len;
519 while ( !file->atEnd()) {
520 len = file->read(buffer.data(), buffer.size());
521 dev->write(buffer.data(),len); // TODO error checking
523 file->close();
524 dev->close();
525 delete dev;
528 kDebug(7041) << "Write temporary file to compressed file done.";
529 return true;
532 bool KTar::closeArchive() {
533 d->dirList.clear();
535 bool ok = true;
537 // If we are in write mode and had created
538 // a temporary tar file, we have to write
539 // back the changes to the original file
540 if( mode() == QIODevice::WriteOnly) {
541 ok = d->writeBackTempFile( fileName() );
542 delete d->tmpFile;
543 d->tmpFile = 0;
544 setDevice(0);
547 return ok;
550 bool KTar::doFinishWriting( qint64 size ) {
551 // Write alignment
552 int rest = size % 0x200;
553 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
554 d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
555 if ( rest )
557 char buffer[ 0x201 ];
558 for( uint i = 0; i < 0x200; ++i )
559 buffer[i] = 0;
560 qint64 nwritten = device()->write( buffer, 0x200 - rest );
561 return nwritten == 0x200 - rest;
563 return true;
566 /*** Some help from the tar sources
567 struct posix_header
568 { byte offset
569 char name[100]; * 0 * 0x0
570 char mode[8]; * 100 * 0x64
571 char uid[8]; * 108 * 0x6c
572 char gid[8]; * 116 * 0x74
573 char size[12]; * 124 * 0x7c
574 char mtime[12]; * 136 * 0x88
575 char chksum[8]; * 148 * 0x94
576 char typeflag; * 156 * 0x9c
577 char linkname[100]; * 157 * 0x9d
578 char magic[6]; * 257 * 0x101
579 char version[2]; * 263 * 0x107
580 char uname[32]; * 265 * 0x109
581 char gname[32]; * 297 * 0x129
582 char devmajor[8]; * 329 * 0x149
583 char devminor[8]; * 337 * ...
584 char prefix[155]; * 345 *
585 * 500 *
589 void KTar::KTarPrivate::fillBuffer( char * buffer,
590 const char * mode, qint64 size, time_t mtime, char typeflag,
591 const char * uname, const char * gname ) {
592 // mode (as in stpos())
593 assert( strlen(mode) == 6 );
594 memcpy( buffer+0x64, mode, 6 );
595 buffer[ 0x6a ] = ' ';
596 buffer[ 0x6b ] = '\0';
598 // dummy uid
599 strcpy( buffer + 0x6c, " 765 ");
600 // dummy gid
601 strcpy( buffer + 0x74, " 144 ");
603 // size
604 QByteArray s = QByteArray::number( size, 8 ); // octal
605 s = s.rightJustified( 11, '0' );
606 memcpy( buffer + 0x7c, s.data(), 11 );
607 buffer[ 0x87 ] = ' '; // space-terminate (no null after)
609 // modification time
610 s = QByteArray::number( static_cast<qulonglong>(mtime), 8 ); // octal
611 s = s.rightJustified( 11, '0' );
612 memcpy( buffer + 0x88, s.data(), 11 );
613 buffer[ 0x93 ] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
615 // spaces, replaced by the check sum later
616 buffer[ 0x94 ] = 0x20;
617 buffer[ 0x95 ] = 0x20;
618 buffer[ 0x96 ] = 0x20;
619 buffer[ 0x97 ] = 0x20;
620 buffer[ 0x98 ] = 0x20;
621 buffer[ 0x99 ] = 0x20;
623 /* From the tar sources :
624 Fill in the checksum field. It's formatted differently from the
625 other fields: it has [6] digits, a null, then a space -- rather than
626 digits, a space, then a null. */
628 buffer[ 0x9a ] = '\0';
629 buffer[ 0x9b ] = ' ';
631 // type flag (dir, file, link)
632 buffer[ 0x9c ] = typeflag;
634 // magic + version
635 strcpy( buffer + 0x101, "ustar");
636 strcpy( buffer + 0x107, "00" );
638 // user
639 strcpy( buffer + 0x109, uname );
640 // group
641 strcpy( buffer + 0x129, gname );
643 // Header check sum
644 int check = 32;
645 for( uint j = 0; j < 0x200; ++j )
646 check += buffer[j];
647 s = QByteArray::number( check, 8 ); // octal
648 s = s.rightJustified( 6, '0' );
649 memcpy( buffer + 0x94, s.constData(), 6 );
652 void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag,
653 const char *uname, const char *gname) {
654 strcpy( buffer, "././@LongLink" );
655 qint64 namelen = name.length() + 1;
656 fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname );
657 q->device()->write( buffer, 0x200 ); // TODO error checking
658 qint64 offset = 0;
659 while (namelen > 0) {
660 int chunksize = qMin(namelen, 0x200LL);
661 memcpy(buffer, name.data()+offset, chunksize);
662 // write long name
663 q->device()->write( buffer, 0x200 ); // TODO error checking
664 // not even needed to reclear the buffer, tar doesn't do it
665 namelen -= chunksize;
666 offset += 0x200;
667 }/*wend*/
670 bool KTar::doPrepareWriting(const QString &name, const QString &user,
671 const QString &group, qint64 size, mode_t perm,
672 time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
673 if ( !isOpen() )
675 kWarning(7041) << "You must open the tar file before writing to it\n";
676 return false;
679 if ( !(mode() & QIODevice::WriteOnly) )
681 kWarning(7041) << "You must open the tar file for writing\n";
682 return false;
685 // In some tar files we can find dir/./file => call cleanPath
686 QString fileName ( QDir::cleanPath( name ) );
689 // Create toplevel dirs
690 // Commented out by David since it's not necessary, and if anybody thinks it is,
691 // he needs to implement a findOrCreate equivalent in writeDir.
692 // But as KTar and the "tar" program both handle tar files without
693 // dir entries, there's really no need for that
694 QString tmp ( fileName );
695 int i = tmp.lastIndexOf( '/' );
696 if ( i != -1 )
698 QString d = tmp.left( i + 1 ); // contains trailing slash
699 if ( !m_dirList.contains( d ) )
701 tmp = tmp.mid( i + 1 );
702 writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
707 char buffer[ 0x201 ];
708 memset( buffer, 0, 0x200 );
709 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
710 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
712 // provide converted stuff we need later on
713 const QByteArray encodedFileName = QFile::encodeName(fileName);
714 const QByteArray uname = user.toLocal8Bit();
715 const QByteArray gname = group.toLocal8Bit();
717 // If more than 100 chars, we need to use the LongLink trick
718 if ( fileName.length() > 99 )
719 d->writeLonglink(buffer,encodedFileName,'L',uname,gname);
721 // Write (potentially truncated) name
722 strncpy( buffer, encodedFileName, 99 );
723 buffer[99] = 0;
724 // zero out the rest (except for what gets filled anyways)
725 memset(buffer+0x9d, 0, 0x200 - 0x9d);
727 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
728 permstr = permstr.rightJustified(6, '0');
729 d->fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname);
731 // Write header
732 return device()->write( buffer, 0x200 ) == 0x200;
735 bool KTar::doWriteDir(const QString &name, const QString &user,
736 const QString &group, mode_t perm,
737 time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
738 if ( !isOpen() )
740 kWarning(7041) << "You must open the tar file before writing to it\n";
741 return false;
744 if ( !(mode() & QIODevice::WriteOnly) )
746 kWarning(7041) << "You must open the tar file for writing\n";
747 return false;
750 // In some tar files we can find dir/./ => call cleanPath
751 QString dirName ( QDir::cleanPath( name ) );
753 // Need trailing '/'
754 if ( !dirName.endsWith( QLatin1Char( '/' ) ) )
755 dirName += QLatin1Char( '/' );
757 if ( d->dirList.contains( dirName ) )
758 return true; // already there
760 char buffer[ 0x201 ];
761 memset( buffer, 0, 0x200 );
762 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
763 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
765 // provide converted stuff we need lateron
766 QByteArray encodedDirname = QFile::encodeName(dirName);
767 QByteArray uname = user.toLocal8Bit();
768 QByteArray gname = group.toLocal8Bit();
770 // If more than 100 chars, we need to use the LongLink trick
771 if ( dirName.length() > 99 )
772 d->writeLonglink(buffer,encodedDirname,'L',uname,gname);
774 // Write (potentially truncated) name
775 strncpy( buffer, encodedDirname, 99 );
776 buffer[99] = 0;
777 // zero out the rest (except for what gets filled anyways)
778 memset(buffer+0x9d, 0, 0x200 - 0x9d);
780 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
781 permstr = permstr.rightJustified(6, ' ');
782 d->fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname);
784 // Write header
785 device()->write( buffer, 0x200 );
786 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
787 d->tarEnd = device()->pos();
789 d->dirList.append( dirName ); // contains trailing slash
790 return true; // TODO if wanted, better error control
793 bool KTar::doWriteSymLink(const QString &name, const QString &target,
794 const QString &user, const QString &group,
795 mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
796 if ( !isOpen() )
798 kWarning(7041) << "You must open the tar file before writing to it\n";
799 return false;
802 if ( !(mode() & QIODevice::WriteOnly) )
804 kWarning(7041) << "You must open the tar file for writing\n";
805 return false;
808 // In some tar files we can find dir/./file => call cleanPath
809 QString fileName ( QDir::cleanPath( name ) );
811 char buffer[ 0x201 ];
812 memset( buffer, 0, 0x200 );
813 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
814 device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
816 // provide converted stuff we need lateron
817 QByteArray encodedFileName = QFile::encodeName(fileName);
818 QByteArray encodedTarget = QFile::encodeName(target);
819 QByteArray uname = user.toLocal8Bit();
820 QByteArray gname = group.toLocal8Bit();
822 // If more than 100 chars, we need to use the LongLink trick
823 if (target.length() > 99)
824 d->writeLonglink(buffer,encodedTarget,'K',uname,gname);
825 if ( fileName.length() > 99 )
826 d->writeLonglink(buffer,encodedFileName,'L',uname,gname);
828 // Write (potentially truncated) name
829 strncpy( buffer, encodedFileName, 99 );
830 buffer[99] = 0;
831 // Write (potentially truncated) symlink target
832 strncpy(buffer+0x9d, encodedTarget, 99);
833 buffer[0x9d+99] = 0;
834 // zero out the rest
835 memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d);
837 QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
838 permstr = permstr.rightJustified(6, ' ');
839 d->fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname);
841 // Write header
842 bool retval = device()->write( buffer, 0x200 ) == 0x200;
843 if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
844 d->tarEnd = device()->pos();
845 return retval;
848 void KTar::virtual_hook( int id, void* data ) {
849 KArchive::virtual_hook( id, data );