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.
ktorrent/libktorrent/torrent/cachefile.cpp

508 lines
12 KiB

/***************************************************************************
* 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 bittqmask. 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").tqarg(path).tqarg(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").tqarg(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").tqarg(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").tqarg(path).tqarg(strerror(errno)));
else if (ret != nb)
throw Error(i18n("Cannot expand file %1 : incomplete write").tqarg(path));
to_write -= nb;
}
file_size += num;
//
// Out() << TQString("growing %1 = %2").tqarg(path).tqarg(kt::BytesToString(file_size)) << endl;
if (file_size != FileSize(fd))
{
// Out() << TQString("Homer Simpson %1 %2").tqarg(file_size).tqarg(sb.st_size) << endl;
fsync(fd);
if (file_size != FileSize(fd))
{
throw Error(i18n("Cannot expand file %1").tqarg(path));
}
}
}
void CacheFile::unmap(void* ptr,Uint32 size)
{
int ret = 0;
TQMutexLocker lock(&mutex);
// see if it wasn't an offsetted mapping
if (mappings.tqcontains(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").tqarg(errno).tqarg(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").tqarg(errno).tqarg(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").tqarg(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").tqarg(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").tqarg(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").tqarg(size).tqarg(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").tqarg(path).tqarg(strerror(errno)));
else if ((Uint32)ret != size)
{
Out() << TQString("Incomplete write of %1 bytes, should be %2").tqarg(ret).tqarg(size) << endl;
throw Error(i18n("Error writing to %1").tqarg(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").tqarg(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").tqarg(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;
}
}