From: Mike Gelfand Date: Tue, 23 May 2017 19:19:45 +0000 (+0300) Subject: ijuxda's and cfpp2p's patch for TRAC-532 with some fixes X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4b9511bc30b9c626fd73841011ce9ebd24e7892f;p=transmission ijuxda's and cfpp2p's patch for TRAC-532 with some fixes --- diff --git a/libtransmission/cache.c b/libtransmission/cache.c index ab921637b..077aa4fc4 100644 --- a/libtransmission/cache.c +++ b/libtransmission/cache.c @@ -450,6 +450,37 @@ int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t i) return err; } +int tr_cacheFlushPiece(tr_cache* cache, tr_torrent* torrent, tr_piece_index_t i) +{ + int pos; + int err = 0; + tr_block_index_t first; + tr_block_index_t last; + tr_torGetPieceBlockRange(torrent, i, &first, &last); + pos = findBlockPos(cache, torrent, first); + dbgmsg("flushing piece %d from cache to disk: blocks [%zu...%zu]", (int)i, (size_t)first, (size_t)last); + + /* flush out all the blocks in that piece */ + while (err == 0 && pos < tr_ptrArraySize(&cache->blocks)) + { + struct cache_block const* b = tr_ptrArrayNth(&cache->blocks, pos); + + if (b->tor != torrent) + { + break; + } + + if (b->block < first || b->block > last) + { + break; + } + + err = flushContiguous(cache, pos, getBlockRun(cache, pos, NULL)); + } + + return err; +} + int tr_cacheFlushTorrent(tr_cache* cache, tr_torrent* torrent) { int err = 0; diff --git a/libtransmission/cache.h b/libtransmission/cache.h index fe900bdc5..7a96b850c 100644 --- a/libtransmission/cache.h +++ b/libtransmission/cache.h @@ -48,4 +48,6 @@ int tr_cacheFlushDone(tr_cache* cache); int tr_cacheFlushTorrent(tr_cache* cache, tr_torrent* torrent); +int tr_cacheFlushPiece(tr_cache* cache, tr_torrent* torrent, tr_piece_index_t piece); + int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t file); diff --git a/libtransmission/fdlimit.c b/libtransmission/fdlimit.c index 49c6a0c41..049edf2fc 100644 --- a/libtransmission/fdlimit.c +++ b/libtransmission/fdlimit.c @@ -124,7 +124,8 @@ struct tr_cached_file bool is_writable; tr_sys_file_t fd; int torrent_id; - tr_file_index_t file_index; + uint32_t index_num; + tr_fd_index_type index_type; time_t used_at; }; @@ -270,7 +271,8 @@ static void fileset_construct(struct tr_fileset* set, int n) .is_writable = false, .fd = TR_BAD_SYS_FILE, .torrent_id = 0, - .file_index = 0, + .index_num = 0, + .index_type = TR_FD_INDEX_FILE, .used_at = 0 }; @@ -318,13 +320,13 @@ static void fileset_close_torrent(struct tr_fileset* set, int torrent_id) } } -static struct tr_cached_file* fileset_lookup(struct tr_fileset* set, int torrent_id, tr_file_index_t i) +static struct tr_cached_file* fileset_lookup(struct tr_fileset* set, int torrent_id, uint32_t i, tr_fd_index_type it) { if (set != NULL) { for (struct tr_cached_file* o = set->begin; o != set->end; ++o) { - if (torrent_id == o->torrent_id && i == o->file_index && cached_file_is_open(o)) + if (torrent_id == o->torrent_id && i == o->index_num && it == o->index_type && cached_file_is_open(o)) { return o; } @@ -439,11 +441,11 @@ static struct tr_fileset* get_fileset(tr_session* session) return &session->fdInfo->fileset; } -void tr_fdFileClose(tr_session* s, tr_torrent const* tor, tr_file_index_t i) +void tr_fdFileClose(tr_session* s, tr_torrent const* tor, uint32_t i, tr_fd_index_type it) { struct tr_cached_file* o; - if ((o = fileset_lookup(get_fileset(s), tr_torrentId(tor), i)) != NULL) + if ((o = fileset_lookup(get_fileset(s), tr_torrentId(tor), i, it)) != NULL) { /* flush writable files so that their mtimes will be * up-to-date when this function returns to the caller... */ @@ -456,9 +458,9 @@ void tr_fdFileClose(tr_session* s, tr_torrent const* tor, tr_file_index_t i) } } -tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, tr_file_index_t i, bool writable) +tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, uint32_t i, tr_fd_index_type it, bool writable) { - struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i); + struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i, it); if (o == NULL || (writable && !o->is_writable)) { @@ -469,11 +471,11 @@ tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, tr_file_index_t return o->fd; } -bool tr_fdFileGetCachedMTime(tr_session* s, int torrent_id, tr_file_index_t i, time_t* mtime) +bool tr_fdFileGetCachedMTime(tr_session* s, int torrent_id, uint32_t i, tr_fd_index_type it, time_t* mtime) { bool success; tr_sys_path_info info; - struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i); + struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i, it); if ((success = o != NULL && tr_sys_file_get_info(o->fd, &info, NULL))) { @@ -491,11 +493,11 @@ void tr_fdTorrentClose(tr_session* session, int torrent_id) } /* returns an fd on success, or a TR_BAD_SYS_FILE on failure and sets errno */ -tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_index_t i, char const* filename, bool writable, - tr_preallocation_mode allocation, uint64_t file_size) +tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, uint32_t i, tr_fd_index_type it, char const* filename, + bool writable, tr_preallocation_mode allocation, uint64_t file_size) { struct tr_fileset* set = get_fileset(session); - struct tr_cached_file* o = fileset_lookup(set, torrent_id, i); + struct tr_cached_file* o = fileset_lookup(set, torrent_id, i, it); if (o != NULL && writable && !o->is_writable) { @@ -522,7 +524,8 @@ tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_ind dbgmsg("checking out '%s'", filename); o->torrent_id = torrent_id; - o->file_index = i; + o->index_num = i; + o->index_type = it; o->used_at = tr_time(); return o->fd; } diff --git a/libtransmission/fdlimit.h b/libtransmission/fdlimit.h index caa15b31f..554c83d41 100644 --- a/libtransmission/fdlimit.h +++ b/libtransmission/fdlimit.h @@ -23,6 +23,13 @@ **** ***/ +typedef enum +{ + TR_FD_INDEX_FILE, + TR_FD_INDEX_PIECE +} +tr_fd_index_type; + /** * Returns an fd to the specified filename. * @@ -38,12 +45,14 @@ * * @see tr_fdFileClose */ -tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_index_t file_num, char const* filename, - bool do_write, tr_preallocation_mode preallocation_mode, uint64_t preallocation_file_size); +tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, uint32_t index_num, tr_fd_index_type index_type, + char const* filename, bool do_write, tr_preallocation_mode preallocation_mode, uint64_t preallocation_file_size); -tr_sys_file_t tr_fdFileGetCached(tr_session* session, int torrent_id, tr_file_index_t file_num, bool doWrite); +tr_sys_file_t tr_fdFileGetCached(tr_session* session, int torrent_id, uint32_t index_num, tr_fd_index_type index_type, + bool doWrite); -bool tr_fdFileGetCachedMTime(tr_session* session, int torrent_id, tr_file_index_t file_num, time_t* mtime); +bool tr_fdFileGetCachedMTime(tr_session* session, int torrent_id, uint32_t index_num, tr_fd_index_type index_type, + time_t* mtime); /** * Closes a file that's being held by our file repository. @@ -53,7 +62,7 @@ bool tr_fdFileGetCachedMTime(tr_session* session, int torrent_id, tr_file_index_ * * @see tr_fdFileCheckout */ -void tr_fdFileClose(tr_session* session, tr_torrent const* tor, tr_file_index_t file_num); +void tr_fdFileClose(tr_session* session, tr_torrent const* tor, uint32_t index_num, tr_fd_index_type index_type); /** * Closes all the files associated with a given torrent id diff --git a/libtransmission/inout.c b/libtransmission/inout.c index 78e8730a1..c40dd98fa 100644 --- a/libtransmission/inout.c +++ b/libtransmission/inout.c @@ -37,8 +37,8 @@ enum }; /* returns 0 on success, or an errno on failure */ -static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr_file_index_t fileIndex, uint64_t fileOffset, - void* buf, size_t buflen) +static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr_piece_index_t pieceIndex, uint32_t pieceOffset, + tr_file_index_t fileIndex, uint64_t fileOffset, void* buf, size_t buflen) { tr_sys_file_t fd; int err = 0; @@ -46,6 +46,11 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr tr_info const* const info = &tor->info; tr_file const* const file = &info->files[fileIndex]; + uint64_t offset; + uint64_t desiredSize; + uint32_t indexNum; + tr_fd_index_type indexType; + TR_ASSERT(fileIndex < info->fileCount); TR_ASSERT(file->length == 0 || fileOffset < file->length); TR_ASSERT(fileOffset + buflen <= file->length); @@ -59,27 +64,52 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr **** Find the fd ***/ - fd = tr_fdFileGetCached(session, tr_torrentId(tor), fileIndex, doWrite); + if (file->usept) + { + offset = pieceOffset; + desiredSize = tr_torPieceCountBytes(tor, pieceIndex); + indexNum = pieceIndex; + indexType = TR_FD_INDEX_PIECE; + } + else + { + offset = fileOffset; + desiredSize = file->length; + indexNum = fileIndex; + indexType = TR_FD_INDEX_FILE; + } + + fd = tr_fdFileGetCached(session, tr_torrentId(tor), indexNum, indexType, doWrite); if (fd == TR_BAD_SYS_FILE) { /* it's not cached, so open/create it now */ char* subpath; char const* base; + bool fileExists; /* see if the file exists... */ - if (!tr_torrentFindFile2(tor, fileIndex, &base, &subpath, NULL)) + if (file->usept) { - /* we can't read a file that doesn't exist... */ - if (!doWrite) + fileExists = tr_torrentFindPieceTemp2(tor, pieceIndex, &base, &subpath); + } + else + { + fileExists = tr_torrentFindFile2(tor, fileIndex, &base, &subpath, NULL); + + if (!fileExists) { - err = ENOENT; + /* figure out where the file should go, so we can create it */ + base = tr_torrentGetCurrentDir(tor); + subpath = tr_sessionIsIncompleteFileNamingEnabled(tor->session) ? tr_torrentBuildPartial(tor, fileIndex) : + tr_strdup(file->name); } + } - /* figure out where the file should go, so we can create it */ - base = tr_torrentGetCurrentDir(tor); - subpath = tr_sessionIsIncompleteFileNamingEnabled(tor->session) ? tr_torrentBuildPartial(tor, fileIndex) : - tr_strdup(file->name); + /* we can't read a file that doesn't exist... */ + if (!fileExists && !doWrite) + { + err = ENOENT; } if (err == 0) @@ -88,8 +118,8 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr char* filename = tr_buildPath(base, subpath, NULL); int const prealloc = (file->dnd || !doWrite) ? TR_PREALLOCATE_NONE : tor->session->preallocationMode; - if ((fd = tr_fdFileCheckout(session, tor->uniqueId, fileIndex, filename, doWrite, prealloc, - file->length)) == TR_BAD_SYS_FILE) + if ((fd = tr_fdFileCheckout(session, tor->uniqueId, indexNum, indexType, filename, doWrite, prealloc, + desiredSize)) == TR_BAD_SYS_FILE) { err = errno; tr_logAddTorErr(tor, "tr_fdFileCheckout failed for \"%s\": %s", filename, tr_strerror(err)); @@ -116,7 +146,7 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr if (ioMode == TR_IO_READ) { - if (!tr_sys_file_read_at(fd, buf, buflen, fileOffset, NULL, &error)) + if (!tr_sys_file_read_at(fd, buf, buflen, offset, NULL, &error)) { err = error->code; tr_logAddTorErr(tor, "read failed for \"%s\": %s", file->name, error->message); @@ -125,7 +155,7 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr } else if (ioMode == TR_IO_WRITE) { - if (!tr_sys_file_write_at(fd, buf, buflen, fileOffset, NULL, &error)) + if (!tr_sys_file_write_at(fd, buf, buflen, offset, NULL, &error)) { err = error->code; tr_logAddTorErr(tor, "write failed for \"%s\": %s", file->name, error->message); @@ -134,7 +164,7 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr } else if (ioMode == TR_IO_PREFETCH) { - tr_sys_file_advise(fd, fileOffset, buflen, TR_SYS_FILE_ADVICE_WILL_NEED, NULL); + tr_sys_file_advise(fd, offset, buflen, TR_SYS_FILE_ADVICE_WILL_NEED, NULL); } else { @@ -203,13 +233,33 @@ static int readOrWritePiece(tr_torrent* tor, int ioMode, tr_piece_index_t pieceI while (buflen != 0 && err == 0) { tr_file const* file = &info->files[fileIndex]; - uint64_t const bytesThisPass = MIN(buflen, file->length - fileOffset); + uint32_t leftInPiece = tr_torPieceCountBytes(tor, pieceIndex) - pieceOffset; + uint64_t leftInFile = file->length - fileOffset; + uint64_t bytesThisPass; + + bytesThisPass = MIN(leftInFile, leftInPiece); + bytesThisPass = MIN(bytesThisPass, buflen); - err = readOrWriteBytes(tor->session, tor, ioMode, fileIndex, fileOffset, buf, bytesThisPass); + err = readOrWriteBytes(tor->session, tor, ioMode, pieceIndex, pieceOffset, fileIndex, fileOffset, buf, bytesThisPass); buf += bytesThisPass; buflen -= bytesThisPass; - fileIndex++; - fileOffset = 0; + + leftInPiece -= bytesThisPass; + leftInFile -= bytesThisPass; + pieceOffset += bytesThisPass; + fileOffset += bytesThisPass; + + if (leftInPiece == 0) + { + ++pieceIndex; + pieceOffset = 0; + } + + if (leftInFile == 0) + { + ++fileIndex; + fileOffset = 0; + } if (err != 0 && ioMode == TR_IO_WRITE && tor->error != TR_STAT_LOCAL_ERROR) { diff --git a/libtransmission/platform.c b/libtransmission/platform.c index 4a8de7c4f..aa054530b 100644 --- a/libtransmission/platform.c +++ b/libtransmission/platform.c @@ -289,9 +289,11 @@ static char const* getHomeDir(void) #if defined(__APPLE__) || defined(_WIN32) #define RESUME_SUBDIR "Resume" #define TORRENT_SUBDIR "Torrents" +#define PIECE_SUBDIR "Pieces" #else #define RESUME_SUBDIR "resume" #define TORRENT_SUBDIR "torrents" +#define PIECE_SUBDIR "pieces" #endif void tr_setConfigDir(tr_session* session, char const* configDir) @@ -459,6 +461,11 @@ char const* tr_getDefaultDownloadDir(void) return user_dir; } +char const* tr_getDefaultPieceSubDir(void) +{ + return PIECE_SUBDIR; +} + /*** **** ***/ diff --git a/libtransmission/platform.h b/libtransmission/platform.h index 802b76a22..4601af754 100644 --- a/libtransmission/platform.h +++ b/libtransmission/platform.h @@ -31,6 +31,9 @@ void tr_setConfigDir(tr_session* session, char const* configDir); /** @brief return the directory where .resume files are stored */ char const* tr_getResumeDir(tr_session const*); +/** @brief return the default name of the directory where temporary piece files are stored */ +char const* tr_getDefaultPieceSubDir(void); + /** @brief return the directory where .torrent files are stored */ char const* tr_getTorrentDir(tr_session const*); diff --git a/libtransmission/quark.c b/libtransmission/quark.c index 861050057..ae9002951 100644 --- a/libtransmission/quark.c +++ b/libtransmission/quark.c @@ -239,6 +239,7 @@ static struct tr_key_struct const my_static[] = { "pex-enabled", 11 }, { "piece", 5 }, { "piece length", 12 }, + { "piece-temp-dir", 14 }, { "pieceCount", 10 }, { "pieceSize", 9 }, { "pieces", 6 }, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index d40ab75fa..e0b4ec370 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -241,6 +241,7 @@ enum TR_KEY_pex_enabled, TR_KEY_piece, TR_KEY_piece_length, + TR_KEY_piece_temp_dir, TR_KEY_pieceCount, TR_KEY_pieceSize, TR_KEY_pieces, diff --git a/libtransmission/resume.c b/libtransmission/resume.c index 82b1f8f07..f4fbb5af9 100644 --- a/libtransmission/resume.c +++ b/libtransmission/resume.c @@ -716,6 +716,7 @@ void tr_torrentSaveResume(tr_torrent* tor) tr_variantDictAddStr(&top, TR_KEY_incomplete_dir, tor->incompleteDir); } + tr_variantDictAddStr(&top, TR_KEY_piece_temp_dir, tor->pieceTempDir); tr_variantDictAddInt(&top, TR_KEY_downloaded, tor->downloadedPrev + tor->downloadedCur); tr_variantDictAddInt(&top, TR_KEY_uploaded, tor->uploadedPrev + tor->uploadedCur); tr_variantDictAddInt(&top, TR_KEY_max_peers, tor->maxConnectedPeers); @@ -811,6 +812,14 @@ static uint64_t loadFromFile(tr_torrent* tor, uint64_t fieldsToLoad) fieldsLoaded |= TR_FR_INCOMPLETE_DIR; } + if ((fieldsToLoad & TR_FR_PIECE_TEMP_DIR) != 0 && tr_variantDictFindStr(&top, TR_KEY_piece_temp_dir, &str, &len) && + str != NULL && *str != '\0') + { + tr_free(tor->pieceTempDir); + tor->pieceTempDir = tr_strndup(str, len); + fieldsLoaded |= TR_FR_PIECE_TEMP_DIR; + } + if ((fieldsToLoad & TR_FR_DOWNLOADED) != 0 && tr_variantDictFindInt(&top, TR_KEY_downloaded, &i)) { tor->downloadedPrev = i; diff --git a/libtransmission/resume.h b/libtransmission/resume.h index 513c1b5f7..e619568c5 100644 --- a/libtransmission/resume.h +++ b/libtransmission/resume.h @@ -35,7 +35,8 @@ enum TR_FR_TIME_SEEDING = (1 << 18), TR_FR_TIME_DOWNLOADING = (1 << 19), TR_FR_FILENAMES = (1 << 20), - TR_FR_NAME = (1 << 21) + TR_FR_NAME = (1 << 21), + TR_FR_PIECE_TEMP_DIR = (1 << 22), }; /** diff --git a/libtransmission/rpcimpl.c b/libtransmission/rpcimpl.c index e3896f197..02b15c6bc 100644 --- a/libtransmission/rpcimpl.c +++ b/libtransmission/rpcimpl.c @@ -1919,6 +1919,11 @@ static char const* sessionSet(tr_session* session, tr_variant* args_in, tr_varia tr_sessionSetDownloadDir(session, download_dir); } + if (tr_variantDictFindStr(args_in, TR_KEY_piece_temp_dir, &str, NULL)) + { + tr_sessionSetPieceTempDir(session, str); + } + if (tr_variantDictFindInt(args_in, TR_KEY_queue_stalled_minutes, &i)) { tr_sessionSetQueueStalledMinutes(session, i); @@ -2206,6 +2211,10 @@ static void addSessionField(tr_session* s, tr_variant* d, tr_quark key) tr_variantDictAddInt(d, key, tr_sessionGetQueueSize(s, TR_DOWN)); break; + case TR_KEY_piece_temp_dir: + tr_variantDictAddStr(d, key, tr_sessionGetPieceTempDir(s)); + break; + case TR_KEY_peer_limit_global: tr_variantDictAddInt(d, key, tr_sessionGetPeerLimit(s)); break; diff --git a/libtransmission/session.c b/libtransmission/session.c index 86d054f7f..de5574c7c 100644 --- a/libtransmission/session.c +++ b/libtransmission/session.c @@ -337,6 +337,7 @@ void tr_sessionGetDefaultSettings(tr_variant* d) tr_variantDictAddBool(d, TR_KEY_utp_enabled, true); tr_variantDictAddBool(d, TR_KEY_lpd_enabled, false); tr_variantDictAddStr(d, TR_KEY_download_dir, tr_getDefaultDownloadDir()); + tr_variantDictAddStr(d, TR_KEY_piece_temp_dir, ""); tr_variantDictAddInt(d, TR_KEY_speed_limit_down, 100); tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false); tr_variantDictAddInt(d, TR_KEY_encryption, TR_DEFAULT_ENCRYPTION); @@ -407,6 +408,7 @@ void tr_sessionGetSettings(tr_session* s, tr_variant* d) tr_variantDictAddBool(d, TR_KEY_utp_enabled, s->isUTPEnabled); tr_variantDictAddBool(d, TR_KEY_lpd_enabled, s->isLPDEnabled); tr_variantDictAddStr(d, TR_KEY_download_dir, tr_sessionGetDownloadDir(s)); + tr_variantDictAddStr(d, TR_KEY_piece_temp_dir, tr_sessionGetPieceTempDir(s)); tr_variantDictAddInt(d, TR_KEY_download_queue_size, tr_sessionGetQueueSize(s, TR_DOWN)); tr_variantDictAddBool(d, TR_KEY_download_queue_enabled, tr_sessionGetQueueEnabled(s, TR_DOWN)); tr_variantDictAddInt(d, TR_KEY_speed_limit_down, tr_sessionGetSpeedLimit_KBps(s, TR_DOWN)); @@ -939,6 +941,11 @@ static void sessionSetImpl(void* vdata) tr_sessionSetDownloadDir(session, str); } + if (tr_variantDictFindStr(settings, TR_KEY_piece_temp_dir, &str, NULL)) + { + tr_sessionSetPieceTempDir(session, str); + } + if (tr_variantDictFindStr(settings, TR_KEY_incomplete_dir, &str, NULL)) { tr_sessionSetIncompleteDir(session, str); @@ -1200,6 +1207,34 @@ int64_t tr_sessionGetDirFreeSpace(tr_session* session, char const* dir) **** ***/ +char const* tr_sessionGetPieceTempDir(tr_session const* session) +{ + assert(tr_isSession(session)); + return session->pieceDir; +} + +void tr_sessionSetPieceTempDir(tr_session* session, char const* path) +{ + assert(tr_isSession(session)); + tr_sessionLock(session); + tr_free(session->pieceDir); + + if (path != NULL && *path != '\0') + { + session->pieceDir = tr_strdup(path); + } + else + { + session->pieceDir = tr_buildPath(tr_sessionGetConfigDir(session), tr_getDefaultPieceSubDir(), NULL); + } + + tr_sessionUnlock(session); +} + +/*** +**** +***/ + void tr_sessionSetIncompleteFileNamingEnabled(tr_session* session, bool b) { TR_ASSERT(tr_isSession(session)); @@ -2098,6 +2133,7 @@ void tr_sessionClose(tr_session* session) tr_free(session->torrentDoneScript); tr_free(session->configDir); tr_free(session->resumeDir); + tr_free(session->pieceDir); tr_free(session->torrentDir); tr_free(session->incompleteDir); tr_free(session->blocklist_url); diff --git a/libtransmission/session.h b/libtransmission/session.h index 9499507f9..da3fa65bf 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -181,6 +181,7 @@ struct tr_session char* configDir; char* resumeDir; + char* pieceDir; char* torrentDir; char* incompleteDir; diff --git a/libtransmission/torrent.c b/libtransmission/torrent.c index 4f7e75cbe..94826b17e 100644 --- a/libtransmission/torrent.c +++ b/libtransmission/torrent.c @@ -34,6 +34,7 @@ #include "fdlimit.h" /* tr_fdTorrentClose */ #include "file.h" #include "inout.h" /* tr_ioTestPiece() */ +#include "list.h" #include "log.h" #include "magnet.h" #include "metainfo.h" @@ -954,6 +955,12 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) tor->incompleteDir = tr_strdup(dir); } + { + char* s = tr_metainfoGetBasename(&tor->info); + tor->pieceTempDir = tr_buildPath(tr_sessionGetPieceTempDir(tor->session), s, NULL); + tr_free(s); + } + tr_bandwidthConstruct(&tor->bandwidth, session, &session->bandwidth); tor->bandwidth.priority = tr_ctorGetBandwidthPriority(ctor); @@ -976,6 +983,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) tr_ctorInitTorrentWanted(ctor, tor); refreshCurrentDir(tor); + tr_sys_dir_create(tor->pieceTempDir, TR_SYS_DIR_CREATE_PARENTS, 0777, NULL); doStart = tor->isRunning; tor->isRunning = false; @@ -1692,6 +1700,7 @@ static void freeTorrent(tr_torrent* tor) tr_cpDestruct(&tor->completion); tr_free(tor->downloadDir); + tr_free(tor->pieceTempDir); tr_free(tor->incompleteDir); if (tor == session->torrentList) @@ -2049,6 +2058,39 @@ void tr_torrentStop(tr_torrent* tor) } } +/** + * @brief Delete all temporary piece files for the torrent. + */ +static void tr_torrentRemovePieceTemp(tr_torrent* tor) +{ + tr_sys_dir_t dir; + tr_list* files = NULL; + char const* path = tor->pieceTempDir; + + if ((dir = tr_sys_dir_open(path, NULL)) != TR_BAD_SYS_DIR) + { + char const* name; + + while ((name = tr_sys_dir_read_name(dir, NULL)) != NULL) + { + if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) + { + tr_list_append(&files, tr_buildPath(path, name, NULL)); + } + } + + tr_sys_dir_close(dir, NULL); + tr_list_append(&files, tr_strdup(path)); + } + + for (tr_list* l = files; l != NULL; l = l->next) + { + tr_sys_path_remove(l->data, NULL); + } + + tr_list_free(&files, tr_free); +} + static void closeTorrent(void* vtor) { tr_torrent* tor = vtor; @@ -2068,6 +2110,7 @@ static void closeTorrent(void* vtor) { tr_metainfoRemoveSaved(tor->session, &tor->info); tr_torrentRemoveResume(tor); + tr_torrentRemovePieceTemp(tor); } tor->isRunning = false; @@ -2491,71 +2534,363 @@ tr_priority_t* tr_torrentGetFilePriorities(tr_torrent const* tor) return p; } +/*** +**** +***/ + +bool tr_torrentFindPieceTemp2(tr_torrent const* tor, tr_piece_index_t pieceIndex, char const** base, char** subpath) +{ + char const* b = tor->pieceTempDir; + char* s; + char* filename; + bool exists; + + s = tr_strdup_printf("%010u.dat", pieceIndex); + + filename = tr_buildPath(b, s, NULL); + exists = tr_sys_path_exists(filename, NULL); + tr_free(filename); + + if (base != NULL) + { + *base = b; + } + + if (subpath != NULL) + { + *subpath = s; + } + else + { + tr_free(s); + } + + return exists; +} + +char* tr_torrentFindPieceTemp(tr_torrent const* tor, tr_piece_index_t pieceIndex) +{ + char const* base; + char* subpath; + char* filename = NULL; + + if (tr_torrentFindPieceTemp2(tor, pieceIndex, &base, &subpath)) + { + filename = tr_buildPath(base, subpath, NULL); + tr_free(subpath); + } + + return filename; +} + /** *** File DND **/ +static void removePieceTemp(tr_torrent* tor, tr_piece_index_t piece) +{ + char* filename; + + tr_fdFileClose(tor->session, tor, piece, TR_FD_INDEX_PIECE); + + if ((filename = tr_torrentFindPieceTemp(tor, piece)) != NULL) + { + tr_sys_path_remove(filename, NULL); + tr_free(filename); + } +} + +/** + * @return TRUE if the file should use temporary piece files. + */ +static bool usePieceTemp(tr_torrent* tor, tr_file_index_t i) +{ + if (!tor->info.files[i].dnd) + { + return false; + } + + tr_cacheFlushFile(tor->session->cache, tor, i); + + return !tr_torrentFindFile2(tor, i, NULL, NULL, NULL); +} + +static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t fileIndex); + +/** + * @note This function assumes @a tor is valid and already locked, and + * @a file_index is a valid file index for the torrent. + * @note When @a file->dnd is TRUE and @a dnd is false, this function has + * the side effect of copying over data from temporary piece files + * to the destination file. + * @see readOrWriteBytes() + */ static void setFileDND(tr_torrent* tor, tr_file_index_t fileIndex, int doDownload) { int8_t const dnd = !doDownload; - tr_piece_index_t firstPiece; - int8_t firstPieceDND; - tr_piece_index_t lastPiece; - int8_t lastPieceDND; tr_file* file = &tor->info.files[fileIndex]; - file->dnd = dnd; - firstPiece = file->firstPiece; - lastPiece = file->lastPiece; + if (file->dnd == dnd) + { + return; + } + + tr_piece_index_t const fpindex = file->firstPiece; + tr_piece_index_t const lpindex = file->lastPiece; + bool fpmovept; + bool lpmovept; + uint64_t fpoverlap; + uint64_t lpoverlap; + uint64_t fpoffset; + uint64_t lpoffset; + uint8_t* fpbuf; + uint8_t* lpbuf; + + /* Flags indicating whether we need to copy over existing data + * from temporary piece files to the actual destination file. */ + fpmovept = file->usept && doDownload; + lpmovept = fpmovept && fpindex != lpindex; + + /* Check cache and filesystem to make sure temporary piece files exist. */ + + if (fpmovept) + { + tr_cacheFlushPiece(tor->session->cache, tor, fpindex); + fpmovept = tr_torrentFindPieceTemp2(tor, fpindex, NULL, NULL); + } + + if (lpmovept) + { + tr_cacheFlushPiece(tor->session->cache, tor, lpindex); + lpmovept = tr_torrentFindPieceTemp2(tor, lpindex, NULL, NULL); + } - /* can't set the first piece to DND unless - every file using that piece is DND */ - firstPieceDND = dnd; + bool const rwfpmovept = fpmovept; + bool const rwlpmovept = lpmovept; - if (fileIndex > 0) + if (fpmovept) { - for (tr_file_index_t i = fileIndex - 1; firstPieceDND; --i) + char* filename = tr_torrentFindPieceTemp(tor, fpindex); + tr_sys_file_t const fdpt = filename == NULL ? TR_BAD_SYS_FILE : tr_sys_file_open(filename, + TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0, NULL); + tr_free(filename); + + if (fdpt != TR_BAD_SYS_FILE) { - if (tor->info.files[i].lastPiece != firstPiece) + fpoffset = file->offset - tr_pieceOffset(tor, fpindex, 0, 0); + fpoverlap = tr_torPieceCountBytes(tor, fpindex) - fpoffset; + + if (fpoverlap > file->length) { - break; + fpoverlap = file->length; } - firstPieceDND = tor->info.files[i].dnd; + fpbuf = tr_malloc0(fpoverlap); - if (i == 0) + if (!tr_sys_file_read_at(fdpt, fpbuf, fpoverlap, fpoffset, NULL, NULL)) { - break; + tr_free(fpbuf); + fpmovept = false; + } + + tr_sys_file_close(fdpt, NULL); + } + else + { + fpmovept = false; + } + } + + if (lpmovept && rwfpmovept == fpmovept) + { + char* filename = tr_torrentFindPieceTemp(tor, lpindex); + tr_sys_file_t const fdpt = filename == NULL ? TR_BAD_SYS_FILE : tr_sys_file_open(filename, + TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0, NULL); + tr_free(filename); + + if (fdpt != TR_BAD_SYS_FILE) + { + lpoffset = 0; + lpoverlap = file->offset + file->length - tr_pieceOffset(tor, lpindex, 0, 0); + lpbuf = tr_malloc0(lpoverlap); + + if (!tr_sys_file_read_at(fdpt, lpbuf, lpoverlap, lpoffset, NULL, NULL)) + { + tr_free (lpbuf); + lpmovept = false; + + if (fpmovept) + { + tr_free(fpbuf); + } + } + + tr_sys_file_close(fdpt, NULL); + } + else + { + lpmovept = false; + + if (fpmovept) + { + tr_free(fpbuf); } } } - /* can't set the last piece to DND unless - every file using that piece is DND */ - lastPieceDND = dnd; + uint8_t const svdnd = file->dnd; + bool const svusept = file->usept; - for (tr_file_index_t i = fileIndex + 1; lastPieceDND && i < tor->info.fileCount; ++i) + file->dnd = dnd; + + if (fpmovept || lpmovept) + { + file->usept = false; + } + else { - if (tor->info.files[i].firstPiece != lastPiece) + file->usept = usePieceTemp(tor, fileIndex); + } + + if (fpmovept && rwlpmovept == lpmovept) + { + int const nr = tr_ioWrite(tor, fpindex, fpoffset, fpoverlap, fpbuf); + + if (nr != 0) { - break; + fpmovept = false; + + if (lpmovept) + { + tr_free(lpbuf); + } } - lastPieceDND = tor->info.files[i].dnd; + tr_free(fpbuf); } - if (firstPiece == lastPiece) + if (lpmovept && rwfpmovept == fpmovept) { - tor->info.pieces[firstPiece].dnd = firstPieceDND && lastPieceDND; + int const nr = tr_ioWrite(tor, lpindex, lpoffset, lpoverlap, lpbuf); + + if (nr != 0) + { + lpmovept = false; + } + + tr_free(lpbuf); + } + + /* Check conditions for setting piece DND and + * removing temporary piece files: + * - We can set the piece to DND if all files using + * that piece are DND. + * - We can remove the temporary piece file if all + * files using it have 'usept' set to false. + * - Do not delete temporary piece files if write failed. */ + + /* Do not change DND state if piece temp copy failed */ + + if (rwfpmovept == fpmovept && rwlpmovept == lpmovept) + { + tr_file_index_t i; + + bool fpdnd = file->dnd; + bool fpnopt = !file->usept; + + if (fileIndex > 0) + { + for (i = fileIndex - 1; fpdnd || fpnopt; --i) + { + if (tor->info.files[i].lastPiece != fpindex) + { + break; + } + + if (fpdnd) + { + fpdnd = tor->info.files[i].dnd; + } + + if (fpnopt) + { + fpnopt = !tor->info.files[i].usept; + } + + if (i == 0) + { + break; + } + } + } + + bool lpdnd = file->dnd; + bool lpnopt = !file->usept; + + for (i = fileIndex + 1; (lpdnd || lpnopt) && i < tor->info.fileCount; ++i) + { + if (tor->info.files[i].firstPiece != lpindex) + { + break; + } + + if (lpdnd) + { + lpdnd = tor->info.files[i].dnd; + } + + if (lpnopt) + { + lpnopt = !tor->info.files[i].usept; + } + } + + if (fpindex == lpindex) + { + tor->info.pieces[fpindex].dnd = fpdnd && lpdnd; + + if (fpnopt && lpnopt) + { + removePieceTemp(tor, fpindex); + } + } + else + { + tor->info.pieces[fpindex].dnd = fpdnd; + tor->info.pieces[lpindex].dnd = lpdnd; + + for (tr_piece_index_t p = fpindex + 1; p < lpindex; ++p) + { + tor->info.pieces[p].dnd = dnd; + } + + if (fpnopt) + { + removePieceTemp(tor, fpindex); + } + + if (lpnopt) + { + removePieceTemp(tor, lpindex); + } + } + + if (tr_cpFileIsComplete(&tor->completion, fileIndex)) + { + tr_torrentFileCompleted(tor, fileIndex); + } } else { - tor->info.pieces[firstPiece].dnd = firstPieceDND; - tor->info.pieces[lastPiece].dnd = lastPieceDND; + file->dnd = svdnd; - for (tr_piece_index_t pp = firstPiece + 1; pp < lastPiece; ++pp) + if (rwfpmovept || rwlpmovept) + { + file->usept = svusept; + } + else { - tor->info.pieces[pp].dnd = dnd; + file->usept = svusept; } } } @@ -2783,7 +3118,7 @@ time_t tr_torrentGetFileMTime(tr_torrent const* tor, tr_file_index_t i) { time_t mtime = 0; - if (!tr_fdFileGetCachedMTime(tor->session, tor->uniqueId, i, &mtime)) + if (!tr_fdFileGetCachedMTime(tor->session, tor->uniqueId, i, TR_FD_INDEX_FILE, &mtime)) { tr_torrentFindFile2(tor, i, NULL, NULL, &mtime); } @@ -3418,7 +3753,7 @@ static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t fileIndex) /* close the file so that we can reopen in read-only mode as needed */ tr_cacheFlushFile(tor->session->cache, tor, fileIndex); - tr_fdFileClose(tor->session, tor, fileIndex); + tr_fdFileClose(tor->session, tor, fileIndex, TR_FD_INDEX_FILE); /* now that the file is complete and closed, we can start watching its * mtime timestamp for changes to know if we need to reverify pieces */ diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 52c2efcf1..f4940b252 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -134,6 +134,9 @@ struct tr_torrent /* Where the files are when the torrent is incomplete */ char* incompleteDir; + /* Where temporary piece files are stored. */ + char* pieceTempDir; + /* Length, in bytes, of the "info" dict in the .torrent file. */ size_t infoDictLength; @@ -360,6 +363,26 @@ void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t blockIndex); */ bool tr_torrentFindFile2(tr_torrent const*, tr_file_index_t fileNo, char const** base, char** subpath, time_t* mtime); +/** + * Like tr_torrentFindFile2() but for temporary piece files. + * Both @a base and @a subpath may be NULL. + * + * @see tr_torrentFindFile2() + * @see tr_torrentFilePieceTemp() + */ +bool tr_torrentFindPieceTemp2(tr_torrent const* tor, tr_piece_index_t pieceIndex, char const** base, char** subpath); + +/** + * Get the full path of the temporary piece file for piece + * with index @a pieceIndex. + * + * @return a newly allocated string containing the full filename + * or NULL if it does not exist. + * + * @see tr_torrentFindPieceTemp2() + */ +char* tr_torrentFindPieceTemp(tr_torrent const* tor, tr_piece_index_t pieceIndex); + /* Returns a newly-allocated version of the tr_file.name string * that's been modified to denote that it's not a complete file yet. * In the current implementation this is done by appending ".part" diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index ac1871adb..6ec84b1a5 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -247,6 +247,23 @@ char const* tr_sessionGetDownloadDir(tr_session const* session); */ int64_t tr_sessionGetDirFreeSpace(tr_session* session, char const* dir); +/** +* @brief Get the full path to where temporary piece files are stored. + */ +char const* tr_sessionGetPieceTempDir(tr_session const* session); + +/** + * @brief Set the full path to where temporary piece files are stored. + * @param path If empty or NULL a default path consisting of the + * config directory and a "pieces" sub-directory is set. + * @note Changing this setting will only affect new torrents. Existing + * torrents will continue to use the same directory as was set + * when they were created. + * @see tr_sessionGetConfigDir + * @see tr_getDefaultPieceSubDir + */ +void tr_sessionSetPieceTempDir(tr_session* session, char const* path); + /** * @brief Set the torrent's bandwidth priority. */ @@ -1573,6 +1590,7 @@ typedef struct tr_file int8_t priority; /* TR_PRI_HIGH, _NORMAL, or _LOW */ int8_t dnd; /* "do not download" flag */ int8_t is_renamed; /* true if we're using a different path from the one in the metainfo; ie, if the user has renamed it */ + int8_t usept; /* nonzero if using temporary piece files */ tr_piece_index_t firstPiece; /* We need pieces [firstPiece... */ tr_piece_index_t lastPiece; /* ...lastPiece] to dl this file */ uint64_t offset; /* file begins at the torrent's nth byte */ diff --git a/libtransmission/verify.c b/libtransmission/verify.c index b4f0d6055..d4e524a8d 100644 --- a/libtransmission/verify.c +++ b/libtransmission/verify.c @@ -65,6 +65,21 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag) hadPiece = tr_torrentPieceIsComplete(tor, pieceIndex); } + /* check for temporary piece files */ + if (file->usept && (piecePos == 0 || filePos == 0) && fd == TR_BAD_SYS_FILE && + (pieceIndex == file->firstPiece || pieceIndex == file->lastPiece)) + { + char* filename = tr_torrentFindPieceTemp(tor, pieceIndex); + fd = filename == NULL ? TR_BAD_SYS_FILE : tr_sys_file_open(filename, TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0, + NULL); + tr_free(filename); + + if (filePos == 0 && fileIndex != prevFileIndex) + { + prevFileIndex = fileIndex; + } + } + /* if we're starting a new file... */ if (filePos == 0 && fd == TR_BAD_SYS_FILE && fileIndex != prevFileIndex) { @@ -84,13 +99,14 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag) /* read a bit */ if (fd != TR_BAD_SYS_FILE) { + uint64_t const readPos = file->usept ? piecePos : filePos; uint64_t numRead; - if (tr_sys_file_read_at(fd, buffer, bytesThisPass, filePos, &numRead, NULL) && numRead > 0) + if (tr_sys_file_read_at(fd, buffer, bytesThisPass, readPos, &numRead, NULL) && numRead > 0) { bytesThisPass = numRead; tr_sha1_update(sha, buffer, bytesThisPass); - tr_sys_file_advise(fd, filePos, bytesThisPass, TR_SYS_FILE_ADVICE_DONT_NEED, NULL); + tr_sys_file_advise(fd, readPos, bytesThisPass, TR_SYS_FILE_ADVICE_DONT_NEED, NULL); } }