size_type file::get_size(error_code& ec) const { #ifdef TORRENT_WINDOWS LARGE_INTEGER file_size; if (!GetFileSizeEx(m_file_handle, &file_size)) { ec = error_code(GetLastError(), get_system_category()); return -1; } return file_size.QuadPart; #else struct stat fs; if (fstat(m_fd, &fs) != 0) { ec = error_code(errno, get_posix_category()); return -1; } return fs.st_size; #endif }
bool file::set_size(size_type s, error_code& ec) { TORRENT_ASSERT(is_open()); TORRENT_ASSERT(s >= 0); #ifdef TORRENT_WINDOWS LARGE_INTEGER offs; LARGE_INTEGER cur_size; if (GetFileSizeEx(m_file_handle, &cur_size) == FALSE) { ec = error_code(GetLastError(), get_system_category()); return false; } offs.QuadPart = s; // only set the file size if it's not already at // the right size. We don't want to update the // modification time if we don't have to if (cur_size.QuadPart != s) { if (SetFilePointerEx(m_file_handle, offs, &offs, FILE_BEGIN) == FALSE) { ec.assign(GetLastError(), get_system_category()); return false; } if (::SetEndOfFile(m_file_handle) == FALSE) { ec.assign(GetLastError(), get_system_category()); return false; } } #if _WIN32_WINNT >= 0x501 if ((m_open_mode & sparse) == 0) { // only allocate the space if the file // is not fully allocated DWORD high_dword = 0; offs.LowPart = GetCompressedFileSize(m_path.c_str(), &high_dword); offs.HighPart = high_dword; ec.assign(GetLastError(), get_system_category()); if (ec) return false; if (offs.QuadPart != s) { // if the user has permissions, avoid filling // the file with zeroes, but just fill it with // garbage instead SetFileValidData(m_file_handle, offs.QuadPart); } } #endif // _WIN32_WINNT >= 0x501 #else // NON-WINDOWS struct stat st; if (fstat(m_fd, &st) != 0) { ec.assign(errno, get_posix_category()); return false; } // only truncate the file if it doesn't already // have the right size. We don't want to update if (st.st_size != s && ftruncate(m_fd, s) < 0) { ec.assign(errno, get_posix_category()); return false; } // if we're not in sparse mode, allocate the storage // but only if the number of allocated blocks for the file // is less than the file size. Otherwise we would just // update the modification time of the file for no good // reason. if ((m_open_mode & sparse) == 0 && st.st_blocks < (s + st.st_blksize - 1) / st.st_blksize) { // How do we know that the file is already allocated? // if we always try to allocate the space, we'll update // the modification time without actually changing the file // but if we don't do anything if the file size is #ifdef F_PREALLOCATE fstore_t f = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, s, 0}; if (fcntl(m_fd, F_PREALLOCATE, &f) < 0) { ec = error_code(errno, get_posix_category()); return false; } #endif // F_PREALLOCATE #if defined TORRENT_LINUX || TORRENT_HAS_FALLOCATE int ret; #endif #if defined TORRENT_LINUX ret = my_fallocate(m_fd, 0, 0, s); // if we return 0, everything went fine // the fallocate call succeeded if (ret == 0) return true; // otherwise, something went wrong. If the error // is ENOSYS, just keep going and do it the old-fashioned // way. If fallocate failed with some other error, it // probably means the user should know about it, error out // and report it. if (errno != ENOSYS && errno != EOPNOTSUPP) { ec.assign(errno, get_posix_category()); return false; } #endif // TORRENT_LINUX #if TORRENT_HAS_FALLOCATE // if fallocate failed, we have to use posix_fallocate // which can be painfully slow // if you get a compile error here, you might want to // define TORRENT_HAS_FALLOCATE to 0. ret = posix_fallocate(m_fd, 0, s); // posix_allocate fails with EINVAL in case the underlying // filesystem does bot support this operation if (ret != 0 && ret != EINVAL) { ec = error_code(ret, get_posix_category()); return false; } #endif // TORRENT_HAS_FALLOCATE } #endif // TORRENT_WINDOWS return true; }
size_type file::writev(size_type file_offset, iovec_t const* bufs, int num_bufs, error_code& ec) { TORRENT_ASSERT((m_open_mode & rw_mask) == write_only || (m_open_mode & rw_mask) == read_write); TORRENT_ASSERT(bufs); TORRENT_ASSERT(num_bufs > 0); TORRENT_ASSERT(is_open()); #if defined TORRENT_WINDOWS || defined TORRENT_LINUX || defined TORRENT_DEBUG // make sure m_page_size is initialized init_file(); #endif #ifdef TORRENT_DEBUG if (m_open_mode & no_buffer) { bool eof = false; int size = 0; // when opened in no_buffer mode, the file_offset must // be aligned to pos_alignment() TORRENT_ASSERT((file_offset & (pos_alignment()-1)) == 0); for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) { TORRENT_ASSERT((uintptr_t(i->iov_base) & (buf_alignment()-1)) == 0); // every buffer must be a multiple of the page size // except for the last one TORRENT_ASSERT((i->iov_len & (size_alignment()-1)) == 0 || i == end-1); if ((i->iov_len & (size_alignment()-1)) != 0) eof = true; size += i->iov_len; } error_code code; if (eof) TORRENT_ASSERT(file_offset + size >= get_size(code)); } #endif #ifdef TORRENT_WINDOWS DWORD ret = 0; // since the ReadFileScatter requires the file to be opened // with no buffering, and no buffering requires page aligned // buffers, open the file in non-buffered mode in case the // buffer is not aligned. Most of the times the buffer should // be aligned though if ((m_open_mode & no_buffer) == 0) { // this means the buffer base or the buffer size is not aligned // to the page size. Use a regular file for this operation. LARGE_INTEGER offs; offs.QuadPart = file_offset; if (SetFilePointerEx(m_file_handle, offs, &offs, FILE_BEGIN) == FALSE) { ec = error_code(GetLastError(), get_system_category()); return -1; } for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) { DWORD intermediate = 0; if (WriteFile(m_file_handle, (char const*)i->iov_base , (DWORD)i->iov_len, &intermediate, 0) == FALSE) { ec = error_code(GetLastError(), get_system_category()); return -1; } ret += intermediate; } return ret; } int size = bufs_size(bufs, num_bufs); // number of pages for the write. round up int num_pages = (size + m_page_size - 1) / m_page_size; // allocate array of FILE_SEGMENT_ELEMENT for WriteFileGather FILE_SEGMENT_ELEMENT* segment_array = TORRENT_ALLOCA(FILE_SEGMENT_ELEMENT, num_pages + 1); FILE_SEGMENT_ELEMENT* cur_seg = segment_array; for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) { for (int k = 0; k < i->iov_len; k += m_page_size) { cur_seg->Buffer = PtrToPtr64((((char*)i->iov_base) + k)); ++cur_seg; } } // terminate the array cur_seg->Buffer = 0; OVERLAPPED ol; ol.Internal = 0; ol.InternalHigh = 0; ol.OffsetHigh = file_offset >> 32; ol.Offset = file_offset & 0xffffffff; ol.hEvent = CreateEvent(0, true, false, 0); ret += size; // if file_size is > 0, the file will be opened in unbuffered // mode after the write completes, and truncate the file to // file_size. size_type file_size = 0; if ((size & (m_page_size-1)) != 0) { // if size is not an even multiple, this must be the tail // of the file. Write the whole page and then open a new // file without FILE_FLAG_NO_BUFFERING and set the // file size to file_offset + size file_size = file_offset + size; size = num_pages * m_page_size; } if (WriteFileGather(m_file_handle, segment_array, size, 0, &ol) == 0) { if (GetLastError() != ERROR_IO_PENDING) { TORRENT_ASSERT(GetLastError() != ERROR_BAD_ARGUMENTS); ec = error_code(GetLastError(), get_system_category()); CloseHandle(ol.hEvent); return -1; } DWORD tmp; if (GetOverlappedResult(m_file_handle, &ol, &tmp, true) == 0) { ec = error_code(GetLastError(), get_system_category()); CloseHandle(ol.hEvent); return -1; } if (tmp < ret) ret = tmp; } CloseHandle(ol.hEvent); if (file_size > 0) { #if TORRENT_USE_WPATH #define CreateFile_ CreateFileW #else #define CreateFile_ CreateFileA #endif HANDLE f = CreateFile_(m_path.c_str(), GENERIC_WRITE , FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, 0); if (f == INVALID_HANDLE_VALUE) { ec = error_code(GetLastError(), get_system_category()); return -1; } LARGE_INTEGER offs; offs.QuadPart = file_size; if (SetFilePointerEx(f, offs, &offs, FILE_BEGIN) == FALSE) { CloseHandle(f); ec = error_code(GetLastError(), get_system_category()); return -1; } if (::SetEndOfFile(f) == FALSE) { ec = error_code(GetLastError(), get_system_category()); CloseHandle(f); return -1; } CloseHandle(f); } return ret; #else size_type ret = lseek(m_fd, file_offset, SEEK_SET); if (ret < 0) { ec = error_code(errno, get_posix_category()); return -1; } #if TORRENT_USE_WRITEV #ifdef TORRENT_LINUX bool aligned = false; int size = 0; // if we're not opened in no-buffer mode, we don't need alignment if ((m_open_mode & no_buffer) == 0) aligned = true; if (!aligned) { size = bufs_size(bufs, num_bufs); if ((size & (size_alignment()-1)) == 0) aligned = true; } if (aligned) #endif { ret = ::writev(m_fd, bufs, num_bufs); if (ret < 0) { ec = error_code(errno, get_posix_category()); return -1; } return ret; } #ifdef TORRENT_LINUX file::iovec_t* temp_bufs = TORRENT_ALLOCA(file::iovec_t, num_bufs); memcpy(temp_bufs, bufs, sizeof(file::iovec_t) * num_bufs); iovec_t& last = temp_bufs[num_bufs-1]; last.iov_len = (last.iov_len & ~(size_alignment()-1)) + size_alignment(); ret = ::writev(m_fd, temp_bufs, num_bufs); if (ret < 0) { ec = error_code(errno, get_posix_category()); return -1; } if (ftruncate(m_fd, file_offset + size) < 0) { ec = error_code(errno, get_posix_category()); return -1; } return (std::min)(ret, size_type(size)); #endif // TORRENT_LINUX #else // TORRENT_USE_WRITEV ret = 0; for (file::iovec_t const* i = bufs, *end(bufs + num_bufs); i < end; ++i) { int tmp = write(m_fd, i->iov_base, i->iov_len); if (tmp < 0) { ec = error_code(errno, get_posix_category()); return -1; } ret += tmp; if (tmp < i->iov_len) break; } return ret; #endif // TORRENT_USE_WRITEV #endif // TORRENT_WINDOWS }
file_handle file_pool::open_file(void* st, std::string const& p , int file_index, file_storage const& fs, int m, error_code& ec) { // potentially used to hold a reference to a file object that's // about to be destructed. If we have such object we assign it to // this member to be destructed after we release the mutex. On some // operating systems (such as OSX) closing a file may take a long // time. We don't want to hold the mutex for that. file_handle defer_destruction; mutex::scoped_lock l(m_mutex); #if TORRENT_USE_ASSERTS // we're not allowed to open a file // from a deleted storage! TORRENT_ASSERT(std::find(m_deleted_storages.begin(), m_deleted_storages.end(), std::make_pair(fs.name(), (void const*)&fs)) == m_deleted_storages.end()); #endif TORRENT_ASSERT(st != 0); TORRENT_ASSERT(is_complete(p)); TORRENT_ASSERT((m & file::rw_mask) == file::read_only || (m & file::rw_mask) == file::read_write); file_set::iterator i = m_files.find(std::make_pair(st, file_index)); if (i != m_files.end()) { lru_file_entry& e = i->second; e.last_use = aux::time_now(); if (e.key != st && ((e.mode & file::rw_mask) != file::read_only || (m & file::rw_mask) != file::read_only)) { // this means that another instance of the storage // is using the exact same file. ec = errors::file_collision; return file_handle(); } e.key = st; // if we asked for a file in write mode, // and the cached file is is not opened in // write mode, re-open it if ((((e.mode & file::rw_mask) != file::read_write) && ((m & file::rw_mask) == file::read_write)) || (e.mode & file::random_access) != (m & file::random_access)) { // close the file before we open it with // the new read/write privilages, since windows may // file opening a file twice. However, since there may // be outstanding operations on it, we can't close the // file, we can only delete our reference to it. // if this is the only reference to the file, it will be closed defer_destruction = e.file_ptr; e.file_ptr = boost::make_shared<file>(); std::string full_path = fs.file_path(file_index, p); if (!e.file_ptr->open(full_path, m, ec)) { m_files.erase(i); return file_handle(); } #ifdef TORRENT_WINDOWS if (m_low_prio_io) set_low_priority(e.file_ptr); #endif TORRENT_ASSERT(e.file_ptr->is_open()); e.mode = m; } return e.file_ptr; } lru_file_entry e; e.file_ptr = boost::make_shared<file>(); if (!e.file_ptr) { ec = error_code(ENOMEM, get_posix_category()); return e.file_ptr; } std::string full_path = fs.file_path(file_index, p); if (!e.file_ptr->open(full_path, m, ec)) return file_handle(); #ifdef TORRENT_WINDOWS if (m_low_prio_io) set_low_priority(e.file_ptr); #endif e.mode = m; e.key = st; m_files.insert(std::make_pair(std::make_pair(st, file_index), e)); TORRENT_ASSERT(e.file_ptr->is_open()); file_handle file_ptr = e.file_ptr; // the file is not in our cache if ((int)m_files.size() >= m_size) { // the file cache is at its maximum size, close // the least recently used (lru) file from it remove_oldest(l); } return file_ptr; }
boost::intrusive_ptr<file> file_pool::open_file(void* st, std::string const& p , file_storage::iterator fe, file_storage const& fs, int m, error_code& ec) { TORRENT_ASSERT(st != 0); TORRENT_ASSERT(is_complete(p)); TORRENT_ASSERT((m & file::rw_mask) == file::read_only || (m & file::rw_mask) == file::read_write); mutex::scoped_lock l(m_mutex); file_set::iterator i = m_files.find(std::make_pair(st, fs.file_index(*fe))); if (i != m_files.end()) { lru_file_entry& e = i->second; e.last_use = time_now(); if (e.key != st && ((e.mode & file::rw_mask) != file::read_only || (m & file::rw_mask) != file::read_only)) { // this means that another instance of the storage // is using the exact same file. #if BOOST_VERSION >= 103500 ec = errors::file_collision; #endif return boost::intrusive_ptr<file>(); } e.key = st; // if we asked for a file in write mode, // and the cached file is is not opened in // write mode, re-open it if ((((e.mode & file::rw_mask) != file::read_write) && ((m & file::rw_mask) == file::read_write)) || (e.mode & file::no_buffer) != (m & file::no_buffer) || (e.mode & file::random_access) != (m & file::random_access)) { // close the file before we open it with // the new read/write privilages TORRENT_ASSERT(e.file_ptr->refcount() == 1); #if TORRENT_CLOSE_MAY_BLOCK mutex::scoped_lock l(m_closer_mutex); m_queued_for_close.push_back(e.file_ptr); l.unlock(); e.file_ptr = new file; #else e.file_ptr->close(); #endif std::string full_path = combine_path(p, fs.file_path(*fe)); if (!e.file_ptr->open(full_path, m, ec)) { m_files.erase(i); return boost::intrusive_ptr<file>(); } #ifdef TORRENT_WINDOWS // file prio is supported on vista and up #if _WIN32_WINNT >= 0x0600 if (m_low_prio_io) { // TODO: load this function dynamically from Kernel32.dll FILE_IO_PRIORITY_HINT_INFO priorityHint; priorityHint.PriorityHint = IoPriorityHintLow; SetFileInformationByHandle(e.file_ptr->native_handle(), FileIoPriorityHintInfo, &priorityHint, sizeof(PriorityHint)); } #endif #endif TORRENT_ASSERT(e.file_ptr->is_open()); e.mode = m; } TORRENT_ASSERT((e.mode & file::no_buffer) == (m & file::no_buffer)); return e.file_ptr; } // the file is not in our cache if ((int)m_files.size() >= m_size) { // the file cache is at its maximum size, close // the least recently used (lru) file from it remove_oldest(); } lru_file_entry e; e.file_ptr.reset(new (std::nothrow)file); if (!e.file_ptr) { ec = error_code(ENOMEM, get_posix_category()); return e.file_ptr; } std::string full_path = combine_path(p, fs.file_path(*fe)); if (!e.file_ptr->open(full_path, m, ec)) return boost::intrusive_ptr<file>(); e.mode = m; e.key = st; m_files.insert(std::make_pair(std::make_pair(st, fs.file_index(*fe)), e)); TORRENT_ASSERT(e.file_ptr->is_open()); return e.file_ptr; }