kio Library API Documentation

karchive.cpp

00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2000 David Faure <faure@kde.org> 00003 Copyright (C) 2003 Leo Savernik <l.savernik@aon.at> 00004 00005 Moved from ktar.cpp by Roberto Teixeira <maragato@kde.org> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Library General Public 00009 License version 2 as published by the Free Software Foundation. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00019 Boston, MA 02111-1307, USA. 00020 */ 00021 00022 #include <stdio.h> 00023 #include <stdlib.h> 00024 #include <time.h> 00025 #include <unistd.h> 00026 #include <errno.h> 00027 #include <grp.h> 00028 #include <pwd.h> 00029 #include <assert.h> 00030 #include <sys/types.h> 00031 #include <sys/stat.h> 00032 00033 #include <qptrlist.h> 00034 #include <qptrstack.h> 00035 #include <qvaluestack.h> 00036 #include <qmap.h> 00037 #include <qcstring.h> 00038 #include <qdir.h> 00039 #include <qfile.h> 00040 00041 #include <kdebug.h> 00042 #include <kfilterdev.h> 00043 #include <kfilterbase.h> 00044 #include <kde_file.h> 00045 00046 #include "karchive.h" 00047 #include "klimitediodevice.h" 00048 00049 template class QDict<KArchiveEntry>; 00050 00051 00052 class KArchive::KArchivePrivate 00053 { 00054 public: 00055 KArchiveDirectory* rootDir; 00056 }; 00057 00058 class PosSortedPtrList : public QPtrList<KArchiveFile> { 00059 protected: 00060 int compareItems( QPtrCollection::Item i1, 00061 QPtrCollection::Item i2 ) 00062 { 00063 int pos1 = static_cast<KArchiveFile*>( i1 )->position(); 00064 int pos2 = static_cast<KArchiveFile*>( i2 )->position(); 00065 return ( pos1 - pos2 ); 00066 } 00067 }; 00068 00069 00073 00074 KArchive::KArchive( QIODevice * dev ) 00075 { 00076 d = new KArchivePrivate; 00077 d->rootDir = 0; 00078 m_dev = dev; 00079 m_open = false; 00080 } 00081 00082 KArchive::~KArchive() 00083 { 00084 if ( m_open ) 00085 close(); 00086 delete d->rootDir; 00087 delete d; 00088 } 00089 00090 bool KArchive::open( int mode ) 00091 { 00092 if ( m_dev && !m_dev->open( mode ) ) 00093 return false; 00094 00095 if ( m_open ) 00096 close(); 00097 00098 m_mode = mode; 00099 m_open = true; 00100 00101 Q_ASSERT( d->rootDir == 0L ); 00102 d->rootDir = 0L; 00103 00104 return openArchive( mode ); 00105 } 00106 00107 void KArchive::close() 00108 { 00109 if ( !m_open ) 00110 return; 00111 // moved by holger to allow kzip to write the zip central dir 00112 // to the file in closeArchive() 00113 closeArchive(); 00114 00115 if ( m_dev ) 00116 m_dev->close(); 00117 00118 delete d->rootDir; 00119 d->rootDir = 0; 00120 m_open = false; 00121 } 00122 00123 const KArchiveDirectory* KArchive::directory() const 00124 { 00125 // rootDir isn't const so that parsing-on-demand is possible 00126 return const_cast<KArchive *>(this)->rootDir(); 00127 } 00128 00129 00130 bool KArchive::addLocalFile( const QString& fileName, const QString& destName ) 00131 { 00132 QFileInfo fileInfo( fileName ); 00133 if ( !fileInfo.isFile() && !fileInfo.isSymLink() ) 00134 { 00135 kdWarning() << "KArchive::addLocalFile " << fileName << " doesn't exist or is not a regular file." << endl; 00136 return false; 00137 } 00138 00139 KDE_struct_stat fi; 00140 if (KDE_lstat(QFile::encodeName(fileName),&fi) == -1) { 00141 kdWarning() << "KArchive::addLocalFile stating " << fileName 00142 << " failed: " << strerror(errno) << endl; 00143 return false; 00144 } 00145 00146 if (fileInfo.isSymLink()) { 00147 return writeSymLink(destName, fileInfo.readLink(), fileInfo.owner(), 00148 fileInfo.group(), fi.st_mode, fi.st_atime, fi.st_mtime, 00149 fi.st_ctime); 00150 }/*end if*/ 00151 00152 uint size = fileInfo.size(); 00153 00154 // the file must be opened before prepareWriting is called, otherwise 00155 // if the opening fails, no content will follow the already written 00156 // header and the tar file is effectively f*cked up 00157 QFile file( fileName ); 00158 if ( !file.open( IO_ReadOnly ) ) 00159 { 00160 kdWarning() << "KArchive::addLocalFile couldn't open file " << fileName << endl; 00161 return false; 00162 } 00163 00164 if ( !prepareWriting( destName, fileInfo.owner(), fileInfo.group(), size, 00165 fi.st_mode, fi.st_atime, fi.st_mtime, fi.st_ctime ) ) 00166 { 00167 kdWarning() << "KArchive::addLocalFile prepareWriting " << destName << " failed" << endl; 00168 return false; 00169 } 00170 00171 // Read and write data in chunks to minimize memory usage 00172 QByteArray array(8*1024); 00173 int n; 00174 uint total = 0; 00175 while ( ( n = file.readBlock( array.data(), array.size() ) ) > 0 ) 00176 { 00177 if ( !writeData( array.data(), n ) ) 00178 { 00179 kdWarning() << "KArchive::addLocalFile writeData failed" << endl; 00180 return false; 00181 } 00182 total += n; 00183 } 00184 Q_ASSERT( total == size ); 00185 00186 if ( !doneWriting( size ) ) 00187 { 00188 kdWarning() << "KArchive::addLocalFile doneWriting failed" << endl; 00189 return false; 00190 } 00191 return true; 00192 } 00193 00194 bool KArchive::addLocalDirectory( const QString& path, const QString& destName ) 00195 { 00196 QString dot = "."; 00197 QString dotdot = ".."; 00198 QDir dir( path ); 00199 if ( !dir.exists() ) 00200 return false; 00201 QStringList files = dir.entryList(); 00202 for ( QStringList::Iterator it = files.begin(); it != files.end(); ++it ) 00203 { 00204 if ( *it != dot && *it != dotdot ) 00205 { 00206 QString fileName = path + "/" + *it; 00207 // kdDebug() << "storing " << fileName << endl; 00208 QString dest = destName.isEmpty() ? *it : (destName + "/" + *it); 00209 QFileInfo fileInfo( fileName ); 00210 00211 if ( fileInfo.isFile() || fileInfo.isSymLink() ) 00212 addLocalFile( fileName, dest ); 00213 else if ( fileInfo.isDir() ) 00214 addLocalDirectory( fileName, dest ); 00215 // We omit sockets 00216 } 00217 } 00218 return true; 00219 } 00220 00221 bool KArchive::writeFile( const QString& name, const QString& user, const QString& group, uint size, const char* data ) 00222 { 00223 mode_t perm = 0100644; 00224 time_t the_time = time(0); 00225 return writeFile(name,user,group,size,perm,the_time,the_time,the_time,data); 00226 } 00227 00228 bool KArchive::prepareWriting( const QString& name, const QString& user, 00229 const QString& group, uint size, mode_t perm, 00230 time_t atime, time_t mtime, time_t ctime ) { 00231 PrepareWritingParams params; 00232 params.name = &name; 00233 params.user = &user; 00234 params.group = &group; 00235 params.size = size; 00236 params.perm = perm; 00237 params.atime = atime; 00238 params.mtime = mtime; 00239 params.ctime = ctime; 00240 virtual_hook(VIRTUAL_PREPARE_WRITING,&params); 00241 return params.retval; 00242 } 00243 00244 bool KArchive::prepareWriting_impl(const QString &name, const QString &user, 00245 const QString &group, uint size, mode_t /*perm*/, 00246 time_t /*atime*/, time_t /*mtime*/, time_t /*ctime*/ ) { 00247 kdWarning(7040) << "New prepareWriting API not implemented in this class." << endl 00248 << "Falling back to old API (metadata information will be lost)" << endl; 00249 return prepareWriting(name,user,group,size); 00250 } 00251 00252 bool KArchive::writeFile( const QString& name, const QString& user, 00253 const QString& group, uint size, mode_t perm, 00254 time_t atime, time_t mtime, time_t ctime, 00255 const char* data ) { 00256 WriteFileParams params; 00257 params.name = &name; 00258 params.user = &user; 00259 params.group = &group; 00260 params.size = size; 00261 params.perm = perm; 00262 params.atime = atime; 00263 params.mtime = mtime; 00264 params.ctime = ctime; 00265 params.data = data; 00266 virtual_hook(VIRTUAL_WRITE_FILE,&params); 00267 return params.retval; 00268 } 00269 00270 bool KArchive::writeFile_impl( const QString& name, const QString& user, 00271 const QString& group, uint size, mode_t perm, 00272 time_t atime, time_t mtime, time_t ctime, 00273 const char* data ) { 00274 00275 if ( !prepareWriting( name, user, group, size, perm, atime, mtime, ctime ) ) 00276 { 00277 kdWarning() << "KArchive::writeFile prepareWriting failed" << endl; 00278 return false; 00279 } 00280 00281 // Write data 00282 // Note: if data is 0L, don't call writeBlock, it would terminate the KFilterDev 00283 if ( data && size && !writeData( data, size ) ) 00284 { 00285 kdWarning() << "KArchive::writeFile writeData failed" << endl; 00286 return false; 00287 } 00288 00289 if ( !doneWriting( size ) ) 00290 { 00291 kdWarning() << "KArchive::writeFile doneWriting failed" << endl; 00292 return false; 00293 } 00294 return true; 00295 } 00296 00297 bool KArchive::writeDir(const QString& name, const QString& user, 00298 const QString& group, mode_t perm, 00299 time_t atime, time_t mtime, time_t ctime) { 00300 WriteDirParams params; 00301 params.name = &name; 00302 params.user = &user; 00303 params.group = &group; 00304 params.perm = perm; 00305 params.atime = atime; 00306 params.mtime = mtime; 00307 params.ctime = ctime; 00308 virtual_hook(VIRTUAL_WRITE_DIR,&params); 00309 return params.retval; 00310 } 00311 00312 bool KArchive::writeDir_impl(const QString &name, const QString &user, 00313 const QString &group, mode_t /*perm*/, 00314 time_t /*atime*/, time_t /*mtime*/, time_t /*ctime*/ ) { 00315 kdWarning(7040) << "New writeDir API not implemented in this class." << endl 00316 << "Falling back to old API (metadata information will be lost)" << endl; 00317 return writeDir(name,user,group); 00318 } 00319 00320 bool KArchive::writeSymLink(const QString &name, const QString &target, 00321 const QString &user, const QString &group, 00322 mode_t perm, time_t atime, time_t mtime, time_t ctime) { 00323 WriteSymlinkParams params; 00324 params.name = &name; 00325 params.target = &target; 00326 params.user = &user; 00327 params.group = &group; 00328 params.perm = perm; 00329 params.atime = atime; 00330 params.mtime = mtime; 00331 params.ctime = ctime; 00332 virtual_hook(VIRTUAL_WRITE_SYMLINK,&params); 00333 return params.retval; 00334 } 00335 00336 bool KArchive::writeSymLink_impl(const QString &/*name*/,const QString &/*target*/, 00337 const QString &/*user*/, const QString &/*group*/, 00338 mode_t /*perm*/, time_t /*atime*/, time_t /*mtime*/, 00339 time_t /*ctime*/) { 00340 kdWarning(7040) << "writeSymLink not implemented in this class." << endl 00341 << "No fallback available." << endl; 00342 // FIXME: better return true here for compatibility with KDE < 3.2 00343 return false; 00344 } 00345 00346 bool KArchive::writeData( const char* data, uint size ) 00347 { 00348 WriteDataParams params; 00349 params.data = data; 00350 params.size = size; 00351 virtual_hook( VIRTUAL_WRITE_DATA, &params ); 00352 return params.retval; 00353 } 00354 00355 bool KArchive::writeData_impl( const char* data, uint size ) 00356 { 00357 Q_ASSERT( device() ); 00358 return device()->writeBlock( data, size ) == (Q_LONG)size; 00359 } 00360 00361 KArchiveDirectory * KArchive::rootDir() 00362 { 00363 if ( !d->rootDir ) 00364 { 00365 //kdDebug() << "Making root dir " << endl; 00366 struct passwd* pw = getpwuid( getuid() ); 00367 struct group* grp = getgrgid( getgid() ); 00368 QString username = pw ? QFile::decodeName(pw->pw_name) : QString::number( getuid() ); 00369 QString groupname = grp ? QFile::decodeName(grp->gr_name) : QString::number( getgid() ); 00370 00371 d->rootDir = new KArchiveDirectory( this, QString::fromLatin1("/"), (int)(0777 + S_IFDIR), 0, username, groupname, QString::null ); 00372 } 00373 return d->rootDir; 00374 } 00375 00376 KArchiveDirectory * KArchive::findOrCreate( const QString & path ) 00377 { 00378 //kdDebug() << "KArchive::findOrCreate " << path << endl; 00379 if ( path.isEmpty() || path == "/" || path == "." ) // root dir => found 00380 { 00381 //kdDebug() << "KArchive::findOrCreate returning rootdir" << endl; 00382 return rootDir(); 00383 } 00384 // Important note : for tar files containing absolute paths 00385 // (i.e. beginning with "/"), this means the leading "/" will 00386 // be removed (no KDirectory for it), which is exactly the way 00387 // the "tar" program works (though it displays a warning about it) 00388 // See also KArchiveDirectory::entry(). 00389 00390 // Already created ? => found 00391 KArchiveEntry* ent = rootDir()->entry( path ); 00392 if ( ent && ent->isDirectory() ) 00393 { 00394 //kdDebug() << "KArchive::findOrCreate found it" << endl; 00395 return (KArchiveDirectory *) ent; 00396 } 00397 00398 // Otherwise go up and try again 00399 int pos = path.findRev( '/' ); 00400 KArchiveDirectory * parent; 00401 QString dirname; 00402 if ( pos == -1 ) // no more slash => create in root dir 00403 { 00404 parent = rootDir(); 00405 dirname = path; 00406 } 00407 else 00408 { 00409 QString left = path.left( pos ); 00410 dirname = path.mid( pos + 1 ); 00411 parent = findOrCreate( left ); // recursive call... until we find an existing dir. 00412 } 00413 00414 //kdDebug() << "KTar : found parent " << parent->name() << " adding " << dirname << " to ensure " << path << endl; 00415 // Found -> add the missing piece 00416 KArchiveDirectory * e = new KArchiveDirectory( this, dirname, d->rootDir->permissions(), 00417 d->rootDir->date(), d->rootDir->user(), 00418 d->rootDir->group(), QString::null ); 00419 parent->addEntry( e ); 00420 return e; // now a directory to <path> exists 00421 } 00422 00423 void KArchive::setDevice( QIODevice * dev ) 00424 { 00425 m_dev = dev; 00426 } 00427 00428 void KArchive::setRootDir( KArchiveDirectory *rootDir ) 00429 { 00430 Q_ASSERT( !d->rootDir ); // Call setRootDir only once during parsing please ;) 00431 d->rootDir = rootDir; 00432 } 00433 00437 KArchiveEntry::KArchiveEntry( KArchive* t, const QString& name, int access, int date, 00438 const QString& user, const QString& group, const 00439 QString& symlink) 00440 { 00441 m_name = name; 00442 m_access = access; 00443 m_date = date; 00444 m_user = user; 00445 m_group = group; 00446 m_symlink = symlink; 00447 m_archive = t; 00448 00449 } 00450 00451 QDateTime KArchiveEntry::datetime() const 00452 { 00453 QDateTime d; 00454 d.setTime_t( m_date ); 00455 return d; 00456 } 00457 00461 00462 KArchiveFile::KArchiveFile( KArchive* t, const QString& name, int access, int date, 00463 const QString& user, const QString& group, 00464 const QString & symlink, 00465 int pos, int size ) 00466 : KArchiveEntry( t, name, access, date, user, group, symlink ) 00467 { 00468 m_pos = pos; 00469 m_size = size; 00470 } 00471 00472 int KArchiveFile::position() const 00473 { 00474 return m_pos; 00475 } 00476 00477 int KArchiveFile::size() const 00478 { 00479 return m_size; 00480 } 00481 00482 QByteArray KArchiveFile::data() const 00483 { 00484 archive()->device()->at( m_pos ); 00485 00486 // Read content 00487 QByteArray arr( m_size ); 00488 if ( m_size ) 00489 { 00490 assert( arr.data() ); 00491 int n = archive()->device()->readBlock( arr.data(), m_size ); 00492 if ( n != m_size ) 00493 arr.resize( n ); 00494 } 00495 return arr; 00496 } 00497 00498 // ** This should be a virtual method, and this code should be in ktar.cpp 00499 QIODevice *KArchiveFile::device() const 00500 { 00501 return new KLimitedIODevice( archive()->device(), m_pos, m_size ); 00502 } 00503 00504 void KArchiveFile::copyTo(const QString& dest) const 00505 { 00506 QFile f( dest + "/" + name() ); 00507 f.open( IO_ReadWrite | IO_Truncate ); 00508 f.writeBlock( data() ); 00509 f.close(); 00510 } 00511 00515 00516 00517 KArchiveDirectory::KArchiveDirectory( KArchive* t, const QString& name, int access, 00518 int date, 00519 const QString& user, const QString& group, 00520 const QString &symlink) 00521 : KArchiveEntry( t, name, access, date, user, group, symlink ) 00522 { 00523 m_entries.setAutoDelete( true ); 00524 } 00525 00526 QStringList KArchiveDirectory::entries() const 00527 { 00528 QStringList l; 00529 00530 QDictIterator<KArchiveEntry> it( m_entries ); 00531 for( ; it.current(); ++it ) 00532 l.append( it.currentKey() ); 00533 00534 return l; 00535 } 00536 00537 KArchiveEntry* KArchiveDirectory::entry( QString name ) 00538 // not "const QString & name" since we want a local copy 00539 // (to remove leading slash if any) 00540 { 00541 int pos = name.find( '/' ); 00542 if ( pos == 0 ) // ouch absolute path (see also KArchive::findOrCreate) 00543 { 00544 if (name.length()>1) 00545 { 00546 name = name.mid( 1 ); // remove leading slash 00547 pos = name.find( '/' ); // look again 00548 } 00549 else // "/" 00550 return this; 00551 } 00552 // trailing slash ? -> remove 00553 if ( pos != -1 && pos == (int)name.length()-1 ) 00554 { 00555 name = name.left( pos ); 00556 pos = name.find( '/' ); // look again 00557 } 00558 if ( pos != -1 ) 00559 { 00560 QString left = name.left( pos ); 00561 QString right = name.mid( pos + 1 ); 00562 00563 //kdDebug() << "KArchiveDirectory::entry left=" << left << " right=" << right << endl; 00564 00565 KArchiveEntry* e = m_entries[ left ]; 00566 if ( !e || !e->isDirectory() ) 00567 return 0; 00568 return ((KArchiveDirectory*)e)->entry( right ); 00569 } 00570 00571 return m_entries[ name ]; 00572 } 00573 00574 const KArchiveEntry* KArchiveDirectory::entry( QString name ) const 00575 { 00576 return ((KArchiveDirectory*)this)->entry( name ); 00577 } 00578 00579 void KArchiveDirectory::addEntry( KArchiveEntry* entry ) 00580 { 00581 Q_ASSERT( !entry->name().isEmpty() ); 00582 m_entries.insert( entry->name(), entry ); 00583 } 00584 00585 void KArchiveDirectory::copyTo(const QString& dest, bool recursiveCopy ) const 00586 { 00587 QDir root; 00588 00589 PosSortedPtrList fileList; 00590 QMap<int, QString> fileToDir; 00591 00592 QStringList::Iterator it; 00593 00594 // placeholders for iterated items 00595 KArchiveDirectory* curDir; 00596 QString curDirName; 00597 00598 QStringList dirEntries; 00599 KArchiveEntry* curEntry; 00600 KArchiveFile* curFile; 00601 00602 00603 QPtrStack<KArchiveDirectory> dirStack; 00604 QValueStack<QString> dirNameStack; 00605 00606 dirStack.push( this ); // init stack at current directory 00607 dirNameStack.push( dest ); // ... with given path 00608 do { 00609 curDir = dirStack.pop(); 00610 curDirName = dirNameStack.pop(); 00611 root.mkdir(curDirName); 00612 00613 dirEntries = curDir->entries(); 00614 for ( it = dirEntries.begin(); it != dirEntries.end(); ++it ) { 00615 curEntry = curDir->entry(*it); 00616 if ( curEntry->isFile() ) { 00617 curFile = dynamic_cast<KArchiveFile*>( curEntry ); 00618 if (curFile) { 00619 fileList.append( curFile ); 00620 fileToDir.insert( curFile->position(), curDirName ); 00621 } 00622 } 00623 00624 if ( curEntry->isDirectory() ) 00625 if ( recursiveCopy ) { 00626 KArchiveDirectory *ad = dynamic_cast<KArchiveDirectory*>( curEntry ); 00627 if (ad) { 00628 dirStack.push( ad ); 00629 dirNameStack.push( curDirName + "/" + curEntry->name() ); 00630 } 00631 } 00632 } 00633 } while (!dirStack.isEmpty()); 00634 00635 fileList.sort(); // sort on m_pos, so we have a linear access 00636 00637 KArchiveFile* f; 00638 for ( f = fileList.first(); f; f = fileList.next() ) { 00639 int pos = f->position(); 00640 f->copyTo( fileToDir[pos] ); 00641 } 00642 } 00643 00644 void KArchive::virtual_hook( int id, void* data ) 00645 { 00646 switch (id) { 00647 case VIRTUAL_WRITE_DATA: { 00648 WriteDataParams* params = reinterpret_cast<WriteDataParams *>(data); 00649 params->retval = writeData_impl( params->data, params->size ); 00650 break; 00651 } 00652 case VIRTUAL_WRITE_SYMLINK: { 00653 WriteSymlinkParams *params = reinterpret_cast<WriteSymlinkParams *>(data); 00654 params->retval = writeSymLink_impl(*params->name,*params->target, 00655 *params->user,*params->group,params->perm, 00656 params->atime,params->mtime,params->ctime); 00657 break; 00658 } 00659 case VIRTUAL_WRITE_DIR: { 00660 WriteDirParams *params = reinterpret_cast<WriteDirParams *>(data); 00661 params->retval = writeDir_impl(*params->name,*params->user, 00662 *params->group,params->perm, 00663 params->atime,params->mtime,params->ctime); 00664 break; 00665 } 00666 case VIRTUAL_WRITE_FILE: { 00667 WriteFileParams *params = reinterpret_cast<WriteFileParams *>(data); 00668 params->retval = writeFile_impl(*params->name,*params->user, 00669 *params->group,params->size,params->perm, 00670 params->atime,params->mtime,params->ctime, 00671 params->data); 00672 break; 00673 } 00674 case VIRTUAL_PREPARE_WRITING: { 00675 PrepareWritingParams *params = reinterpret_cast<PrepareWritingParams *>(data); 00676 params->retval = prepareWriting_impl(*params->name,*params->user, 00677 *params->group,params->size,params->perm, 00678 params->atime,params->mtime,params->ctime); 00679 break; 00680 } 00681 default: 00682 /*BASE::virtual_hook( id, data )*/; 00683 }/*end switch*/ 00684 } 00685 00686 void KArchiveEntry::virtual_hook( int, void* ) 00687 { /*BASE::virtual_hook( id, data );*/ } 00688 00689 void KArchiveFile::virtual_hook( int id, void* data ) 00690 { KArchiveEntry::virtual_hook( id, data ); } 00691 00692 void KArchiveDirectory::virtual_hook( int id, void* data ) 00693 { KArchiveEntry::virtual_hook( id, data ); }
KDE Logo
This file is part of the documentation for kio Library Version 3.4.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Apr 14 00:20:20 2005 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003