Filelight – graphical disk usage analyzer
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.
 
 
 
filelight/src/part/localLister.cpp

333 lines
11 KiB

//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
//Copyright: See COPYING file that comes with this distribution
#include "Config.h"
#include "debug.h"
#include <dirent.h>
#include "fileTree.h"
#include <fstab.h>
#include "localLister.h"
#ifdef HAVE_MNTENT_H
#include <mntent.h>
#endif
#include <tqapplication.h> //postEvent()
#include <tqfile.h>
#include "scan.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
namespace Filelight
{
TQStringList LocalLister::s_remoteMounts;
TQStringList LocalLister::s_localMounts;
LocalLister::LocalLister( const TQString &path, Chain<Directory> *cachedTrees, TQObject *parent )
: TQThread()
, m_path( path )
, m_trees( cachedTrees )
, m_parent( parent )
{
//add empty directories for any mount points that are in the path
//TODO empty directories is not ideal as adds to fileCount incorrectly
TQStringList list( Config::skipList );
if( !Config::scanAcrossMounts ) list += s_localMounts;
if( !Config::scanRemoteMounts ) list += s_remoteMounts;
for( TQStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it )
if( (*it).startsWith( path ) )
//prevent scanning of these directories
m_trees->append( new Directory( (*it).local8Bit() ) );
start();
}
void
LocalLister::run()
{
//recursively scan the requested path
const TQCString path = TQFile::encodeName( m_path );
Directory *tree = scan( path, path );
//delete the list of trees useful for this scan,
//in a sucessful scan the contents would now be transfered to 'tree'
delete m_trees;
if( ScanManager::s_abort ) //scan was cancelled
{
debug() << "Scan succesfully aborted\n";
delete tree;
tree = 0;
}
TQCustomEvent *e = new TQCustomEvent( 1000 );
e->setData( tree );
TQApplication::postEvent( m_parent, e );
}
// from system.h in GNU coreutils package
/* Extract or fake data from a `struct stat'.
ST_BLKSIZE: Preferred I/O blocksize for the file, in bytes.
ST_NBLOCKS: Number of blocks in the file, including indirect blocks.
ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS. */
#ifndef HAVE_STRUCT_STAT_ST_BLOCKS
#define ST_BLKSIZE(statbuf) DEV_BSIZE
#if defined _POSIX_SOURCE || !defined BSIZE /* fileblocks.c uses BSIZE. */
#define ST_NBLOCKS(statbuf) ((statbuf).st_size / ST_NBLOCKSIZE + ((statbuf).st_size % ST_NBLOCKSIZE != 0))
#else /* !_POSIX_SOURCE && BSIZE */
#define ST_NBLOCKS(statbuf) (S_ISREG ((statbuf).st_mode) || S_ISDIR ((statbuf).st_mode) ? st_blocks ((statbuf).st_size) : 0)
#endif /* !_POSIX_SOURCE && BSIZE */
#else /* HAVE_STRUCT_STAT_ST_BLOCKS */
/* Some systems, like Sequents, return st_blksize of 0 on pipes.
Also, when running `rsh hpux11-system cat any-file', cat would
determine that the output stream had an st_blksize of 2147421096.
So here we arbitrarily limit the `optimal' block size to 4MB.
If anyone knows of a system for which the legitimate value for
st_blksize can exceed 4MB, please report it as a bug in this code. */
#define ST_BLKSIZE(statbuf) ((0 < (statbuf).st_blksize && (statbuf).st_blksize <= (1 << 22)) /* 4MiB */ ? (statbuf).st_blksize : DEV_BSIZE)
#if defined hpux || defined __hpux__ || defined __hpux
/* HP-UX counts st_blocks in 1024-byte units.
This loses when mixing HP-UX and BSD filesystems with NFS. */
#define ST_NBLOCKSIZE 1024
#else /* !hpux */
#if defined _AIX && defined _I386
/* AIX PS/2 counts st_blocks in 4K units. */
#define ST_NBLOCKSIZE (4 * 1024)
#else /* not AIX PS/2 */
#if defined _CRAY
#define ST_NBLOCKS(statbuf) (S_ISREG ((statbuf).st_mode) || S_ISDIR ((statbuf).st_mode) ? (statbuf).st_blocks * ST_BLKSIZE(statbuf)/ST_NBLOCKSIZE : 0)
#endif /* _CRAY */
#endif /* not AIX PS/2 */
#endif /* !hpux */
#endif /* HAVE_STRUCT_STAT_ST_BLOCKS */
#ifndef ST_NBLOCKS
#define ST_NBLOCKS(statbuf) ((statbuf).st_blocks)
#endif
#ifndef ST_NBLOCKSIZE
#define ST_NBLOCKSIZE 512
#endif
//some GNU systems don't support big files for some reason
#ifdef __USE_LARGEFILE64 //see dirent.h
#define dirent dirent64
#define scandir scandir64
#define stat stat64
#define statstruct stat64
#define lstat lstat64
#define readdir readdir64
#endif
#ifndef NULL
#define NULL 0
#endif
#include <errno.h>
static void
outputError( TQCString path )
{
///show error message that stat or opendir may give
#define out( s ) error() << s ": " << path << endl; break
switch( errno ) {
case EACCES:
out( "Inadequate access permisions" );
case EMFILE:
out( "Too many file descriptors in use by Filelight" );
case ENFILE:
out( "Too many files are currently open in the system" );
case ENOENT:
out( "A component of the path does not exist, or the path is an empty string" );
case ENOMEM:
out( "Insufficient memory to complete the operation" );
case ENOTDIR:
out( "A component of the path is not a directory" );
case EBADF:
out( "Bad file descriptor" );
case EFAULT:
out( "Bad address" );
case ELOOP: //NOTE shouldn't ever happen
out( "Too many symbolic links encountered while traversing the path" );
case ENAMETOOLONG:
out( "File name too long" );
}
#undef out
}
Directory*
LocalLister::scan( const TQCString &path, const TQCString &dirname )
{
Directory *cwd = new Directory( dirname );
DIR *dir = opendir( path );
if( !dir ) {
outputError( path );
return cwd;
}
struct stat statbuf;
dirent *ent;
while ((ent = readdir( dir )))
{
if( ScanManager::s_abort )
return cwd;
if( qstrcmp( ent->d_name, "." ) == 0 || qstrcmp( ent->d_name, ".." ) == 0 )
continue;
TQCString new_path = path; new_path += ent->d_name;
//get file information
if( lstat( new_path, &statbuf ) == -1 ) {
outputError( new_path );
continue;
}
if( S_ISLNK( statbuf.st_mode ) ||
S_ISCHR( statbuf.st_mode ) ||
S_ISBLK( statbuf.st_mode ) ||
S_ISFIFO( statbuf.st_mode ) ||
S_ISSOCK( statbuf.st_mode ) )
{
continue;
}
if( S_ISREG( statbuf.st_mode ) ) //file
//using units of KiB as 32bit max is 4GiB and 64bit ints are expensive
cwd->append( ent->d_name, (ST_NBLOCKS( statbuf ) * ST_NBLOCKSIZE) / 1024 );
else if( S_ISDIR( statbuf.st_mode ) ) //directory
{
Directory *d = 0;
TQCString new_dirname = ent->d_name;
new_dirname += '/';
new_path += '/';
//check to see if we've scanned this section already
for( Iterator<Directory> it = m_trees->iterator(); it != m_trees->end(); ++it )
{
if( new_path == (*it)->name8Bit() )
{
debug() << "Tree pre-completed: " << (*it)->name() << "\n";
d = it.remove();
ScanManager::s_files += d->tqchildren();
//**** ideally don't have this redundant extra somehow
cwd->append( d, new_dirname );
}
}
if( !d ) //then scan
if ((d = scan( new_path, new_dirname ))) //then scan was successful
cwd->append( d );
}
++ScanManager::s_files;
}
closedir( dir );
return cwd;
}
bool
LocalLister::readMounts()
{
#define INFO_PARTITIONS "/proc/partitions"
#define INFO_MOUNTED_PARTITIONS "/etc/mtab" /* on Linux... */
//**** SHAMBLES
// ** mtab should have priority as mount points don't have to follow fstab
// ** no removable media detection
// ** no updates if mounts change
// ** you want a KDE extension that handles this for you really
struct fstab *fstab_ent;
#ifdef HAVE_MNTENT_H
struct mntent *mnt_ent;
#endif
TQString str;
#ifdef HAVE_MNTENT_H
FILE *fp;
if( setfsent() == 0 || !( fp = setmntent( INFO_MOUNTED_PARTITIONS, "r" ) ) )
#else
if( setfsent() == 0 )
#endif
return false;
#define FS_NAME fstab_ent->fs_spec // device-name
#define FS_FILE fstab_ent->fs_file // mount-point
#define FS_TYPE fstab_ent->fs_vfstype // fs-type
#define FS_MNTOPS fstab_ent->fs_mntops // mount-options
TQStringList remoteFsTypes;
remoteFsTypes << "smbfs" ;
#ifdef MNTTYPE_NFS
remoteFsTypes << MNTTYPE_NFS;
#else
remoteFsTypes << "nfs";
#endif
// What about afs?
while( (fstab_ent = getfsent()) != NULL )
{
str = TQString( FS_FILE );
if( str == "/" ) continue;
str += '/';
if( remoteFsTypes.contains( FS_TYPE ) )
s_remoteMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
else
s_localMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
kdDebug() << "FSTAB: " << FS_TYPE << "\n";
}
endfsent(); /* close fstab.. */
#undef FS_NAME
#undef FS_FILE
#undef FS_TYPE
#undef FS_MNTOPS
#define FS_NAME mnt_ent->mnt_fsname // device-name
#define FS_FILE mnt_ent->mnt_dir // mount-point
#define FS_TYPE mnt_ent->mnt_type // fs-type
#define FS_MNTOPS mnt_ent->mnt_opts // mount-options
//scan mtab, **** mtab should take priority, but currently it isn't
#ifdef HAVE_MNTENT_H
while( ( mnt_ent = getmntent( fp ) ) != NULL )
{
bool b = false;
str = TQString( FS_FILE );
if( str == "/" ) continue;
str += "/";
if( remoteFsTypes.contains( FS_TYPE ) )
if( b = !s_remoteMounts.contains( str ) )
s_remoteMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
else if( b = !s_localMounts.contains( str ) )
s_localMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
if( b ) kdDebug() << "MTAB: " << FS_TYPE << "\n";
}
endmntent( fp ); /* close mtab.. */
#endif
return true;
}
}