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
}
示例#4
0
	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;
	}
示例#5
0
	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;
	}