You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
18 KiB
622 lines
18 KiB
#include <config.h>
|
|
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tdeglobal.h>
|
|
#include <kurl.h>
|
|
#include <kdebug.h>
|
|
#include <kinstance.h>
|
|
#include <ktar.h>
|
|
#include <kzip.h>
|
|
#include <kar.h>
|
|
#include <kmimemagic.h>
|
|
#include <tdelocale.h>
|
|
#include <kde_file.h>
|
|
#include <tdeio/global.h>
|
|
#include <kremoteencoding.h>
|
|
|
|
#include <errno.h> // to be removed
|
|
|
|
#include "tar.h"
|
|
|
|
using namespace TDEIO;
|
|
|
|
extern "C" { int TDE_EXPORT kdemain(int argc, char **argv); }
|
|
|
|
int kdemain( int argc, char **argv )
|
|
{
|
|
TDEInstance instance( "tdeio_tar" );
|
|
|
|
kdDebug(7109) << "Starting " << getpid() << endl;
|
|
|
|
if (argc != 4)
|
|
{
|
|
fprintf(stderr, "Usage: tdeio_tar protocol domain-socket1 domain-socket2\n");
|
|
exit(-1);
|
|
}
|
|
|
|
ArchiveProtocol slave(argv[2], argv[3]);
|
|
slave.dispatchLoop();
|
|
|
|
kdDebug(7109) << "Done" << endl;
|
|
return 0;
|
|
}
|
|
|
|
ArchiveProtocol::ArchiveProtocol( const TQCString &pool, const TQCString &app ) : SlaveBase( "tar", pool, app )
|
|
{
|
|
kdDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol" << endl;
|
|
m_archiveFile = 0L;
|
|
}
|
|
|
|
ArchiveProtocol::~ArchiveProtocol()
|
|
{
|
|
delete m_archiveFile;
|
|
}
|
|
|
|
bool ArchiveProtocol::checkNewFile( const KURL & url, TQString & path, TDEIO::Error& errorNum )
|
|
{
|
|
TQString fullPath = url.path();
|
|
kdDebug(7109) << "ArchiveProtocol::checkNewFile " << fullPath << endl;
|
|
|
|
|
|
// Are we already looking at that file ?
|
|
if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) )
|
|
{
|
|
// Has it changed ?
|
|
KDE_struct_stat statbuf;
|
|
if ( KDE_stat( TQFile::encodeName( m_archiveName ), &statbuf ) == 0 )
|
|
{
|
|
if ( m_mtime == statbuf.st_mtime )
|
|
{
|
|
path = fullPath.mid( m_archiveName.length() );
|
|
kdDebug(7109) << "ArchiveProtocol::checkNewFile returning " << path << endl;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
kdDebug(7109) << "Need to open a new file" << endl;
|
|
|
|
// Close previous file
|
|
if ( m_archiveFile )
|
|
{
|
|
m_archiveFile->close();
|
|
delete m_archiveFile;
|
|
m_archiveFile = 0L;
|
|
}
|
|
|
|
// Find where the tar file is in the full path
|
|
int pos = 0;
|
|
TQString archiveFile;
|
|
path = TQString::null;
|
|
|
|
int len = fullPath.length();
|
|
if ( len != 0 && fullPath[ len - 1 ] != '/' )
|
|
fullPath += '/';
|
|
|
|
kdDebug(7109) << "the full path is " << fullPath << endl;
|
|
KDE_struct_stat statbuf;
|
|
statbuf.st_mode = 0; // be sure to clear the directory bit
|
|
while ( (pos=fullPath.find( '/', pos+1 )) != -1 )
|
|
{
|
|
TQString tryPath = fullPath.left( pos );
|
|
kdDebug(7109) << fullPath << " trying " << tryPath << endl;
|
|
if ( KDE_stat( TQFile::encodeName(tryPath), &statbuf ) == -1 )
|
|
{
|
|
// We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore
|
|
break;
|
|
}
|
|
if ( !S_ISDIR(statbuf.st_mode) )
|
|
{
|
|
archiveFile = tryPath;
|
|
m_mtime = statbuf.st_mtime;
|
|
path = fullPath.mid( pos + 1 );
|
|
kdDebug(7109) << "fullPath=" << fullPath << " path=" << path << endl;
|
|
len = path.length();
|
|
if ( len > 1 )
|
|
{
|
|
if ( path[ len - 1 ] == '/' )
|
|
path.truncate( len - 1 );
|
|
}
|
|
else
|
|
path = TQString::fromLatin1("/");
|
|
kdDebug(7109) << "Found. archiveFile=" << archiveFile << " path=" << path << endl;
|
|
break;
|
|
}
|
|
}
|
|
if ( archiveFile.isEmpty() )
|
|
{
|
|
kdDebug(7109) << "ArchiveProtocol::checkNewFile: not found" << endl;
|
|
if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory?
|
|
{
|
|
// Too bad, it is a directory, not an archive.
|
|
kdDebug(7109) << "Path is a directory, not an archive." << endl;
|
|
errorNum = TDEIO::ERR_IS_DIRECTORY;
|
|
}
|
|
else
|
|
errorNum = TDEIO::ERR_DOES_NOT_EXIST;
|
|
return false;
|
|
}
|
|
|
|
// Open new file
|
|
if ( url.protocol() == "tar" ) {
|
|
kdDebug(7109) << "Opening KTar on " << archiveFile << endl;
|
|
m_archiveFile = new KTar( archiveFile );
|
|
} else if ( url.protocol() == "ar" ) {
|
|
kdDebug(7109) << "Opening KAr on " << archiveFile << endl;
|
|
m_archiveFile = new KAr( archiveFile );
|
|
} else if ( url.protocol() == "zip" ) {
|
|
kdDebug(7109) << "Opening KZip on " << archiveFile << endl;
|
|
m_archiveFile = new KZip( archiveFile );
|
|
} else {
|
|
kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave" << endl;
|
|
errorNum = TDEIO::ERR_UNSUPPORTED_PROTOCOL;
|
|
return false;
|
|
}
|
|
|
|
if ( !m_archiveFile->open( IO_ReadOnly ) )
|
|
{
|
|
kdDebug(7109) << "Opening " << archiveFile << "failed." << endl;
|
|
delete m_archiveFile;
|
|
m_archiveFile = 0L;
|
|
errorNum = TDEIO::ERR_CANNOT_OPEN_FOR_READING;
|
|
return false;
|
|
}
|
|
|
|
m_archiveName = archiveFile;
|
|
return true;
|
|
}
|
|
|
|
|
|
void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry )
|
|
{
|
|
UDSAtom atom;
|
|
entry.clear();
|
|
atom.m_uds = UDS_NAME;
|
|
atom.m_str = remoteEncoding()->decode(archiveEntry->name().local8Bit());
|
|
entry.append(atom);
|
|
|
|
atom.m_uds = UDS_FILE_TYPE;
|
|
atom.m_long = archiveEntry->permissions() & S_IFMT; // keep file type only
|
|
entry.append( atom );
|
|
|
|
atom.m_uds = UDS_SIZE;
|
|
atom.m_long = archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L ;
|
|
entry.append( atom );
|
|
|
|
atom.m_uds = UDS_MODIFICATION_TIME;
|
|
atom.m_long = archiveEntry->date();
|
|
entry.append( atom );
|
|
|
|
atom.m_uds = UDS_ACCESS;
|
|
atom.m_long = archiveEntry->permissions() & 07777; // keep permissions only
|
|
entry.append( atom );
|
|
|
|
atom.m_uds = UDS_USER;
|
|
atom.m_str = remoteEncoding()->decode(archiveEntry->user().local8Bit());
|
|
entry.append( atom );
|
|
|
|
atom.m_uds = UDS_GROUP;
|
|
atom.m_str = remoteEncoding()->decode(archiveEntry->group().local8Bit());
|
|
entry.append( atom );
|
|
|
|
atom.m_uds = UDS_LINK_DEST;
|
|
atom.m_str = remoteEncoding()->decode(archiveEntry->symlink().local8Bit());
|
|
entry.append( atom );
|
|
}
|
|
|
|
void ArchiveProtocol::listDir( const KURL & url )
|
|
{
|
|
kdDebug( 7109 ) << "ArchiveProtocol::listDir " << url << endl;
|
|
|
|
TQString path;
|
|
TDEIO::Error errorNum;
|
|
if ( !checkNewFile( url, path, errorNum ) )
|
|
{
|
|
if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING )
|
|
{
|
|
// If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
|
|
// Therefore give a more specific error message
|
|
error( TDEIO::ERR_SLAVE_DEFINED,
|
|
i18n( "Could not open the file, probably due to an unsupported file format.\n%1")
|
|
.arg( url.prettyURL() ) );
|
|
return;
|
|
}
|
|
else if ( errorNum != ERR_IS_DIRECTORY )
|
|
{
|
|
// We have any other error
|
|
error( errorNum, url.prettyURL() );
|
|
return;
|
|
}
|
|
// It's a real dir -> redirect
|
|
KURL redir;
|
|
redir.setPath( url.path() );
|
|
kdDebug( 7109 ) << "Ok, redirection to " << redir.url() << endl;
|
|
redirection( redir );
|
|
finished();
|
|
// And let go of the tar file - for people who want to unmount a cdrom after that
|
|
delete m_archiveFile;
|
|
m_archiveFile = 0L;
|
|
return;
|
|
}
|
|
|
|
if ( path.isEmpty() )
|
|
{
|
|
KURL redir( url.protocol() + TQString::fromLatin1( ":/") );
|
|
kdDebug( 7109 ) << "url.path()==" << url.path() << endl;
|
|
redir.setPath( url.path() + TQString::fromLatin1("/") );
|
|
kdDebug( 7109 ) << "ArchiveProtocol::listDir: redirection " << redir.url() << endl;
|
|
redirection( redir );
|
|
finished();
|
|
return;
|
|
}
|
|
|
|
path = TQString::fromLocal8Bit(remoteEncoding()->encode(path));
|
|
|
|
kdDebug( 7109 ) << "checkNewFile done" << endl;
|
|
const KArchiveDirectory* root = m_archiveFile->directory();
|
|
const KArchiveDirectory* dir;
|
|
if (!path.isEmpty() && path != "/")
|
|
{
|
|
kdDebug(7109) << TQString(TQString("Looking for entry %1").arg(path)) << endl;
|
|
const KArchiveEntry* e = root->entry( path );
|
|
if ( !e )
|
|
{
|
|
error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() );
|
|
return;
|
|
}
|
|
if ( ! e->isDirectory() )
|
|
{
|
|
error( TDEIO::ERR_IS_FILE, url.prettyURL() );
|
|
return;
|
|
}
|
|
dir = (KArchiveDirectory*)e;
|
|
} else {
|
|
dir = root;
|
|
}
|
|
|
|
TQStringList l = dir->entries();
|
|
totalSize( l.count() );
|
|
|
|
UDSEntry entry;
|
|
TQStringList::Iterator it = l.begin();
|
|
for( ; it != l.end(); ++it )
|
|
{
|
|
kdDebug(7109) << (*it) << endl;
|
|
const KArchiveEntry* archiveEntry = dir->entry( (*it) );
|
|
|
|
createUDSEntry( archiveEntry, entry );
|
|
|
|
listEntry( entry, false );
|
|
}
|
|
|
|
listEntry( entry, true ); // ready
|
|
|
|
finished();
|
|
|
|
kdDebug( 7109 ) << "ArchiveProtocol::listDir done" << endl;
|
|
}
|
|
|
|
void ArchiveProtocol::stat( const KURL & url )
|
|
{
|
|
TQString path;
|
|
UDSEntry entry;
|
|
TDEIO::Error errorNum;
|
|
if ( !checkNewFile( url, path, errorNum ) )
|
|
{
|
|
// We may be looking at a real directory - this happens
|
|
// when pressing up after being in the root of an archive
|
|
if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING )
|
|
{
|
|
// If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
|
|
// Therefore give a more specific error message
|
|
error( TDEIO::ERR_SLAVE_DEFINED,
|
|
i18n( "Could not open the file, probably due to an unsupported file format.\n%1")
|
|
.arg( url.prettyURL() ) );
|
|
return;
|
|
}
|
|
else if ( errorNum != ERR_IS_DIRECTORY )
|
|
{
|
|
// We have any other error
|
|
error( errorNum, url.prettyURL() );
|
|
return;
|
|
}
|
|
// Real directory. Return just enough information for KRun to work
|
|
UDSAtom atom;
|
|
atom.m_uds = TDEIO::UDS_NAME;
|
|
atom.m_str = url.fileName();
|
|
entry.append( atom );
|
|
kdDebug( 7109 ) << "ArchiveProtocol::stat returning name=" << url.fileName() << endl;
|
|
|
|
KDE_struct_stat buff;
|
|
if ( KDE_stat( TQFile::encodeName( url.path() ), &buff ) == -1 )
|
|
{
|
|
// Should not happen, as the file was already stated by checkNewFile
|
|
error( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL() );
|
|
return;
|
|
}
|
|
|
|
atom.m_uds = TDEIO::UDS_FILE_TYPE;
|
|
atom.m_long = buff.st_mode & S_IFMT;
|
|
entry.append( atom );
|
|
|
|
statEntry( entry );
|
|
|
|
finished();
|
|
|
|
// And let go of the tar file - for people who want to unmount a cdrom after that
|
|
delete m_archiveFile;
|
|
m_archiveFile = 0L;
|
|
return;
|
|
}
|
|
|
|
const KArchiveDirectory* root = m_archiveFile->directory();
|
|
const KArchiveEntry* archiveEntry;
|
|
if ( path.isEmpty() )
|
|
{
|
|
path = TQString::fromLatin1( "/" );
|
|
archiveEntry = root;
|
|
} else {
|
|
path = TQString::fromLocal8Bit(remoteEncoding()->encode(path));
|
|
archiveEntry = root->entry( path );
|
|
}
|
|
if ( !archiveEntry )
|
|
{
|
|
error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() );
|
|
return;
|
|
}
|
|
|
|
createUDSEntry( archiveEntry, entry );
|
|
statEntry( entry );
|
|
|
|
finished();
|
|
}
|
|
|
|
void ArchiveProtocol::get( const KURL & url )
|
|
{
|
|
kdDebug( 7109 ) << "ArchiveProtocol::get " << url << endl;
|
|
|
|
TQString path;
|
|
TDEIO::Error errorNum;
|
|
if ( !checkNewFile( url, path, errorNum ) )
|
|
{
|
|
if ( errorNum == TDEIO::ERR_CANNOT_OPEN_FOR_READING )
|
|
{
|
|
// If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
|
|
// Therefore give a more specific error message
|
|
error( TDEIO::ERR_SLAVE_DEFINED,
|
|
i18n( "Could not open the file, probably due to an unsupported file format.\n%1")
|
|
.arg( url.prettyURL() ) );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// We have any other error
|
|
error( errorNum, url.prettyURL() );
|
|
return;
|
|
}
|
|
}
|
|
|
|
path = TQString::fromLocal8Bit(remoteEncoding()->encode(path));
|
|
|
|
const KArchiveDirectory* root = m_archiveFile->directory();
|
|
const KArchiveEntry* archiveEntry = root->entry( path );
|
|
|
|
if ( !archiveEntry )
|
|
{
|
|
error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() );
|
|
return;
|
|
}
|
|
if ( archiveEntry->isDirectory() )
|
|
{
|
|
error( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() );
|
|
return;
|
|
}
|
|
const KArchiveFile* archiveFileEntry = static_cast<const KArchiveFile *>(archiveEntry);
|
|
if ( !archiveEntry->symlink().isEmpty() )
|
|
{
|
|
kdDebug(7109) << "Redirection to " << archiveEntry->symlink() << endl;
|
|
KURL realURL;
|
|
if (archiveEntry->symlink().startsWith("/")) { // absolute path
|
|
realURL.setPath(archiveEntry->symlink() ); // goes out of tar:/, back into file:
|
|
} else {
|
|
realURL = KURL( url, archiveEntry->symlink() );
|
|
}
|
|
kdDebug(7109) << "realURL= " << realURL << endl;
|
|
redirection( realURL );
|
|
finished();
|
|
return;
|
|
}
|
|
|
|
//kdDebug(7109) << "Preparing to get the archive data" << endl;
|
|
|
|
/*
|
|
* The easy way would be to get the data by calling archiveFileEntry->data()
|
|
* However this has drawbacks:
|
|
* - the complete file must be read into the memory
|
|
* - errors are skipped, resulting in an empty file
|
|
*/
|
|
|
|
TQIODevice* io = 0;
|
|
// Getting the device is hard, as archiveFileEntry->device() is not virtual!
|
|
if ( url.protocol() == "tar" )
|
|
{
|
|
io = archiveFileEntry->device();
|
|
}
|
|
else if ( url.protocol() == "ar" )
|
|
{
|
|
io = archiveFileEntry->device();
|
|
}
|
|
else if ( url.protocol() == "zip" )
|
|
{
|
|
io = ((KZipFileEntry*) archiveFileEntry)->device();
|
|
}
|
|
else
|
|
{
|
|
// Wrong protocol? Why was this not catched by checkNewFile?
|
|
kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave; " << k_funcinfo << endl;
|
|
error( TDEIO::ERR_UNSUPPORTED_PROTOCOL, url.protocol() );
|
|
return;
|
|
}
|
|
|
|
if (!io)
|
|
{
|
|
error( TDEIO::ERR_SLAVE_DEFINED,
|
|
i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" )
|
|
.arg( url.prettyURL() ) );
|
|
return;
|
|
}
|
|
|
|
if ( !io->open( IO_ReadOnly ) )
|
|
{
|
|
error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL() );
|
|
return;
|
|
}
|
|
|
|
totalSize( archiveFileEntry->size() );
|
|
|
|
// Size of a TQIODevice read. It must be large enough so that the mime type check will not fail
|
|
const int maxSize = 0x100000; // 1MB
|
|
|
|
int bufferSize = kMin( maxSize, archiveFileEntry->size() );
|
|
TQByteArray buffer ( bufferSize );
|
|
if ( buffer.isEmpty() && bufferSize > 0 )
|
|
{
|
|
// Something went wrong
|
|
error( TDEIO::ERR_OUT_OF_MEMORY, url.prettyURL() );
|
|
return;
|
|
}
|
|
|
|
bool firstRead = true;
|
|
|
|
// How much file do we still have to process?
|
|
int fileSize = archiveFileEntry->size();
|
|
TDEIO::filesize_t processed = 0;
|
|
|
|
while ( !io->atEnd() && fileSize > 0 )
|
|
{
|
|
if ( !firstRead )
|
|
{
|
|
bufferSize = kMin( maxSize, fileSize );
|
|
buffer.resize( bufferSize, TQGArray::SpeedOptim );
|
|
}
|
|
const TQ_LONG read = io->readBlock( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong.
|
|
if ( read != bufferSize )
|
|
{
|
|
kdWarning(7109) << "Read " << read << " bytes but expected " << bufferSize << endl;
|
|
error( TDEIO::ERR_COULD_NOT_READ, url.prettyURL() );
|
|
return;
|
|
}
|
|
if ( firstRead )
|
|
{
|
|
// We use the magic one the first data read
|
|
// (As magic detection is about fixed positions, we can be sure that it is enough data.)
|
|
KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( buffer, path );
|
|
kdDebug(7109) << "Emitting mimetype " << result->mimeType() << endl;
|
|
mimeType( result->mimeType() );
|
|
firstRead = false;
|
|
}
|
|
data( buffer );
|
|
processed += read;
|
|
processedSize( processed );
|
|
fileSize -= bufferSize;
|
|
}
|
|
io->close();
|
|
delete io;
|
|
|
|
data( TQByteArray() );
|
|
|
|
finished();
|
|
}
|
|
|
|
/*
|
|
In case someone wonders how the old filter stuff looked like : :)
|
|
void TARProtocol::slotData(void *_p, int _len)
|
|
{
|
|
switch (m_cmd) {
|
|
case CMD_PUT:
|
|
assert(m_pFilter);
|
|
m_pFilter->send(_p, _len);
|
|
break;
|
|
default:
|
|
abort();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TARProtocol::slotDataEnd()
|
|
{
|
|
switch (m_cmd) {
|
|
case CMD_PUT:
|
|
assert(m_pFilter && m_pJob);
|
|
m_pFilter->finish();
|
|
m_pJob->dataEnd();
|
|
m_cmd = CMD_NONE;
|
|
break;
|
|
default:
|
|
abort();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TARProtocol::jobData(void *_p, int _len)
|
|
{
|
|
switch (m_cmd) {
|
|
case CMD_GET:
|
|
assert(m_pFilter);
|
|
m_pFilter->send(_p, _len);
|
|
break;
|
|
case CMD_COPY:
|
|
assert(m_pFilter);
|
|
m_pFilter->send(_p, _len);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void TARProtocol::jobDataEnd()
|
|
{
|
|
switch (m_cmd) {
|
|
case CMD_GET:
|
|
assert(m_pFilter);
|
|
m_pFilter->finish();
|
|
dataEnd();
|
|
break;
|
|
case CMD_COPY:
|
|
assert(m_pFilter);
|
|
m_pFilter->finish();
|
|
m_pJob->dataEnd();
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void TARProtocol::filterData(void *_p, int _len)
|
|
{
|
|
debug("void TARProtocol::filterData");
|
|
switch (m_cmd) {
|
|
case CMD_GET:
|
|
data(_p, _len);
|
|
break;
|
|
case CMD_PUT:
|
|
assert (m_pJob);
|
|
m_pJob->data(_p, _len);
|
|
break;
|
|
case CMD_COPY:
|
|
assert(m_pJob);
|
|
m_pJob->data(_p, _len);
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
*/
|