/***************************************************************************
                                kiso.cpp
                             -------------------
    begin                : Oct 25 2002
    copyright            : (C) 2002 by Szombathelyi Gy�gy
    email                : gyurco@users.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

 /* This file is heavily based on ktar.cpp from kdelibs (c) David Faure */
 
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <tqcstring.h>
#include <tqdir.h>
#include <tqfile.h>
#include <kdebug.h>
#include <kurl.h>
#include <kmimetype.h>
#include <kconfig.h>
#include <kfilterdev.h>
#include <kfilterbase.h>

#include "kiso.h"
#include "libisofs/isofs.h"
#include "qfilehack.h"


#ifdef __linux__
#undef __STRICT_ANSI__
#include <linux/cdrom.h>
#define __STRICT_ANSI__
#include <sys/ioctl.h>
#include <fcntl.h>
#endif

////////////////////////////////////////////////////////////////////////
/////////////////////////// KIso ///////////////////////////////////
////////////////////////////////////////////////////////////////////////

/**
 * puts the track layout of the device 'fname' into 'tracks'
 * tracks structure: start sector, track number, ...
 * tracks should be 100*2 entry long (this is the maximum in the CD-ROM standard)
 * currently it's linux only, porters are welcome
 */
static int getTracks(const char *fname,int *tracks) {
    int ret=0;
    memset(tracks,0,200*sizeof(int));

#ifdef __linux__
    int fd,i;
    struct cdrom_tochdr tochead;
    struct cdrom_tocentry tocentry;

    kdDebug() << "getTracks open:" << fname << endl;
    fd=open(fname, O_RDONLY | O_NONBLOCK);
    if (fd > 0) {
        if (ioctl(fd,CDROMREADTOCHDR,&tochead)!=-1) {
            kdDebug() << "getTracks first track:" << tochead.cdth_trk0
                << " last track " << tochead.cdth_trk1 << endl;
            for (i=tochead.cdth_trk0;i<=tochead.cdth_trk1;i++) {
                if (ret>99) break;
                memset(&tocentry,0,sizeof(struct cdrom_tocentry));
                tocentry.cdte_track=i;
                tocentry.cdte_format=CDROM_LBA;
                if (ioctl(fd,CDROMREADTOCENTRY,&tocentry)<0) break;
                kdDebug() << "getTracks got track " << i << " starting at: " <<
                    tocentry.cdte_addr.lba << endl;
                if ((tocentry.cdte_ctrl & 0x4) == 0x4) {
                    tracks[ret<<1]=tocentry.cdte_addr.lba;
                    tracks[(ret<<1)+1]=i;
                    ret++;
                }
            }
        }
        close(fd);
    }

#endif

    return ret;
}

class KIso::KIsoPrivate
{
public:
    KIsoPrivate() {}
    TQStringList dirList;
};

KIso::KIso( const TQString& filename, const TQString & _mimetype )
    : KArchive( 0L )
{
    m_startsec = -1;
    m_filename = filename;
    d = new KIsoPrivate;
    TQString mimetype( _mimetype );
    bool forced = true;
    if ( mimetype.isEmpty() )
    {
        mimetype = KMimeType::findByFileContent( filename )->name();
        kdDebug() << "KIso::KIso mimetype=" << mimetype << endl;

        // Don't move to prepareDevice - the other constructor theoretically allows ANY filter
        if ( mimetype == "application/x-tgz" || mimetype == "application/x-targz" || // the latter is deprecated but might still be around
             mimetype == "application/x-webarchive" )
            // that's a gzipped tar file, so ask for gzip filter
            mimetype = "application/x-gzip";
        else if ( mimetype == "application/x-tbz" ) // that's a bzipped2 tar file, so ask for bz2 filter
            mimetype = "application/x-bzip2";
        else
        {
            // Something else. Check if it's not really gzip though (e.g. for KOffice docs)
            TQFile file( filename );
            if ( file.open( IO_ReadOnly ) )
            {
                unsigned char firstByte = file.getch();
                unsigned char secondByte = file.getch();
                unsigned char thirdByte = file.getch();
                if ( firstByte == 0037 && secondByte == 0213 )
                    mimetype = "application/x-gzip";
                else if ( firstByte == 'B' && secondByte == 'Z' && thirdByte == 'h' )
                    mimetype = "application/x-bzip2";
                else if ( firstByte == 'P' && secondByte == 'K' && thirdByte == 3 )
                {
                    unsigned char fourthByte = file.getch();
                    if ( fourthByte == 4 )
                        mimetype = "application/x-zip";
                }
            }
        }
        forced = false;
    }

    prepareDevice( filename, mimetype, forced );
}

void KIso::prepareDevice( const TQString & filename,
                            const TQString & mimetype, bool forced )
{
  /* 'hack' for Qt's false assumption that only S_ISREG is seekable */
  if( "inode/blockdevice" == mimetype )
      setDevice( TQT_TQIODEVICE(new QFileHack( filename )) );
  else
  {
    if( "application/x-gzip" == mimetype
       || "application/x-bzip2" == mimetype)
        forced = true;

    TQIODevice *dev = KFilterDev::deviceForFile( filename, mimetype, forced );
    if( dev )
      setDevice( dev );
  }

}

