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.
tdebase/tdeioslave/tar/tar.cc

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 KDE_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();
}
}
*/