|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2005 by Joris Guisson *
|
|
|
|
* joris.guisson@gmail.com *
|
|
|
|
* *
|
|
|
|
* 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 program is distributed in the hope that it will be useful, *
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
* GNU General Public License for more details. *
|
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
|
|
|
|
***************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <kio/netaccess.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kfileitem.h>
|
|
|
|
#include <util/array.h>
|
|
|
|
#include <util/fileops.h>
|
|
|
|
#include <torrent/globals.h>
|
|
|
|
#include <interfaces/functions.h>
|
|
|
|
#include <kapplication.h>
|
|
|
|
#include <util/log.h>
|
|
|
|
#include <util/error.h>
|
|
|
|
#include "cachefile.h"
|
|
|
|
#include "preallocationthread.h"
|
|
|
|
#include "settings.h"
|
|
|
|
|
|
|
|
|
|
|
|
// Not all systems have an O_LARGEFILE - Solaris depending
|
|
|
|
// on command-line defines, FreeBSD never - so in those cases,
|
|
|
|
// make it a zero bitmask. As long as it's only OR'ed into
|
|
|
|
// open(2) flags, that's fine.
|
|
|
|
//
|
|
|
|
#ifndef O_LARGEFILE
|
|
|
|
#define O_LARGEFILE (0)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace bt
|
|
|
|
{
|
|
|
|
|
|
|
|
CacheFile::CacheFile() : fd(-1),max_size(0),file_size(0),mutex(true)
|
|
|
|
{
|
|
|
|
read_only = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CacheFile::~CacheFile()
|
|
|
|
{
|
|
|
|
if (fd != -1)
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::changePath(const TQString & npath)
|
|
|
|
{
|
|
|
|
path = npath;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::openFile(Mode mode)
|
|
|
|
{
|
|
|
|
int flags = O_LARGEFILE;
|
|
|
|
|
|
|
|
// by default allways try read write
|
|
|
|
fd = ::open(TQFile::encodeName(path),flags | O_RDWR);
|
|
|
|
if (fd < 0 && mode == READ)
|
|
|
|
{
|
|
|
|
// in case RDWR fails, try readonly if possible
|
|
|
|
fd = ::open(TQFile::encodeName(path),flags | O_RDONLY);
|
|
|
|
if (fd >= 0)
|
|
|
|
read_only = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fd < 0)
|
|
|
|
{
|
|
|
|
throw Error(i18n("Cannot open %1 : %2").arg(path).arg(strerror(errno)));
|
|
|
|
}
|
|
|
|
|
|
|
|
file_size = FileSize(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::open(const TQString & path,Uint64 size)
|
|
|
|
{
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
// only set the path and the max size, we only open the file when it is needed
|
|
|
|
this->path = path;
|
|
|
|
max_size = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void* CacheFile::map(MMappeable* thing,Uint64 off,Uint32 size,Mode mode)
|
|
|
|
{
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
// reopen the file if necessary
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
// Out() << "Reopening " << path << endl;
|
|
|
|
openFile(mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read_only && mode != READ)
|
|
|
|
{
|
|
|
|
throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (off + size > max_size)
|
|
|
|
{
|
|
|
|
Out() << "Warning : writing past the end of " << path << endl;
|
|
|
|
Out() << (off + size) << " " << max_size << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int mmap_flag = 0;
|
|
|
|
switch (mode)
|
|
|
|
{
|
|
|
|
case READ:
|
|
|
|
mmap_flag = PROT_READ;
|
|
|
|
break;
|
|
|
|
case WRITE:
|
|
|
|
mmap_flag = PROT_WRITE;
|
|
|
|
break;
|
|
|
|
case RW:
|
|
|
|
mmap_flag = PROT_READ|PROT_WRITE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (off + size > file_size)
|
|
|
|
{
|
|
|
|
Uint64 to_write = (off + size) - file_size;
|
|
|
|
// Out() << "Growing file with " << to_write << " bytes" << endl;
|
|
|
|
growFile(to_write);
|
|
|
|
}
|
|
|
|
|
|
|
|
Uint32 page_size = sysconf(_SC_PAGESIZE);
|
|
|
|
if (off % page_size > 0)
|
|
|
|
{
|
|
|
|
// off is not a multiple of the page_size
|
|
|
|
// so we play around a bit
|
|
|
|
Uint32 diff = (off % page_size);
|
|
|
|
Uint64 noff = off - diff;
|
|
|
|
// Out() << "Offsetted mmap : " << diff << endl;
|
|
|
|
#if HAVE_MMAP64
|
|
|
|
char* ptr = (char*)mmap64(0, size + diff, mmap_flag, MAP_SHARED, fd, noff);
|
|
|
|
#else
|
|
|
|
char* ptr = (char*)mmap(0, size + diff, mmap_flag, MAP_SHARED, fd, noff);
|
|
|
|
#endif
|
|
|
|
if (ptr == MAP_FAILED)
|
|
|
|
{
|
|
|
|
Out() << "mmap failed : " << TQString(strerror(errno)) << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CacheFile::Entry e;
|
|
|
|
e.thing = thing;
|
|
|
|
e.offset = off;
|
|
|
|
e.diff = diff;
|
|
|
|
e.ptr = ptr;
|
|
|
|
e.size = size + diff;
|
|
|
|
e.mode = mode;
|
|
|
|
mappings.insert((void*)(ptr + diff),e);
|
|
|
|
return ptr + diff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#if HAVE_MMAP64
|
|
|
|
void* ptr = mmap64(0, size, mmap_flag, MAP_SHARED, fd, off);
|
|
|
|
#else
|
|
|
|
void* ptr = mmap(0, size, mmap_flag, MAP_SHARED, fd, off);
|
|
|
|
#endif
|
|
|
|
if (ptr == MAP_FAILED)
|
|
|
|
{
|
|
|
|
Out() << "mmap failed : " << TQString(strerror(errno)) << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CacheFile::Entry e;
|
|
|
|
e.thing = thing;
|
|
|
|
e.offset = off;
|
|
|
|
e.ptr = ptr;
|
|
|
|
e.diff = 0;
|
|
|
|
e.size = size;
|
|
|
|
e.mode = mode;
|
|
|
|
mappings.insert(ptr,e);
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::growFile(Uint64 to_write)
|
|
|
|
{
|
|
|
|
// reopen the file if necessary
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
// Out() << "Reopening " << path << endl;
|
|
|
|
openFile(RW);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read_only)
|
|
|
|
throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path));
|
|
|
|
|
|
|
|
// jump to the end of the file
|
|
|
|
SeekFile(fd,0,SEEK_END);
|
|
|
|
|
|
|
|
if (file_size + to_write > max_size)
|
|
|
|
{
|
|
|
|
Out() << "Warning : writing past the end of " << path << endl;
|
|
|
|
Out() << (file_size + to_write) << " " << max_size << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
Uint8 buf[1024];
|
|
|
|
memset(buf,0,1024);
|
|
|
|
Uint64 num = to_write;
|
|
|
|
// write data until to_write is 0
|
|
|
|
while (to_write > 0)
|
|
|
|
{
|
|
|
|
int nb = to_write > 1024 ? 1024 : to_write;
|
|
|
|
int ret = ::write(fd,buf,nb);
|
|
|
|
if (ret < 0)
|
|
|
|
throw Error(i18n("Cannot expand file %1 : %2").arg(path).arg(strerror(errno)));
|
|
|
|
else if (ret != nb)
|
|
|
|
throw Error(i18n("Cannot expand file %1 : incomplete write").arg(path));
|
|
|
|
to_write -= nb;
|
|
|
|
}
|
|
|
|
file_size += num;
|
|
|
|
//
|
|
|
|
// Out() << TQString("growing %1 = %2").arg(path).arg(kt::BytesToString(file_size)) << endl;
|
|
|
|
|
|
|
|
if (file_size != FileSize(fd))
|
|
|
|
{
|
|
|
|
// Out() << TQString("Homer Simpson %1 %2").arg(file_size).arg(sb.st_size) << endl;
|
|
|
|
fsync(fd);
|
|
|
|
if (file_size != FileSize(fd))
|
|
|
|
{
|
|
|
|
throw Error(i18n("Cannot expand file %1").arg(path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::unmap(void* ptr,Uint32 size)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
// see if it wasn't an offsetted mapping
|
|
|
|
if (mappings.contains(ptr))
|
|
|
|
{
|
|
|
|
CacheFile::Entry & e = mappings[ptr];
|
|
|
|
#if HAVE_MUNMAP64
|
|
|
|
if (e.diff > 0)
|
|
|
|
ret = munmap64((char*)ptr - e.diff,e.size);
|
|
|
|
else
|
|
|
|
ret = munmap64(ptr,e.size);
|
|
|
|
#else
|
|
|
|
if (e.diff > 0)
|
|
|
|
ret = munmap((char*)ptr - e.diff,e.size);
|
|
|
|
else
|
|
|
|
ret = munmap(ptr,e.size);
|
|
|
|
#endif
|
|
|
|
mappings.erase(ptr);
|
|
|
|
// no mappings, close temporary
|
|
|
|
if (mappings.count() == 0)
|
|
|
|
closeTemporary();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#if HAVE_MUNMAP64
|
|
|
|
ret = munmap64(ptr,size);
|
|
|
|
#else
|
|
|
|
ret = munmap(ptr,size);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
Out(SYS_DIO|LOG_IMPORTANT) << TQString("Munmap failed with error %1 : %2").arg(errno).arg(strerror(errno)) << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::close()
|
|
|
|
{
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
|
|
|
|
if (fd == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQMap<void*,Entry>::iterator i = mappings.begin();
|
|
|
|
while (i != mappings.end())
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
CacheFile::Entry & e = i.data();
|
|
|
|
#if HAVE_MUNMAP64
|
|
|
|
if (e.diff > 0)
|
|
|
|
ret = munmap64((char*)e.ptr - e.diff,e.size);
|
|
|
|
else
|
|
|
|
ret = munmap64(e.ptr,e.size);
|
|
|
|
#else
|
|
|
|
if (e.diff > 0)
|
|
|
|
ret = munmap((char*)e.ptr - e.diff,e.size);
|
|
|
|
else
|
|
|
|
ret = munmap(e.ptr,e.size);
|
|
|
|
#endif
|
|
|
|
e.thing->unmapped();
|
|
|
|
|
|
|
|
i++;
|
|
|
|
mappings.erase(e.ptr);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
Out(SYS_DIO|LOG_IMPORTANT) << TQString("Munmap failed with error %1 : %2").arg(errno).arg(strerror(errno)) << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
::close(fd);
|
|
|
|
fd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::read(Uint8* buf,Uint32 size,Uint64 off)
|
|
|
|
{
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
bool close_again = false;
|
|
|
|
|
|
|
|
// reopen the file if necessary
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
// Out() << "Reopening " << path << endl;
|
|
|
|
openFile(READ);
|
|
|
|
close_again = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (off >= file_size || off >= max_size)
|
|
|
|
{
|
|
|
|
throw Error(i18n("Error : Reading past the end of the file %1").arg(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
// jump to right position
|
|
|
|
SeekFile(fd,(Int64)off,SEEK_SET);
|
|
|
|
if ((Uint32)::read(fd,buf,size) != size)
|
|
|
|
{
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
|
|
|
|
throw Error(i18n("Error reading from %1").arg(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::write(const Uint8* buf,Uint32 size,Uint64 off)
|
|
|
|
{
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
bool close_again = false;
|
|
|
|
|
|
|
|
// reopen the file if necessary
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
// Out() << "Reopening " << path << endl;
|
|
|
|
openFile(RW);
|
|
|
|
close_again = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read_only)
|
|
|
|
throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path));
|
|
|
|
|
|
|
|
if (off + size > max_size)
|
|
|
|
{
|
|
|
|
Out() << "Warning : writing past the end of " << path << endl;
|
|
|
|
Out() << (off + size) << " " << max_size << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file_size < off)
|
|
|
|
{
|
|
|
|
//Out() << TQString("Writing %1 bytes at %2").arg(size).arg(off) << endl;
|
|
|
|
growFile(off - file_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
// jump to right position
|
|
|
|
SeekFile(fd,(Int64)off,SEEK_SET);
|
|
|
|
int ret = ::write(fd,buf,size);
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
|
|
|
|
if (ret == -1)
|
|
|
|
throw Error(i18n("Error writing to %1 : %2").arg(path).arg(strerror(errno)));
|
|
|
|
else if ((Uint32)ret != size)
|
|
|
|
{
|
|
|
|
Out() << TQString("Incomplete write of %1 bytes, should be %2").arg(ret).arg(size) << endl;
|
|
|
|
throw Error(i18n("Error writing to %1").arg(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (off + size > file_size)
|
|
|
|
file_size = off + size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CacheFile::closeTemporary()
|
|
|
|
{
|
|
|
|
if (fd == -1 || mappings.count() > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
::close(fd);
|
|
|
|
fd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CacheFile::preallocate(PreallocationThread* prealloc)
|
|
|
|
{
|
|
|
|
TQMutexLocker lock(&mutex);
|
|
|
|
|
|
|
|
if (FileSize(path) == max_size)
|
|
|
|
{
|
|
|
|
Out(SYS_GEN|LOG_NOTICE) << "File " << path << " already big enough" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Out(SYS_GEN|LOG_NOTICE) << "Preallocating file " << path << " (" << max_size << " bytes)" << endl;
|
|
|
|
bool close_again = false;
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
openFile(RW);
|
|
|
|
close_again = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read_only)
|
|
|
|
{
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
|
|
|
|
throw Error(i18n("Cannot open %1 for writing : readonly filesystem").arg(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
bool res = false;
|
|
|
|
|
|
|
|
#ifdef HAVE_XFS_XFS_H
|
|
|
|
if( (! res) && Settings::fullDiskPrealloc() && (Settings::fullDiskPreallocMethod() == 1) )
|
|
|
|
{
|
|
|
|
res = XfsPreallocate(fd, max_size);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(! res)
|
|
|
|
{
|
|
|
|
bt::TruncateFile(fd,max_size,!Settings::fullDiskPrealloc());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (bt::Error & e)
|
|
|
|
{
|
|
|
|
// first attempt failed, must be fat so try that
|
|
|
|
if (!FatPreallocate(fd,max_size))
|
|
|
|
{
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
|
|
|
|
throw Error(i18n("Cannot preallocate diskspace : %1").arg(strerror(errno)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file_size = FileSize(fd);
|
|
|
|
Out(SYS_GEN|LOG_DEBUG) << "file_size = " << file_size << endl;
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
}
|
|
|
|
|
|
|
|
Uint64 CacheFile::diskUsage()
|
|
|
|
{
|
|
|
|
Uint64 ret = 0;
|
|
|
|
bool close_again = false;
|
|
|
|
if (fd == -1)
|
|
|
|
{
|
|
|
|
openFile(READ);
|
|
|
|
close_again = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct stat sb;
|
|
|
|
if (fstat(fd,&sb) == 0)
|
|
|
|
{
|
|
|
|
ret = (Uint64)sb.st_blocks * 512;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Out(SYS_GEN|LOG_NOTICE) << "CF: " << path << " is taking up " << ret << " bytes" << endl;
|
|
|
|
if (close_again)
|
|
|
|
closeTemporary();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|