KIso::KIso( TQIODevice * dev )
    : KArchive( dev )
{
    d = new KIsoPrivate;
}

KIso::~KIso()
{
    // mjarrett: Closes to prevent ~KArchive from aborting w/o device
    if( isOpened() )
        close();
    if ( !m_filename.isEmpty() )
        delete device(); // we created it ourselves
    delete d;
}

/* callback function for libisofs */
static int readf(char *buf, long long start, long long len,void *udata) {

    TQIODevice* dev = ( static_cast<KIso*> (udata) )->device();

    if (dev->at(start<<11)) {
        if ((dev->readBlock(buf, len<<11)) != -1) return (len);
    }
    kdDebug() << "KIso::ReadRequest failed start: " << start << " len: " << len << endl;

    return -1;
}

/* callback function for libisofs */
static int mycallb(struct iso_directory_record *idr,void *udata) {

    KIso *iso = static_cast<KIso*> (udata);
    TQString path,user,group,symlink;
    int i;
    int access;
    int time,cdate,adate;
    rr_entry rr;
    bool special=false;
    KArchiveEntry *entry=NULL,*oldentry=NULL;
    char z_algo[2],z_params[2];
    long long z_size=0;

    if ((idr->flags[0] & 1) && !iso->showhidden) return 0;
    if (iso->level) {
        if (isonum_711(idr->name_len)==1) {
            switch (idr->name[0]) {
                case 0:
                    path+=(".");
                    special=true;
                    break;
                case 1:
                    path+=("..");
                    special=true;
                    break;
            }
        }
        if (iso->showrr && ParseRR(idr,&rr)>0) {
            if (!special) path=rr.name;
            symlink=rr.sl;
            access=rr.mode;
            time=rr.t_mtime;
            adate=rr.t_atime;
            cdate=rr.t_ctime;
            user.setNum(rr.uid);
            group.setNum(rr.gid);
            z_algo[0]=rr.z_algo[0];z_algo[1]=rr.z_algo[1];
            z_params[0]=rr.z_params[0];z_params[1]=rr.z_params[1];
            z_size=rr.z_size;
        } else {
            access=iso->dirent->permissions() & ~S_IFMT;
            adate=cdate=time=isodate_915(idr->date,0);
            user=iso->dirent->user();
            group=iso->dirent->group();
            if (idr->flags[0] & 2) access |= S_IFDIR; else access |= S_IFREG;
            if (!special) {
                if (iso->joliet) {
                    for (i=0;i<(isonum_711(idr->name_len)-1);i+=2) {
                        TQChar ch( be2me_16(*((ushort*)&(idr->name[i]))) );
                        if (ch==';') break;
                        path+=ch;
                    }
                } else {
                    for (i=0;i<isonum_711(idr->name_len);i++) {
                        if (idr->name[i]==';') break;
                        if (idr->name[i]) path+=(idr->name[i]);
                    }
                }
                if (path.endsWith(".")) path.setLength(path.length()-1);
            }
        }
        if (iso->showrr) FreeRR(&rr);
        if (idr->flags[0] & 2) {
            entry = new KIsoDirectory( iso, path, access | S_IFDIR, time, adate, cdate,
                user, group, symlink );
        } else {
            entry = new KIsoFile( iso, path, access, time, adate, cdate,
                user, group, symlink, isonum_733(idr->extent)<<11,isonum_733(idr->size) );
            if (z_size) (static_cast <KIsoFile*> (entry))->setZF(z_algo,z_params,z_size);

        }
        iso->dirent->addEntry(entry);
    }
    if ( (idr->flags[0] & 2) && (iso->level==0 || !special) ) {
        if (iso->level) {
            oldentry=iso->dirent;
            iso->dirent=static_cast<KIsoDirectory*> (entry);
        }
        iso->level++;
        ProcessDir(&readf,isonum_733(idr->extent),isonum_733(idr->size),&mycallb,udata);
        iso->level--;
        if (iso->level) iso->dirent=static_cast<KIsoDirectory*> (oldentry);
    }
    return 0;
}

void KIso::addBoot(struct el_torito_boot_descriptor* bootdesc) {

    int i;
    long long size;
    boot_head boot;
    boot_entry *be;
    TQString path;
    KIsoFile *entry;
    
    entry=new KIsoFile( this, "Catalog", dirent->permissions() & ~S_IFDIR,
        dirent->date(), dirent->adate(), dirent->cdate(),
        dirent->user(), dirent->group(), TQString::null,
        isonum_731(bootdesc->boot_catalog)<<11, 2048 );
    dirent->addEntry(entry);
    if (!ReadBootTable(&readf,isonum_731(bootdesc->boot_catalog),&boot,this)) {
        i=1;
        be=boot.defentry;
        while (be) {
            size=BootImageSize( isonum_711((reinterpret_cast<struct default_entry*>(be->data))->media),
                                isonum_721((reinterpret_cast<struct default_entry*>(be->data))->seccount));
            path="Default Image";
            if (i>1) path += " (" + TQString::number(i) + ")";
            entry=new KIsoFile( this, path, dirent->permissions() & ~S_IFDIR,
                dirent->date(), dirent->adate(), dirent->cdate(),
                dirent->user(), dirent->group(), TQString::null,
                isonum_731((reinterpret_cast<struct default_entry*>(be->data))->start)<<11, size<<9 );
            dirent->addEntry(entry);
            be=be->next;
            i++;
        }

        FreeBootTable(&boot);
    }
}

