forked from jiuyuan/JYCache
1050 lines
41 KiB
C++
1050 lines
41 KiB
C++
/*
|
|
* s3fs - FUSE-based file system backed by Amazon S3
|
|
*
|
|
* Copyright(C) 2007 Takeshi Nakatani <ggtakec.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.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
#include "s3fs_logger.h"
|
|
#include "s3fs_util.h"
|
|
#include "fdcache_fdinfo.h"
|
|
#include "fdcache_pseudofd.h"
|
|
#include "fdcache_entity.h"
|
|
#include "curl.h"
|
|
#include "string_util.h"
|
|
#include "threadpoolman.h"
|
|
|
|
//------------------------------------------------
|
|
// PseudoFdInfo class variables
|
|
//------------------------------------------------
|
|
int PseudoFdInfo::max_threads = -1;
|
|
int PseudoFdInfo::opt_max_threads = -1;
|
|
|
|
//------------------------------------------------
|
|
// PseudoFdInfo class methods
|
|
//------------------------------------------------
|
|
//
|
|
// Worker function for uploading
|
|
//
|
|
void* PseudoFdInfo::MultipartUploadThreadWorker(void* arg)
|
|
{
|
|
std::unique_ptr<pseudofdinfo_thparam> pthparam(static_cast<pseudofdinfo_thparam*>(arg));
|
|
if(!pthparam || !(pthparam->ppseudofdinfo)){
|
|
return reinterpret_cast<void*>(-EIO);
|
|
}
|
|
S3FS_PRN_INFO3("Upload Part Thread [tpath=%s][start=%lld][size=%lld][part=%d]", pthparam->path.c_str(), static_cast<long long>(pthparam->start), static_cast<long long>(pthparam->size), pthparam->part_num);
|
|
|
|
int result;
|
|
{
|
|
AutoLock auto_lock(&(pthparam->ppseudofdinfo->upload_list_lock));
|
|
|
|
if(0 != (result = pthparam->ppseudofdinfo->last_result)){
|
|
S3FS_PRN_DBG("Already occurred error, thus this thread worker is exiting.");
|
|
|
|
if(!pthparam->ppseudofdinfo->CompleteInstruction(result, AutoLock::ALREADY_LOCKED)){ // result will be overwritten with the same value.
|
|
result = -EIO;
|
|
}
|
|
return reinterpret_cast<void*>(result);
|
|
}
|
|
}
|
|
|
|
// setup and make curl object
|
|
std::unique_ptr<S3fsCurl> s3fscurl(S3fsCurl::CreateParallelS3fsCurl(pthparam->path.c_str(), pthparam->upload_fd, pthparam->start, pthparam->size, pthparam->part_num, pthparam->is_copy, pthparam->petag, pthparam->upload_id, result));
|
|
if(nullptr == s3fscurl){
|
|
S3FS_PRN_ERR("failed creating s3fs curl object for uploading [path=%s][start=%lld][size=%lld][part=%d]", pthparam->path.c_str(), static_cast<long long>(pthparam->start), static_cast<long long>(pthparam->size), pthparam->part_num);
|
|
|
|
// set result for exiting
|
|
if(!pthparam->ppseudofdinfo->CompleteInstruction(result, AutoLock::NONE)){
|
|
result = -EIO;
|
|
}
|
|
return reinterpret_cast<void*>(result);
|
|
}
|
|
|
|
// Send request and get result
|
|
if(0 == (result = s3fscurl->RequestPerform())){
|
|
S3FS_PRN_DBG("succeed uploading [path=%s][start=%lld][size=%lld][part=%d]", pthparam->path.c_str(), static_cast<long long>(pthparam->start), static_cast<long long>(pthparam->size), pthparam->part_num);
|
|
if(!s3fscurl->MixMultipartPostComplete()){
|
|
S3FS_PRN_ERR("failed completion uploading [path=%s][start=%lld][size=%lld][part=%d]", pthparam->path.c_str(), static_cast<long long>(pthparam->start), static_cast<long long>(pthparam->size), pthparam->part_num);
|
|
result = -EIO;
|
|
}
|
|
}else{
|
|
S3FS_PRN_ERR("failed uploading with error(%d) [path=%s][start=%lld][size=%lld][part=%d]", result, pthparam->path.c_str(), static_cast<long long>(pthparam->start), static_cast<long long>(pthparam->size), pthparam->part_num);
|
|
}
|
|
s3fscurl->DestroyCurlHandle(true, false);
|
|
|
|
// set result
|
|
if(!pthparam->ppseudofdinfo->CompleteInstruction(result, AutoLock::NONE)){
|
|
S3FS_PRN_WARN("This thread worker is about to end, so it doesn't return an EIO here and runs to the end.");
|
|
}
|
|
|
|
return reinterpret_cast<void*>(result);
|
|
}
|
|
|
|
//------------------------------------------------
|
|
// PseudoFdInfo methods
|
|
//------------------------------------------------
|
|
PseudoFdInfo::PseudoFdInfo(int fd, int open_flags) : pseudo_fd(-1), physical_fd(fd), flags(0), upload_fd(-1), uploaded_sem(0), instruct_count(0), completed_count(0), last_result(0)
|
|
{
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init(&attr);
|
|
#if S3FS_PTHREAD_ERRORCHECK
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
|
#endif
|
|
int result;
|
|
if(0 != (result = pthread_mutex_init(&upload_list_lock, &attr))){
|
|
S3FS_PRN_CRIT("failed to init upload_list_lock: %d", result);
|
|
abort();
|
|
}
|
|
is_lock_init = true;
|
|
|
|
if(-1 != physical_fd){
|
|
pseudo_fd = PseudoFdManager::Get();
|
|
flags = open_flags;
|
|
}
|
|
}
|
|
|
|
PseudoFdInfo::~PseudoFdInfo()
|
|
{
|
|
Clear(); // call before destrying the mutex
|
|
|
|
if(is_lock_init){
|
|
int result;
|
|
if(0 != (result = pthread_mutex_destroy(&upload_list_lock))){
|
|
S3FS_PRN_CRIT("failed to destroy upload_list_lock: %d", result);
|
|
abort();
|
|
}
|
|
is_lock_init = false;
|
|
}
|
|
}
|
|
|
|
bool PseudoFdInfo::Clear()
|
|
{
|
|
// cppcheck-suppress unmatchedSuppression
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if(!CancelAllThreads() || !ResetUploadInfo(AutoLock::NONE)){
|
|
return false;
|
|
}
|
|
CloseUploadFd();
|
|
|
|
if(-1 != pseudo_fd){
|
|
PseudoFdManager::Release(pseudo_fd);
|
|
}
|
|
pseudo_fd = -1;
|
|
physical_fd = -1;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PseudoFdInfo::CloseUploadFd()
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
|
|
if(-1 != upload_fd){
|
|
close(upload_fd);
|
|
}
|
|
}
|
|
|
|
bool PseudoFdInfo::OpenUploadFd(AutoLock::Type type)
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock, type);
|
|
|
|
if(-1 != upload_fd){
|
|
// already initialized
|
|
return true;
|
|
}
|
|
if(-1 == physical_fd){
|
|
S3FS_PRN_ERR("physical_fd is not initialized yet.");
|
|
return false;
|
|
}
|
|
|
|
// duplicate fd
|
|
int fd;
|
|
if(-1 == (fd = dup(physical_fd))){
|
|
S3FS_PRN_ERR("Could not duplicate physical file descriptor(errno=%d)", errno);
|
|
return false;
|
|
}
|
|
scope_guard guard([&]() { close(fd); });
|
|
|
|
if(0 != lseek(fd, 0, SEEK_SET)){
|
|
S3FS_PRN_ERR("Could not seek physical file descriptor(errno=%d)", errno);
|
|
return false;
|
|
}
|
|
struct stat st;
|
|
if(-1 == fstat(fd, &st)){
|
|
S3FS_PRN_ERR("Invalid file descriptor for uploading(errno=%d)", errno);
|
|
return false;
|
|
}
|
|
|
|
guard.dismiss();
|
|
upload_fd = fd;
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::Set(int fd, int open_flags)
|
|
{
|
|
if(-1 == fd){
|
|
return false;
|
|
}
|
|
Clear();
|
|
physical_fd = fd;
|
|
pseudo_fd = PseudoFdManager::Get();
|
|
flags = open_flags;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::Writable() const
|
|
{
|
|
if(-1 == pseudo_fd){
|
|
return false;
|
|
}
|
|
if(0 == (flags & (O_WRONLY | O_RDWR))){
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::Readable() const
|
|
{
|
|
if(-1 == pseudo_fd){
|
|
return false;
|
|
}
|
|
// O_RDONLY is 0x00, it means any pattern is readable.
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::ClearUploadInfo(bool is_cancel_mp)
|
|
{
|
|
if(is_cancel_mp){
|
|
// cppcheck-suppress unmatchedSuppression
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if(!CancelAllThreads()){
|
|
return false;
|
|
}
|
|
}
|
|
return ResetUploadInfo(AutoLock::NONE);
|
|
}
|
|
|
|
bool PseudoFdInfo::ResetUploadInfo(AutoLock::Type type)
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock, type);
|
|
|
|
upload_id.erase();
|
|
upload_list.clear();
|
|
instruct_count = 0;
|
|
completed_count = 0;
|
|
last_result = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::RowInitialUploadInfo(const std::string& id, bool is_cancel_mp, AutoLock::Type type)
|
|
{
|
|
if(is_cancel_mp && AutoLock::ALREADY_LOCKED == type){
|
|
S3FS_PRN_ERR("Internal Error: Could not call this with type=AutoLock::ALREADY_LOCKED and is_cancel_mp=true");
|
|
return false;
|
|
}
|
|
|
|
if(is_cancel_mp){
|
|
// cppcheck-suppress unmatchedSuppression
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if(!ClearUploadInfo(is_cancel_mp)){
|
|
return false;
|
|
}
|
|
}else{
|
|
// cppcheck-suppress unmatchedSuppression
|
|
// cppcheck-suppress knownConditionTrueFalse
|
|
if(!ResetUploadInfo(type)){
|
|
return false;
|
|
}
|
|
}
|
|
|
|
AutoLock auto_lock(&upload_list_lock, type);
|
|
upload_id = id;
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::CompleteInstruction(int result, AutoLock::Type type)
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock, type);
|
|
|
|
if(0 != result){
|
|
last_result = result;
|
|
}
|
|
|
|
if(0 >= instruct_count){
|
|
S3FS_PRN_ERR("Internal error: instruct_count caused an underflow.");
|
|
return false;
|
|
}
|
|
--instruct_count;
|
|
++completed_count;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::GetUploadId(std::string& id) const
|
|
{
|
|
if(!IsUploading()){
|
|
S3FS_PRN_ERR("Multipart Upload has not started yet.");
|
|
return false;
|
|
}
|
|
id = upload_id;
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::GetEtaglist(etaglist_t& list) const
|
|
{
|
|
if(!IsUploading()){
|
|
S3FS_PRN_ERR("Multipart Upload has not started yet.");
|
|
return false;
|
|
}
|
|
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
|
|
list.clear();
|
|
for(filepart_list_t::const_iterator iter = upload_list.begin(); iter != upload_list.end(); ++iter){
|
|
if(iter->petag){
|
|
list.push_back(*(iter->petag));
|
|
}else{
|
|
S3FS_PRN_ERR("The pointer to the etag string is null(internal error).");
|
|
return false;
|
|
}
|
|
}
|
|
return !list.empty();
|
|
}
|
|
|
|
// [NOTE]
|
|
// This method adds a part for a multipart upload.
|
|
// The added new part must be an area that is exactly continuous with the
|
|
// immediately preceding part.
|
|
// An error will occur if it is discontinuous or if it overlaps with an
|
|
// existing area.
|
|
//
|
|
bool PseudoFdInfo::AppendUploadPart(off_t start, off_t size, bool is_copy, etagpair** ppetag)
|
|
{
|
|
if(!IsUploading()){
|
|
S3FS_PRN_ERR("Multipart Upload has not started yet.");
|
|
return false;
|
|
}
|
|
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
off_t next_start_pos = 0;
|
|
if(!upload_list.empty()){
|
|
next_start_pos = upload_list.back().startpos + upload_list.back().size;
|
|
}
|
|
if(start != next_start_pos){
|
|
S3FS_PRN_ERR("The expected starting position for the next part is %lld, but %lld was specified.", static_cast<long long int>(next_start_pos), static_cast<long long int>(start));
|
|
return false;
|
|
}
|
|
|
|
// make part number
|
|
int partnumber = static_cast<int>(upload_list.size()) + 1;
|
|
|
|
// add new part
|
|
etagpair* petag_entity = etag_entities.add(etagpair(nullptr, partnumber)); // [NOTE] Create the etag entity and register it in the list.
|
|
upload_list.emplace_back(false, physical_fd, start, size, is_copy, petag_entity);
|
|
|
|
// set etag pointer
|
|
if(ppetag){
|
|
*ppetag = petag_entity;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Utility for sorting upload list
|
|
//
|
|
static bool filepart_partnum_compare(const filepart& src1, const filepart& src2)
|
|
{
|
|
return src1.get_part_number() < src2.get_part_number();
|
|
}
|
|
|
|
bool PseudoFdInfo::InsertUploadPart(off_t start, off_t size, int part_num, bool is_copy, etagpair** ppetag, AutoLock::Type type)
|
|
{
|
|
//S3FS_PRN_DBG("[start=%lld][size=%lld][part_num=%d][is_copy=%s]", static_cast<long long int>(start), static_cast<long long int>(size), part_num, (is_copy ? "true" : "false"));
|
|
|
|
if(!IsUploading()){
|
|
S3FS_PRN_ERR("Multipart Upload has not started yet.");
|
|
return false;
|
|
}
|
|
if(start < 0 || size <= 0 || part_num < 0 || !ppetag){
|
|
S3FS_PRN_ERR("Parameters are wrong.");
|
|
return false;
|
|
}
|
|
|
|
AutoLock auto_lock(&upload_list_lock, type);
|
|
|
|
// insert new part
|
|
etagpair* petag_entity = etag_entities.add(etagpair(nullptr, part_num));
|
|
upload_list.emplace_back(false, physical_fd, start, size, is_copy, petag_entity);
|
|
|
|
// sort by part number
|
|
std::sort(upload_list.begin(), upload_list.end(), filepart_partnum_compare);
|
|
|
|
// set etag pointer
|
|
*ppetag = petag_entity;
|
|
|
|
return true;
|
|
}
|
|
|
|
// [NOTE]
|
|
// This method only launches the upload thread.
|
|
// Check the maximum number of threads before calling.
|
|
//
|
|
bool PseudoFdInfo::ParallelMultipartUpload(const char* path, const mp_part_list_t& mplist, bool is_copy, AutoLock::Type type)
|
|
{
|
|
//S3FS_PRN_DBG("[path=%s][mplist(%zu)]", SAFESTRPTR(path), mplist.size());
|
|
|
|
AutoLock auto_lock(&upload_list_lock, type);
|
|
|
|
if(mplist.empty()){
|
|
// nothing to do
|
|
return true;
|
|
}
|
|
if(!OpenUploadFd(AutoLock::ALREADY_LOCKED)){
|
|
return false;
|
|
}
|
|
|
|
for(mp_part_list_t::const_iterator iter = mplist.begin(); iter != mplist.end(); ++iter){
|
|
// Insert upload part
|
|
etagpair* petag = nullptr;
|
|
if(!InsertUploadPart(iter->start, iter->size, iter->part_num, is_copy, &petag, AutoLock::ALREADY_LOCKED)){
|
|
S3FS_PRN_ERR("Failed to insert insert upload part(path=%s, start=%lld, size=%lld, part=%d, copy=%s) to mplist", SAFESTRPTR(path), static_cast<long long int>(iter->start), static_cast<long long int>(iter->size), iter->part_num, (is_copy ? "true" : "false"));
|
|
return false;
|
|
}
|
|
|
|
// make parameter for my thread
|
|
pseudofdinfo_thparam* thargs = new pseudofdinfo_thparam;
|
|
thargs->ppseudofdinfo = this;
|
|
thargs->path = SAFESTRPTR(path);
|
|
thargs->upload_id = upload_id;
|
|
thargs->upload_fd = upload_fd;
|
|
thargs->start = iter->start;
|
|
thargs->size = iter->size;
|
|
thargs->is_copy = is_copy;
|
|
thargs->part_num = iter->part_num;
|
|
thargs->petag = petag;
|
|
|
|
// make parameter for thread pool
|
|
std::unique_ptr<thpoolman_param> ppoolparam(new thpoolman_param);
|
|
ppoolparam->args = thargs;
|
|
ppoolparam->psem = &uploaded_sem;
|
|
ppoolparam->pfunc = PseudoFdInfo::MultipartUploadThreadWorker;
|
|
|
|
// setup instruction
|
|
if(!ThreadPoolMan::Instruct(std::move(ppoolparam))){
|
|
S3FS_PRN_ERR("failed setup instruction for uploading.");
|
|
delete thargs;
|
|
return false;
|
|
}
|
|
++instruct_count;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool PseudoFdInfo::ParallelMultipartUploadAll(const char* path, const mp_part_list_t& to_upload_list, const mp_part_list_t& copy_list, int& result)
|
|
{
|
|
S3FS_PRN_DBG("[path=%s][to_upload_list(%zu)][copy_list(%zu)]", SAFESTRPTR(path), to_upload_list.size(), copy_list.size());
|
|
|
|
result = 0;
|
|
|
|
if(!OpenUploadFd(AutoLock::NONE)){
|
|
return false;
|
|
}
|
|
|
|
if(!ParallelMultipartUpload(path, to_upload_list, false, AutoLock::NONE) || !ParallelMultipartUpload(path, copy_list, true, AutoLock::NONE)){
|
|
S3FS_PRN_ERR("Failed setup instruction for uploading(path=%s, to_upload_list=%zu, copy_list=%zu).", SAFESTRPTR(path), to_upload_list.size(), copy_list.size());
|
|
return false;
|
|
}
|
|
|
|
// Wait for all thread exiting
|
|
result = WaitAllThreadsExit();
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Upload the last updated Untreated area
|
|
//
|
|
// [Overview]
|
|
// Uploads untreated areas with the maximum multipart upload size as the
|
|
// boundary.
|
|
//
|
|
// * The starting position of the untreated area is aligned with the maximum
|
|
// multipart upload size as the boundary.
|
|
// * If there is an uploaded area that overlaps with the aligned untreated
|
|
// area, that uploaded area is canceled and absorbed by the untreated area.
|
|
// * Upload only when the aligned untreated area exceeds the maximum multipart
|
|
// upload size.
|
|
// * When the start position of the untreated area is changed to boundary
|
|
// alignment(to backward), and if that gap area is remained, that area is
|
|
// rest to untreated area.
|
|
//
|
|
ssize_t PseudoFdInfo::UploadBoundaryLastUntreatedArea(const char* path, headers_t& meta, FdEntity* pfdent)
|
|
{
|
|
S3FS_PRN_DBG("[path=%s][pseudo_fd=%d][physical_fd=%d]", SAFESTRPTR(path), pseudo_fd, physical_fd);
|
|
|
|
if(!path || -1 == physical_fd || -1 == pseudo_fd || !pfdent){
|
|
S3FS_PRN_ERR("pseudo_fd(%d) to physical_fd(%d) for path(%s) is not opened or not writable, or pfdent is nullptr.", pseudo_fd, physical_fd, path);
|
|
return -EBADF;
|
|
}
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
|
|
//
|
|
// Get last update untreated area
|
|
//
|
|
off_t last_untreated_start = 0;
|
|
off_t last_untreated_size = 0;
|
|
if(!pfdent->GetLastUpdateUntreatedPart(last_untreated_start, last_untreated_size) || last_untreated_start < 0 || last_untreated_size <= 0){
|
|
S3FS_PRN_WARN("Not found last update untreated area or it is empty, thus return without any error.");
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Aligns the start position of the last updated raw area with the boundary
|
|
//
|
|
// * Align the last updated raw space with the maximum upload size boundary.
|
|
// * The remaining size of the part before the boundary is will not be uploaded.
|
|
//
|
|
off_t max_mp_size = S3fsCurl::GetMultipartSize();
|
|
off_t aligned_start = ((last_untreated_start / max_mp_size) + (0 < (last_untreated_start % max_mp_size) ? 1 : 0)) * max_mp_size;
|
|
if((last_untreated_start + last_untreated_size) <= aligned_start){
|
|
S3FS_PRN_INFO("After the untreated area(start=%lld, size=%lld) is aligned with the boundary, the aligned start(%lld) exceeds the untreated area, so there is nothing to do.", static_cast<long long int>(last_untreated_start), static_cast<long long int>(last_untreated_size), static_cast<long long int>(aligned_start));
|
|
return 0;
|
|
}
|
|
|
|
off_t aligned_size = (((last_untreated_start + last_untreated_size) - aligned_start) / max_mp_size) * max_mp_size;
|
|
if(0 == aligned_size){
|
|
S3FS_PRN_DBG("After the untreated area(start=%lld, size=%lld) is aligned with the boundary(start is %lld), the aligned size is empty, so nothing to do.", static_cast<long long int>(last_untreated_start), static_cast<long long int>(last_untreated_size), static_cast<long long int>(aligned_start));
|
|
return 0;
|
|
}
|
|
|
|
off_t front_rem_start = last_untreated_start; // start of the remainder untreated area in front of the boundary
|
|
off_t front_rem_size = aligned_start - last_untreated_start; // size of the remainder untreated area in front of the boundary
|
|
|
|
//
|
|
// Get the area for uploading, if last update treated area can be uploaded.
|
|
//
|
|
// [NOTE]
|
|
// * Create the updoad area list, if the untreated area aligned with the boundary
|
|
// exceeds the maximum upload size.
|
|
// * If it overlaps with an area that has already been uploaded(unloaded list),
|
|
// that area is added to the cancellation list and included in the untreated area.
|
|
//
|
|
mp_part_list_t to_upload_list;
|
|
filepart_list_t cancel_uploaded_list;
|
|
if(!ExtractUploadPartsFromUntreatedArea(aligned_start, aligned_size, to_upload_list, cancel_uploaded_list, S3fsCurl::GetMultipartSize())){
|
|
S3FS_PRN_ERR("Failed to extract upload parts from last untreated area.");
|
|
return -EIO;
|
|
}
|
|
if(to_upload_list.empty()){
|
|
S3FS_PRN_INFO("There is nothing to upload. In most cases, the untreated area does not meet the upload size.");
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Has multipart uploading already started?
|
|
//
|
|
if(!IsUploading()){
|
|
// Multipart uploading hasn't started yet, so start it.
|
|
//
|
|
S3fsCurl s3fscurl(true);
|
|
std::string tmp_upload_id;
|
|
int result;
|
|
if(0 != (result = s3fscurl.PreMultipartPostRequest(path, meta, tmp_upload_id, true))){
|
|
S3FS_PRN_ERR("failed to setup multipart upload(create upload id) by errno(%d)", result);
|
|
return result;
|
|
}
|
|
if(!RowInitialUploadInfo(tmp_upload_id, false/* not need to cancel */, AutoLock::ALREADY_LOCKED)){
|
|
S3FS_PRN_ERR("failed to setup multipart upload(set upload id to object)");
|
|
return result;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Output debug level information
|
|
//
|
|
// When canceling(overwriting) a part that has already been uploaded, output it.
|
|
//
|
|
if(S3fsLog::IsS3fsLogDbg()){
|
|
for(filepart_list_t::const_iterator cancel_iter = cancel_uploaded_list.begin(); cancel_iter != cancel_uploaded_list.end(); ++cancel_iter){
|
|
S3FS_PRN_DBG("Cancel uploaded: start(%lld), size(%lld), part number(%d)", static_cast<long long int>(cancel_iter->startpos), static_cast<long long int>(cancel_iter->size), (cancel_iter->petag ? cancel_iter->petag->part_num : -1));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Upload Multipart parts
|
|
//
|
|
if(!ParallelMultipartUpload(path, to_upload_list, false, AutoLock::ALREADY_LOCKED)){
|
|
S3FS_PRN_ERR("Failed to upload multipart parts.");
|
|
return -EIO;
|
|
}
|
|
|
|
//
|
|
// Exclude the uploaded Untreated area and update the last Untreated area.
|
|
//
|
|
off_t behind_rem_start = aligned_start + aligned_size;
|
|
off_t behind_rem_size = (last_untreated_start + last_untreated_size) - behind_rem_start;
|
|
|
|
if(!pfdent->ReplaceLastUpdateUntreatedPart(front_rem_start, front_rem_size, behind_rem_start, behind_rem_size)){
|
|
S3FS_PRN_WARN("The last untreated area could not be detected and the uploaded area could not be excluded from it, but continue because it does not affect the overall processing.");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PseudoFdInfo::WaitAllThreadsExit()
|
|
{
|
|
int result;
|
|
bool is_loop = true;
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
if(0 == instruct_count && 0 == completed_count){
|
|
result = last_result;
|
|
is_loop = false;
|
|
}
|
|
}
|
|
|
|
while(is_loop){
|
|
// need to wait the worker exiting
|
|
uploaded_sem.wait();
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
if(0 < completed_count){
|
|
--completed_count;
|
|
}
|
|
if(0 == instruct_count && 0 == completed_count){
|
|
// break loop
|
|
result = last_result;
|
|
is_loop = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool PseudoFdInfo::CancelAllThreads()
|
|
{
|
|
bool need_cancel = false;
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
if(0 < instruct_count && 0 < completed_count){
|
|
S3FS_PRN_INFO("The upload thread is running, so cancel them and wait for the end.");
|
|
need_cancel = true;
|
|
last_result = -ECANCELED; // to stop thread running
|
|
}
|
|
}
|
|
if(need_cancel){
|
|
WaitAllThreadsExit();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Extract the list for multipart upload from the Unteated Area
|
|
//
|
|
// The untreated_start parameter must be set aligning it with the boundaries
|
|
// of the maximum multipart upload size. This method expects it to be bounded.
|
|
//
|
|
// This method creates the upload area aligned from the untreated area by
|
|
// maximum size and creates the required list.
|
|
// If it overlaps with an area that has already been uploaded, the overlapped
|
|
// upload area will be canceled and absorbed by the untreated area.
|
|
// If the list creation process is complete and areas smaller than the maximum
|
|
// size remain, those area will be reset to untreated_start and untreated_size
|
|
// and returned to the caller.
|
|
// If the called untreated area is smaller than the maximum size of the
|
|
// multipart upload, no list will be created.
|
|
//
|
|
// [NOTE]
|
|
// Maximum multipart upload size must be uploading boundary.
|
|
//
|
|
bool PseudoFdInfo::ExtractUploadPartsFromUntreatedArea(const off_t& untreated_start, const off_t& untreated_size, mp_part_list_t& to_upload_list, filepart_list_t& cancel_upload_list, off_t max_mp_size)
|
|
{
|
|
if(untreated_start < 0 || untreated_size <= 0){
|
|
S3FS_PRN_ERR("Paramters are wrong(untreated_start=%lld, untreated_size=%lld).", static_cast<long long int>(untreated_start), static_cast<long long int>(untreated_size));
|
|
return false;
|
|
}
|
|
|
|
// Initiliaze lists
|
|
to_upload_list.clear();
|
|
cancel_upload_list.clear();
|
|
|
|
//
|
|
// Align start position with maximum multipart upload boundaries
|
|
//
|
|
off_t aligned_start = (untreated_start / max_mp_size) * max_mp_size;
|
|
off_t aligned_size = untreated_size + (untreated_start - aligned_start);
|
|
|
|
//
|
|
// Check aligned untreated size
|
|
//
|
|
if(aligned_size < max_mp_size){
|
|
S3FS_PRN_INFO("untreated area(start=%lld, size=%lld) to aligned boundary(start=%lld, size=%lld) is smaller than max mp size(%lld), so nothing to do.", static_cast<long long int>(untreated_start), static_cast<long long int>(untreated_size), static_cast<long long int>(aligned_start), static_cast<long long int>(aligned_size), static_cast<long long int>(max_mp_size));
|
|
return true; // successful termination
|
|
}
|
|
|
|
//
|
|
// Check each unloaded area in list
|
|
//
|
|
// [NOTE]
|
|
// The uploaded area must be to be aligned by boundary.
|
|
// Also, it is assumed that it must not be a copy area.
|
|
// So if the areas overlap, include uploaded area as an untreated area.
|
|
//
|
|
for(filepart_list_t::iterator cur_iter = upload_list.begin(); cur_iter != upload_list.end(); /* ++cur_iter */){
|
|
// Check overlap
|
|
if((cur_iter->startpos + cur_iter->size - 1) < aligned_start || (aligned_start + aligned_size - 1) < cur_iter->startpos){
|
|
// Areas do not overlap
|
|
++cur_iter;
|
|
|
|
}else{
|
|
// The areas overlap
|
|
//
|
|
// Since the start position of the uploaded area is aligned with the boundary,
|
|
// it is not necessary to check the start position.
|
|
// If the uploaded area exceeds the untreated area, expand the untreated area.
|
|
//
|
|
if((aligned_start + aligned_size - 1) < (cur_iter->startpos + cur_iter->size - 1)){
|
|
aligned_size += (cur_iter->startpos + cur_iter->size) - (aligned_start + aligned_size);
|
|
}
|
|
|
|
//
|
|
// Add this to cancel list
|
|
//
|
|
cancel_upload_list.push_back(*cur_iter); // Copy and Push to cancel list
|
|
cur_iter = upload_list.erase(cur_iter);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add upload area to the list
|
|
//
|
|
while(max_mp_size <= aligned_size){
|
|
int part_num = static_cast<int>((aligned_start / max_mp_size) + 1);
|
|
to_upload_list.emplace_back(aligned_start, max_mp_size, part_num);
|
|
|
|
aligned_start += max_mp_size;
|
|
aligned_size -= max_mp_size;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Extract the area lists to be uploaded/downloaded for the entire file.
|
|
//
|
|
// [Parameters]
|
|
// to_upload_list : A list of areas to upload in multipart upload.
|
|
// to_copy_list : A list of areas for copy upload in multipart upload.
|
|
// to_download_list : A list of areas that must be downloaded before multipart upload.
|
|
// cancel_upload_list : A list of areas that have already been uploaded and will be canceled(overwritten).
|
|
// wait_upload_complete : If cancellation areas exist, this flag is set to true when it is necessary to wait until the upload of those cancellation areas is complete.
|
|
// file_size : The size of the upload file.
|
|
// use_copy : Specify true if copy multipart upload is available.
|
|
//
|
|
// [NOTE]
|
|
// The untreated_list in fdentity does not change, but upload_list is changed.
|
|
// (If you want to restore it, you can use cancel_upload_list.)
|
|
//
|
|
bool PseudoFdInfo::ExtractUploadPartsFromAllArea(UntreatedParts& untreated_list, mp_part_list_t& to_upload_list, mp_part_list_t& to_copy_list, mp_part_list_t& to_download_list, filepart_list_t& cancel_upload_list, bool& wait_upload_complete, off_t max_mp_size, off_t file_size, bool use_copy)
|
|
{
|
|
AutoLock auto_lock(&upload_list_lock);
|
|
|
|
// Initiliaze lists
|
|
to_upload_list.clear();
|
|
to_copy_list.clear();
|
|
to_download_list.clear();
|
|
cancel_upload_list.clear();
|
|
wait_upload_complete = false;
|
|
|
|
// Duplicate untreated list
|
|
untreated_list_t dup_untreated_list;
|
|
untreated_list.Duplicate(dup_untreated_list);
|
|
|
|
// Initialize the iterator of each list first
|
|
untreated_list_t::iterator dup_untreated_iter = dup_untreated_list.begin();
|
|
filepart_list_t::iterator uploaded_iter = upload_list.begin();
|
|
|
|
//
|
|
// Loop to extract areas to upload and download
|
|
//
|
|
// Check at the boundary of the maximum upload size from the beginning of the file
|
|
//
|
|
for(off_t cur_start = 0, cur_size = 0; cur_start < file_size; cur_start += cur_size){
|
|
//
|
|
// Set part size
|
|
// (To avoid confusion, the area to be checked is called the "current area".)
|
|
//
|
|
cur_size = ((cur_start + max_mp_size) <= file_size ? max_mp_size : (file_size - cur_start));
|
|
|
|
//
|
|
// Extract the untreated erea that overlaps this current area.
|
|
// (The extracted area is deleted from dup_untreated_list.)
|
|
//
|
|
untreated_list_t cur_untreated_list;
|
|
for(cur_untreated_list.clear(); dup_untreated_iter != dup_untreated_list.end(); ){
|
|
if((dup_untreated_iter->start < (cur_start + cur_size)) && (cur_start < (dup_untreated_iter->start + dup_untreated_iter->size))){
|
|
// this untreated area is overlap
|
|
off_t tmp_untreated_start;
|
|
off_t tmp_untreated_size;
|
|
if(dup_untreated_iter->start < cur_start){
|
|
// [NOTE]
|
|
// This untreated area overlaps with the current area, but starts
|
|
// in front of the target area.
|
|
// This state should not be possible, but if this state is detected,
|
|
// the part before the target area will be deleted.
|
|
//
|
|
tmp_untreated_start = cur_start;
|
|
tmp_untreated_size = dup_untreated_iter->size - (cur_start - dup_untreated_iter->start);
|
|
}else{
|
|
tmp_untreated_start = dup_untreated_iter->start;
|
|
tmp_untreated_size = dup_untreated_iter->size;
|
|
}
|
|
|
|
//
|
|
// Check the end of the overlapping untreated area.
|
|
//
|
|
if((tmp_untreated_start + tmp_untreated_size) <= (cur_start + cur_size)){
|
|
//
|
|
// All of untreated areas are within the current area
|
|
//
|
|
// - Add this untreated area to cur_untreated_list
|
|
// - Delete this from dup_untreated_list
|
|
//
|
|
cur_untreated_list.emplace_back(tmp_untreated_start, tmp_untreated_size);
|
|
dup_untreated_iter = dup_untreated_list.erase(dup_untreated_iter);
|
|
}else{
|
|
//
|
|
// The untreated area exceeds the end of the current area
|
|
//
|
|
|
|
// Ajust untreated area
|
|
tmp_untreated_size = (cur_start + cur_size) - tmp_untreated_start;
|
|
|
|
// Add ajusted untreated area to cur_untreated_list
|
|
cur_untreated_list.emplace_back(tmp_untreated_start, tmp_untreated_size);
|
|
|
|
// Remove this ajusted untreated area from the area pointed
|
|
// to by dup_untreated_iter.
|
|
dup_untreated_iter->size = (dup_untreated_iter->start + dup_untreated_iter->size) - (cur_start + cur_size);
|
|
dup_untreated_iter->start = tmp_untreated_start + tmp_untreated_size;
|
|
}
|
|
|
|
}else if((cur_start + cur_size - 1) < dup_untreated_iter->start){
|
|
// this untreated area is over the current area, thus break loop.
|
|
break;
|
|
}else{
|
|
++dup_untreated_iter;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check uploaded area
|
|
//
|
|
// [NOTE]
|
|
// The uploaded area should be aligned with the maximum upload size boundary.
|
|
// It also assumes that each size of uploaded area must be a maximum upload
|
|
// size.
|
|
//
|
|
filepart_list_t::iterator overlap_uploaded_iter = upload_list.end();
|
|
for(; uploaded_iter != upload_list.end(); ++uploaded_iter){
|
|
if((cur_start < (uploaded_iter->startpos + uploaded_iter->size)) && (uploaded_iter->startpos < (cur_start + cur_size))){
|
|
if(overlap_uploaded_iter != upload_list.end()){
|
|
//
|
|
// Something wrong in this unloaded area.
|
|
//
|
|
// This area is not aligned with the boundary, then this condition
|
|
// is unrecoverable and return failure.
|
|
//
|
|
S3FS_PRN_ERR("The uploaded list may not be the boundary for the maximum multipart upload size. No further processing is possible.");
|
|
return false;
|
|
}
|
|
// Set this iterator to overlap iter
|
|
overlap_uploaded_iter = uploaded_iter;
|
|
|
|
}else if((cur_start + cur_size - 1) < uploaded_iter->startpos){
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create upload/download/cancel/copy list for this current area
|
|
//
|
|
int part_num = static_cast<int>((cur_start / max_mp_size) + 1);
|
|
if(cur_untreated_list.empty()){
|
|
//
|
|
// No untreated area was detected in this current area
|
|
//
|
|
if(overlap_uploaded_iter != upload_list.end()){
|
|
//
|
|
// This current area already uploaded, then nothing to add to lists.
|
|
//
|
|
S3FS_PRN_DBG("Already uploaded: start=%lld, size=%lld", static_cast<long long int>(cur_start), static_cast<long long int>(cur_size));
|
|
|
|
}else{
|
|
//
|
|
// This current area has not been uploaded
|
|
// (neither an uploaded area nor an untreated area.)
|
|
//
|
|
if(use_copy){
|
|
//
|
|
// Copy multipart upload available
|
|
//
|
|
S3FS_PRN_DBG("To copy: start=%lld, size=%lld", static_cast<long long int>(cur_start), static_cast<long long int>(cur_size));
|
|
to_copy_list.emplace_back(cur_start, cur_size, part_num);
|
|
}else{
|
|
//
|
|
// This current area needs to be downloaded and uploaded
|
|
//
|
|
S3FS_PRN_DBG("To download and upload: start=%lld, size=%lld", static_cast<long long int>(cur_start), static_cast<long long int>(cur_size));
|
|
to_download_list.emplace_back(cur_start, cur_size);
|
|
to_upload_list.emplace_back(cur_start, cur_size, part_num);
|
|
}
|
|
}
|
|
}else{
|
|
//
|
|
// Found untreated area in this current area
|
|
//
|
|
if(overlap_uploaded_iter != upload_list.end()){
|
|
//
|
|
// This current area is also the uploaded area
|
|
//
|
|
// [NOTE]
|
|
// The uploaded area is aligned with boundary, there are all data in
|
|
// this current area locally(which includes all data of untreated area).
|
|
// So this current area only needs to be uploaded again.
|
|
//
|
|
S3FS_PRN_DBG("Cancel upload: start=%lld, size=%lld", static_cast<long long int>(overlap_uploaded_iter->startpos), static_cast<long long int>(overlap_uploaded_iter->size));
|
|
|
|
if(!overlap_uploaded_iter->uploaded){
|
|
S3FS_PRN_DBG("This cancel upload area is still uploading, so you must wait for it to complete before starting any Stream uploads.");
|
|
wait_upload_complete = true;
|
|
}
|
|
cancel_upload_list.push_back(*overlap_uploaded_iter); // add this uploaded area to cancel_upload_list
|
|
uploaded_iter = upload_list.erase(overlap_uploaded_iter); // remove it from upload_list
|
|
|
|
S3FS_PRN_DBG("To upload: start=%lld, size=%lld", static_cast<long long int>(cur_start), static_cast<long long int>(cur_size));
|
|
to_upload_list.emplace_back(cur_start, cur_size, part_num); // add new uploading area to list
|
|
|
|
}else{
|
|
//
|
|
// No uploaded area overlap this current area
|
|
// (Areas other than the untreated area must be downloaded.)
|
|
//
|
|
// [NOTE]
|
|
// Need to consider the case where there is a gap between the start
|
|
// of the current area and the untreated area.
|
|
// This gap is the area that should normally be downloaded.
|
|
// But it is the area that can be copied if we can use copy multipart
|
|
// upload. Then If we can use copy multipart upload and the previous
|
|
// area is used copy multipart upload, this gap will be absorbed by
|
|
// the previous area.
|
|
// Unifying the copy multipart upload area can reduce the number of
|
|
// upload requests.
|
|
//
|
|
off_t tmp_cur_start = cur_start;
|
|
off_t tmp_cur_size = cur_size;
|
|
off_t changed_start = cur_start;
|
|
off_t changed_size = cur_size;
|
|
bool first_area = true;
|
|
for(untreated_list_t::const_iterator tmp_cur_untreated_iter = cur_untreated_list.begin(); tmp_cur_untreated_iter != cur_untreated_list.end(); ++tmp_cur_untreated_iter, first_area = false){
|
|
if(tmp_cur_start < tmp_cur_untreated_iter->start){
|
|
//
|
|
// Detected a gap at the start of area
|
|
//
|
|
bool include_prev_copy_part = false;
|
|
if(first_area && use_copy && !to_copy_list.empty()){
|
|
//
|
|
// Make sure that the area of the last item in to_copy_list
|
|
// is contiguous with this current area.
|
|
//
|
|
// [NOTE]
|
|
// Areas can be unified if the total size of the areas is
|
|
// within 5GB and the remaining area after unification is
|
|
// larger than the minimum multipart upload size.
|
|
//
|
|
mp_part_list_t::reverse_iterator copy_riter = to_copy_list.rbegin();
|
|
|
|
if( (copy_riter->start + copy_riter->size) == tmp_cur_start &&
|
|
(copy_riter->size + (tmp_cur_untreated_iter->start - tmp_cur_start)) <= FIVE_GB &&
|
|
((tmp_cur_start + tmp_cur_size) - tmp_cur_untreated_iter->start) >= MIN_MULTIPART_SIZE )
|
|
{
|
|
//
|
|
// Unify to this area to previouse copy area.
|
|
//
|
|
copy_riter->size += tmp_cur_untreated_iter->start - tmp_cur_start;
|
|
S3FS_PRN_DBG("Resize to copy: start=%lld, size=%lld", static_cast<long long int>(copy_riter->start), static_cast<long long int>(copy_riter->size));
|
|
|
|
changed_size -= (tmp_cur_untreated_iter->start - changed_start);
|
|
changed_start = tmp_cur_untreated_iter->start;
|
|
include_prev_copy_part = true;
|
|
}
|
|
}
|
|
if(!include_prev_copy_part){
|
|
//
|
|
// If this area is not unified, need to download this area
|
|
//
|
|
S3FS_PRN_DBG("To download: start=%lld, size=%lld", static_cast<long long int>(tmp_cur_start), static_cast<long long int>(tmp_cur_untreated_iter->start - tmp_cur_start));
|
|
to_download_list.emplace_back(tmp_cur_start, tmp_cur_untreated_iter->start - tmp_cur_start);
|
|
}
|
|
}
|
|
//
|
|
// Set next start position
|
|
//
|
|
tmp_cur_size = (tmp_cur_start + tmp_cur_size) - (tmp_cur_untreated_iter->start + tmp_cur_untreated_iter->size);
|
|
tmp_cur_start = tmp_cur_untreated_iter->start + tmp_cur_untreated_iter->size;
|
|
}
|
|
|
|
//
|
|
// Add download area to list, if remaining size
|
|
//
|
|
if(0 < tmp_cur_size){
|
|
S3FS_PRN_DBG("To download: start=%lld, size=%lld", static_cast<long long int>(tmp_cur_start), static_cast<long long int>(tmp_cur_size));
|
|
to_download_list.emplace_back(tmp_cur_start, tmp_cur_size);
|
|
}
|
|
|
|
//
|
|
// Set upload area(whole of area) to list
|
|
//
|
|
S3FS_PRN_DBG("To upload: start=%lld, size=%lld", static_cast<long long int>(changed_start), static_cast<long long int>(changed_size));
|
|
to_upload_list.emplace_back(changed_start, changed_size, part_num);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Local variables:
|
|
* tab-width: 4
|
|
* c-basic-offset: 4
|
|
* End:
|
|
* vim600: expandtab sw=4 ts=4 fdm=marker
|
|
* vim<600: expandtab sw=4 ts=4
|
|
*/
|