void KIso::readParams()
{
    KConfig *config;

    config = new KConfig("kio_isorc");
    
    showhidden=config->readBoolEntry("showhidden",false);
    showrr=config->readBoolEntry("showrr",true);
    delete config;
}

bool KIso::openArchive( int mode )
{
    iso_vol_desc *desc;
    TQString path,tmp,uid,gid;
    struct stat buf;
    int tracks[2*100],trackno=0,i,access,c_b,c_i,c_j;
    KArchiveDirectory *root;
    struct iso_directory_record* idr;
    struct el_torito_boot_descriptor* bootdesc;

    if ( mode == IO_WriteOnly )
        return false;

    readParams();
    d->dirList.clear();

    tracks[0]=0;
    if (m_startsec>0) tracks[0]=m_startsec;
    kdDebug() << " m_startsec: " << m_startsec << endl;
    /* We'll use the permission and user/group of the 'host' file except
     * in Rock Ridge, where the permissions are stored on the file system
     */
    if (::stat( m_filename.local8Bit(), &buf )<0) {
        /* defaults, if stat fails */
        memset(&buf,0,sizeof(struct stat));
        buf.st_mode=0777;
    } else {
        /* If it's a block device, try to query the track layout (for multisession) */
        if (m_startsec == -1 && S_ISBLK(buf.st_mode))
            trackno=getTracks(m_filename.latin1(),(int*) &tracks);
    }
    uid.setNum(buf.st_uid);
    gid.setNum(buf.st_gid);
    access = buf.st_mode & ~S_IFMT;

    kdDebug() << "KIso::openArchive number of tracks: " << trackno << endl;

    if (trackno==0) trackno=1;
    for (i=0;i<trackno;i++) {

        c_b=1;c_i=1;c_j=1;       
        root=rootDir();
        if (trackno>1) {
            path=TQString::null;
            TQTextOStream(&path) << "Track " << tracks[(i<<1)+1];
            root = new KIsoDirectory( this, path, access | S_IFDIR,
                buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, TQString::null );
            rootDir()->addEntry(root);
        }

        desc=ReadISO9660(&readf,tracks[i<<1],this);
        if (!desc) {
            kdDebug() << "KIso::openArchive no volume descriptors" << endl;
            continue;
        }

        while (desc) {
            switch (isonum_711(desc->data.type)) {
                case ISO_VD_BOOT:

                    bootdesc=(struct el_torito_boot_descriptor*) &(desc->data);
                    if ( !memcmp(EL_TORITO_ID,bootdesc->system_id,ISODCL(8,39)) ) {
                        path="El Torito Boot";
                        if (c_b>1) path += " (" + TQString::number(c_b) + ")";
                        
                        dirent = new KIsoDirectory( this, path, access | S_IFDIR,
                            buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, TQString::null );
                        root->addEntry(dirent);
                        
                        addBoot(bootdesc);
                        c_b++;
                    }
                    break;

                case ISO_VD_PRIMARY:
                case ISO_VD_SUPPLEMENTARY:
                    idr=(struct iso_directory_record*) &( ((struct iso_primary_descriptor*) &desc->data)->root_directory_record);
                    joliet = JolietLevel(&desc->data);
                    if (joliet) {
                        TQTextOStream(&path) << "Joliet level " << joliet;
                        if (c_j>1) path += " (" + TQString::number(c_j) + ")";
                    } else {
                        path = "ISO9660";
                        if (c_i>1) path += " (" + TQString::number(c_i) + ")";
                    }
                    dirent = new KIsoDirectory( this, path, access | S_IFDIR,
                        buf.st_mtime, buf.st_atime, buf.st_ctime, uid, gid, TQString::null );
                    root->addEntry(dirent);
                    level=0;
                    mycallb(idr, this );
                    if (joliet) c_j++; else c_i++;
                    break;
            }
            desc=desc->next;
        }
        free(desc);
    }
    device()->close();
    return true;
}

bool KIso::closeArchive()
{
    d->dirList.clear();
    return true;
}

bool KIso::writeDir( const TQString&, const TQString&, const TQString& )
{
    return false;
}

bool KIso::prepareWriting( const TQString&, const TQString&, const TQString&, uint)
{
    return false;
}

bool KIso::doneWriting( uint )
{
    return false;
}

void KIso::virtual_hook( int id, void* data )
{ KArchive::virtual_hook( id, data ); }