// ---------------------------------------------------------------------- // File: HttpHandler.cc // Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2011 CERN/Switzerland * * * * 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 3 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, see .* ************************************************************************/ #include "fst/http/HttpHandler.hh" #include "fst/http/HttpServer.hh" #include "fst/checksum/Adler.hh" #include "common/Path.hh" #include "common/Timing.hh" #include "common/http/HttpResponse.hh" #include "common/http/OwnCloud.hh" #include "common/http/PlainHttpResponse.hh" #include "common/http/MimeTypes.hh" #include "fst/XrdFstOfs.hh" #include "fst/XrdFstOfsFile.hh" #include "XrdSys/XrdSysPthread.hh" #include "XrdSfs/XrdSfsInterface.hh" #include EOSFSTNAMESPACE_BEGIN XrdSysMutex HttpHandler::mOpenMutexMapMutex; std::map HttpHandler::mOpenMutexMap; eos::common::MimeTypes HttpHandler::gMime; /*----------------------------------------------------------------------------*/ HttpHandler::~HttpHandler() { if (mFile) { delete mFile; mFile = nullptr; } } /*----------------------------------------------------------------------------*/ bool HttpHandler::Matches(const std::string& meth, HeaderMap& headers) { int method = ParseMethodString(meth); // We only support GET, HEAD and PUT on the FST (CREATE is used by XrdHttp) if (method == GET || method == HEAD || method == PUT || method == CREATE) { eos_static_info("%s", "msg=\"Matched HTTP protocol for request\""); return true; } else { return false; } } /*----------------------------------------------------------------------------*/ void HttpHandler::HandleRequest(eos::common::HttpRequest* request) { eos_static_debug("Handling HTTP request"); if (!mFile) { Initialize(request); } if (!mFile) { mFile = (XrdFstOfsFile*) gOFS.newFile(mClient.name); // default modes are for GET=read XrdSfsFileOpenMode open_mode = 0; mode_t create_mode = 0; XrdOucString openUrl = request->GetUrl().c_str(); XrdOucString query = request->GetQuery().c_str(); if (request->GetHeaders().count("x-upload-range")) { // we need to indicate to XrdFstOfsFile that this is a partial upload query += "&x-upload-range="; query += request->GetHeaders()["x-upload-range"].c_str(); } if ((request->GetMethod() == "PUT") || (request->GetMethod() == "CREATE")) { // use the proper creation/open flags for PUT's open_mode |= SFS_O_CREAT; if (EOS_LOGS_DEBUG) { for (auto it = request->GetHeaders().begin(); it != request->GetHeaders().end(); ++it) { eos_static_debug("header %s <=> %s", it->first.c_str(), it->second.c_str()); } } // Avoid truncation of chunked uploads if (!request->GetHeaders().count("oc-chunked") && !request->GetHeaders().count("x-upload-range")) { open_mode |= SFS_O_TRUNC; } else { eos_static_info("%s", "msg=\"removing truncation flag\""); } open_mode |= SFS_O_RDWR; open_mode |= SFS_O_MKPTH; create_mode |= (SFS_O_MKPTH | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); } XrdSysMutex* hMutex = 0; { // use path dependent locks for opens // we will accumulate up to 64k mutexes for this Adler lHash; lHash.Add(openUrl.c_str(), openUrl.length(), 0); lHash.Finalize(); { XrdSysMutexHelper oLock(mOpenMutexMapMutex); if (!mOpenMutexMap.count(lHash.GetAdler())) { hMutex = new XrdSysMutex(); mOpenMutexMap[lHash.GetAdler()] = hMutex; } else { hMutex = mOpenMutexMap[lHash.GetAdler()]; } } } { XrdSysMutexHelper oLock(*hMutex); mRc = mFile->open(openUrl.c_str(), open_mode, create_mode, &mClient, query.c_str()); } mFileSize = mFile->GetOpenSize(); mFileId = mFile->GetFileId(); mLogId = mFile->logId; // check for range requests if (request->GetHeaders().count("range")) { if (!DecodeByteRange(request->GetHeaders()["range"], mOffsetMap, mRangeRequestSize, mFileSize)) { // indicate range decoding error mRangeDecodingError = true; } else { mRangeRequest = true; } } // check for range requests if (request->GetHeaders().count("x-upload-range") && request->GetHeaders().count("x-upload-totalsize")) { if (!DecodeByteRange(request->GetHeaders()["x-upload-range"], mOffsetMap, mRangeRequestSize, std::stoul(request->GetHeaders()["x-upload-totalsize"]))) { // indicate range decoding error mRangeDecodingError = true; } else { mRangeRequest = true; } } if (!mRangeRequest) { // we put the file size as request size if this is not a range request // aka full file download mRangeRequestSize = mFile->GetOpenSize(); } } if (request->GetMethod() == "GET") { // call the HttpHandler::Get method mHttpResponse = Get(request); } if (request->GetMethod() == "CREATE") { // fake method for XrdHttp bridge mHttpResponse = new eos::common::PlainHttpResponse(); mHttpResponse->SetResponseCode(0); return; } if (request->GetMethod() == "PUT") { if (((mUploadLeftSize > (1 * 1024 * 1024)) && ((*request->GetBodySize()) < (1 * 1024 * 1024)))) { // we want more bytes, we don't process this eos_static_debug("msg=\"wait for more bytes\" leftsize=%llu uploadsize=%llu", mUploadLeftSize, *request->GetBodySize()); mHttpResponse = new eos::common::PlainHttpResponse(); mHttpResponse->SetResponseCode(0); return; } mHttpResponse = Put(request); if (!mHttpResponse || (*request->GetBodySize()) == 0) { // clean-up left-over objects on error or end-of-put if (mFile) { delete mFile; mFile = 0; } } } } /*----------------------------------------------------------------------------*/ void HttpHandler::Initialize(eos::common::HttpRequest* request) { if (request->GetCookies().count("EOSCAPABILITY")) { // if we have a capability we don't use the query CGI but that one request->SetQuery(request->GetCookies()["EOSCAPABILITY"]); } if (request->GetHeaders().count("content-length")) { mContentLength = strtoull(request->GetHeaders()["content-length"].c_str(), 0, 10); mUploadLeftSize = mContentLength; } std::string query = request->GetQuery(); HttpServer::DecodeURI(query); // unescape '+' '/' '=' request->SetQuery(query); eos_static_debug("path=%s query=%s", request->GetUrl().c_str(), request->GetQuery().c_str()); // define the client sec entity object strncpy(mClient.prot, "unix", XrdSecPROTOIDSIZE - 1); mClient.prot[XrdSecPROTOIDSIZE - 1] = '\0'; mClient.name = strdup("nobody"); mClient.host = strdup("localhost"); mClient.tident = strdup("http"); } /*----------------------------------------------------------------------------*/ eos::common::HttpResponse* HttpHandler::Get(eos::common::HttpRequest* request) { eos::common::HttpResponse* response = nullptr; if (mRangeDecodingError) { mErrCode = eos::common::HttpResponse::REQUESTED_RANGE_NOT_SATISFIABLE; mErrText = "Illegal Range request"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); } else { if (mErrCode) { eos_static_err("msg=\"return stored error\" errc=%d errmsg=\"%s\"", mErrCode, mErrText.c_str()); response = HttpServer::HttpError(mErrText.c_str(), mErrCode); return response; } if (mRc != SFS_OK) { if (mRc == SFS_REDIRECT) { mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR; mErrText = mFile->error.getErrText(); response = HttpServer::HttpError(mErrText.c_str(), mErrCode); } else if (mRc == SFS_ERROR) { mErrCode = mFile->error.getErrInfo(); mErrText = mFile->error.getErrText(); response = HttpServer::HttpError(mErrText.c_str(), mErrCode); } else if (mRc == SFS_DATA) { response = HttpServer::HttpData(mFile->error.getErrText(), mFile->error.getErrInfo()); } else if (mRc == SFS_STALL) { response = HttpServer::HttpStall(mFile->error.getErrText(), mFile->error.getErrInfo()); } else { response = HttpServer::HttpError("unexpected result from file open", EOPNOTSUPP); } delete mFile; mFile = 0; } else { response = new eos::common::PlainHttpResponse(); if (mRangeRequest) { CreateMultipartHeader("application/octet-stream"); eos_static_debug(Print()); char clength[16]; snprintf(clength, sizeof(clength) - 1, "%llu", (unsigned long long) mRequestSize); if (mOffsetMap.size() == 1) { // if there is only one range we don't send a multipart response response->AddHeader("Content-Type", gMime.Match(request->GetUrl())); response->AddHeader("Content-Range", mSinglepartHeader); } else { // for several ranges we send a multipart response response->AddHeader("Content-Type", mMultipartHeader); } response->AddHeader("Content-Length", clength); response->mResponseLength = mRequestSize; response->SetResponseCode(eos::common::HttpResponse::PARTIAL_CONTENT); } else { // successful http open char clength[16]; snprintf(clength, sizeof(clength) - 1, "%llu", (unsigned long long) mFile->GetOpenSize()); mRequestSize = mFile->GetOpenSize(); response->mResponseLength = mRequestSize; response->AddHeader("Content-Type", gMime.Match(request->GetUrl())); response->AddHeader("Content-Length", clength); // retrieve a checksum when file is still open if (mFile->GetChecksum()) { std::string checksum_name = mFile->GetChecksum()->GetName(); std::string checksum_val = mFile->GetFmdChecksum(); while (checksum_val[0] == '0') { checksum_val.erase(0, 1); } std::string checksum_string = eos::common::OwnCloud::GetChecksumString( checksum_name, checksum_val); response->AddHeader("OC-Checksum", checksum_string); const auto& hdrs = request->GetHeaders(); auto it = hdrs.find("want-digest"); if (it != hdrs.end()) { // According to RFC 3230 the Digest reponse needs to have the // following format: // instance-digest = digest-algorithm "=" std::replace(checksum_string.begin(), checksum_string.end(), ':', '='); response->AddHeader("Digest", checksum_string); } } } std::string query = request->GetQuery(); if (query.find("mgm.etag") != std::string::npos) { XrdOucEnv queryenv(query.c_str()); const char* etag = 0; if ((etag = queryenv.Get("mgm.etag"))) { response->AddHeader("ETag", etag); } if (mRangeRequest) { response->SetResponseCode(eos::common::HttpResponse::PARTIAL_CONTENT); } else { response->SetResponseCode(eos::common::HttpResponse::OK); } } } } if (mFile) { time_t mtime = mFile->GetMtime(); response->AddHeader("Last-Modified", eos::common::Timing::utctime(mtime)); // We want to use the file callbacks response->mUseFileReaderCallback = true; } return response; } /*----------------------------------------------------------------------------*/ eos::common::HttpResponse* HttpHandler::Head(eos::common::HttpRequest* request) { eos::common::HttpResponse* response = Get(request); response->mUseFileReaderCallback = false; if (mFile) { mFile->close(); delete mFile; mFile = 0; } return response; } /*----------------------------------------------------------------------------*/ eos::common::HttpResponse* HttpHandler::Put(eos::common::HttpRequest* request) { eos_static_info("method=PUT offset=%llu size=%llu size_ptr=%llu range-map-size:%u", mCurrentCallbackOffset, request->GetBodySize() ? *request->GetBodySize() : 0, request->GetBodySize(), mOffsetMap.size()); eos::common::HttpResponse* response = nullptr; bool checksumError = false; bool checksumMatch = false; if (mRangeDecodingError) { mErrCode = eos::common::HttpResponse::REQUESTED_RANGE_NOT_SATISFIABLE; mErrText = "Illegal Range request"; } else { if (mRangeRequest) { auto it = mOffsetMap.begin(); if ((it->second) != (off_t)std::stoul( request->GetHeaders()["content-length"])) { mErrCode = eos::common::HttpResponse::REQUESTED_RANGE_NOT_SATISFIABLE; mErrText = "Illegal Range request - not matching content length"; eos_static_err("range: [%lu:%lu] content-length: %lu", it->first, it->second, std::stoul(request->GetHeaders()["content-length"])); } } } if (mErrCode) { eos_static_err("msg=\"return stored error\" errc=%d errmsg=\"%s\"", mErrCode, mErrText.c_str()); response = HttpServer::HttpError(mErrText.c_str(), mErrCode); delete mFile; mFile = 0; return response; } if (mRc) { if (mRc != SFS_OK) { if (mRc == SFS_REDIRECT) { // we cannot redirect the PUT at this point, just send an error back mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR; mErrText = mFile->error.getErrText(); response = HttpServer::HttpError(mErrText.c_str(), mErrCode); } else if (mRc == SFS_ERROR) { mErrCode = mFile->error.getErrInfo(); mErrText = mFile->error.getErrText(); response = HttpServer::HttpError(mErrText.c_str(), mErrCode); } else if (mRc == SFS_DATA) { response = HttpServer::HttpData(mFile->error.getErrText(), mFile->error.getErrInfo()); } else if (mRc == SFS_STALL) { response = HttpServer::HttpStall(mFile->error.getErrText(), mFile->error.getErrInfo()); } else { response = HttpServer::HttpError("unexpected result from file open", EOPNOTSUPP); } delete mFile; mFile = 0; return response; } } else { // check for chunked uploads if (!mCurrentCallbackOffset && request->GetHeaders().count("oc-chunked")) { int chunk_n = 0; int chunk_max = 0; XrdOucString chunk_uuid; if ((!request->GetHeaders().count("cbox-chunked-android-issue-900")) && (!eos::common::OwnCloud::getContentSize(request))) { // ------------------------------------------------------- // WARNING: // there is buggy ANDROID client not providing this header // but we let it pass if a special cbox header allows a // bypass // ------------------------------------------------------- mErrCode = eos::common::HttpResponse::BAD_REQUEST; mErrText = "Missing total length in OC request"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); delete mFile; mFile = 0; return response; } eos::common::OwnCloud::GetChunkInfo(request->GetQuery().c_str(), chunk_n, chunk_max, chunk_uuid); if (chunk_n >= chunk_max) { // there is something inconsistent here // HTTP write error mErrCode = eos::common::HttpResponse::BAD_REQUEST; mErrText = "Illegal chunks specified in OC request"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); delete mFile; mFile = 0; return response; } unsigned long long contentlength = eos::common::StringConversion::GetSizeFromString( request->GetHeaders()["content-length"]); // recompute offset where to write if ((chunk_n + 1) < chunk_max) { // the first n-1 chunks have a straight forward offset mCurrentCallbackOffset = contentlength * chunk_n; eos_static_debug("setting to false %lld", mCurrentCallbackOffset); mLastChunk = false; } else { // ------------------------------------------------------- // WARNING: // there is buggy ANDROID client not providing this header // in this case we have to assume 1MB chunks // ------------------------------------------------------- // the last chunks has to be written at offset=total-length - chunk-length if (eos::common::StringConversion::GetSizeFromString( eos::common::OwnCloud::getContentSize(request))) { mCurrentCallbackOffset = eos::common::StringConversion::GetSizeFromString( eos::common::OwnCloud::getContentSize(request)); mCurrentCallbackOffset -= contentlength; } else { mCurrentCallbackOffset = (chunk_n * (1 * 1024 * 1000)); // ANDROID client } eos_static_debug("setting to true %lld", mCurrentCallbackOffset); mLastChunk = true; } } // check for content range PUT if (mOffsetMap.size() == 1) { auto it = mOffsetMap.begin(); // there is a range header if (mUploadLeftSize == mContentLength) { // place the offset to the initial range mCurrentCallbackOffset = it->first; } if (!mUploadLeftSize && ((off_t) std::stoul(request->GetHeaders()["x-upload-totalsize"]) == mCurrentCallbackOffset)) { mLastChunk = true; } if (!mUploadLeftSize) { if (request->GetHeaders()["x-upload-done"] == "true") { mLastChunk = true; } if (request->GetHeaders()["x-upload-done"] == "false") { mLastChunk = false; } } eos_static_debug("c-offset=%lu body-size=%lu ranget-offset=%lu range-size=%lu last-chunk=%d", mCurrentCallbackOffset, *request->GetBodySize(), it->first, it->second, mLastChunk); } // File streaming in size_t* bodySize = request->GetBodySize(); if (request->GetBody().c_str() && bodySize && (*bodySize)) { size_t stored = mFile->write(mCurrentCallbackOffset, request->GetBody().c_str(), *request->GetBodySize()); if (stored != *request->GetBodySize()) { eos_static_err("stored %lu of %lu bytes", stored, *request->GetBodySize()); // HTTP write error mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR; mErrText = "Write error occured"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); delete mFile; mFile = 0; return response; } else { eos_static_info("msg=\"stored requested bytes\" offset=%lld", mCurrentCallbackOffset); // decrease the upload left data size mUploadLeftSize -= *request->GetBodySize(); mCurrentCallbackOffset += *request->GetBodySize(); response = new eos::common::PlainHttpResponse(); std::string query = request->GetQuery(); XrdOucEnv queryenv(query.c_str()); const char* etag = 0; if ((etag = queryenv.Get("mgm.etag"))) { response->AddHeader("ETag", etag); } response->SetResponseCode(eos::common::HttpResponse::CREATED); return response; } } else { eos_static_info("entering close handler"); eos::common::HttpRequest::HeaderMap header = request->GetHeaders(); if (header.count("x-upload-mtime")) { header["x-oc-mtime"] = header["x-upload-mtime"]; } if (mOffsetMap.size()) { header["oc-chunked"] = "true"; } if (header.count("x-oc-mtime")) { // there is an X-OC-Mtime header to force the mtime for that file mFile->SetForcedMtime(strtoull(header["x-oc-mtime"].c_str(), 0, 10), 0); } if ((!mLastChunk) && (header.count("oc-chunked"))) { // WARNING: this assumes that the last chunk is the last uploaded std::string cmd = "nochecksum"; if (mFile->fctl(SFS_FCTL_SPEC1, cmd.length(), cmd.c_str(), 0)) { mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR; mErrText = "Failed to disable checksum"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); delete mFile; mFile = 0; return response; } } else { if (mFile->GetChecksum()) { // retrieve a checksum when file is still open eos_static_debug("enabled checksum lastchunk=%d checksum=%x", mLastChunk, mFile->GetChecksum()); // Call explicitly the checksum verification mFile->VerifyChecksum(); if (mFile->GetChecksum()) { std::string checksum_name = mFile->GetChecksum()->GetName(); std::string checksum_val = mFile->GetChecksum()->GetHexChecksum(); while (checksum_val[0] == '0') { checksum_val.erase(0, 1); } eos::common::OwnCloud::checksum_t checksum = std::make_pair( checksum_name, checksum_val); // inspect if there is checksum provided eos::common::OwnCloud::checksum_t client_checksum = eos::common::OwnCloud::GetChecksum(request, header.count("x-upload-checksum") ? "x-upload-checksum" : "oc-checksum"); eos_static_debug("client-checksum-type=%s client-checksum-value=%s " "server-checksum-type=%s server-checksum-value=%s", client_checksum.first.c_str(), client_checksum.second.c_str(), checksum.first.c_str(), checksum.second.c_str()); if (client_checksum.first != "") { if (client_checksum.first == checksum.first) { // compare only if the algorithm is the same if (client_checksum.second != checksum.second) { eos_static_err("msg=\"invalid checksum\" client-checksum-type=%s" " client-checksum-value=%s server-checksum-type=%s" " server-checksum-value=%s", client_checksum.first.c_str(), client_checksum.second.c_str(), checksum.first.c_str(), checksum.second.c_str()); checksumError = true; } checksumMatch = true; } else { eos_static_warning("msg=\"client required different checksum\" " "client-checksum-type=%s client-checksum-value=%s " "server-checksum-type=%s server-checksum-value=%s", client_checksum.first.c_str(), client_checksum.second.c_str(), checksum.first.c_str(), checksum.second.c_str()); } } } } } if (checksumError) { response = new eos::common::PlainHttpResponse(); response->SetResponseCode(eos::common::HttpResponse::PRECONDITION_FAILED); delete mFile; mFile = 0; return response; } mCloseCode = mFile->close(); if (mCloseCode) { mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR; mErrText = "File close failed"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); delete mFile; mFile = 0; return response; } else { response = new eos::common::PlainHttpResponse(); // Add the etag only if we are not an intermediary chunk upload, // otherwise the cernbox client interprets it as the end of the // transfers and gets an error. if (header.count("x-oc-mtime") == 0) { response->AddHeader("ETag", mFile->GetETag()); } if (header.count("x-oc-mtime") && (mLastChunk || (!header.count("oc-chunked")))) { // only normal uploads or the last chunk receive these extra response headers response->AddHeader("ETag", mFile->GetETag()); if (!mOffsetMap.size()) { response->AddHeader("X-OC-Mtime", "accepted"); // return the OC-FileId header std::string ocid; eos::common::StringConversion::GetSizeString(ocid, eos::common::FileId::FidToInode(mFileId)); response->AddHeader("OC-FileId", ocid); if (checksumMatch && request->GetHeaders().count("oc-checksum")) { response->AddHeader("OC-Checksum", request->GetHeaders()["oc-checksum"]); } } else { // PUT with range time_t mtime = mFile->GetMtime(); response->AddHeader("Last-Modified", eos::common::Timing::utctime(mtime)); std::string inode; eos::common::StringConversion::GetSizeString(inode, eos::common::FileId::FidToInode(mFileId)); response->AddHeader("x-eos-inode", inode); if (checksumMatch && request->GetHeaders().count("x-upload-checksum")) { response->AddHeader("x-eos-checksum", request->GetHeaders()["x-upload-checksum"]); } } } response->SetResponseCode(eos::common::HttpResponse::CREATED); return response; } } } // Should never get here mErrCode = eos::common::HttpResponse::INTERNAL_SERVER_ERROR; mErrText = "Internal Server Error"; response = HttpServer::HttpError(mErrText.c_str(), mErrCode); return response; } /*----------------------------------------------------------------------------*/ bool HttpHandler::DecodeByteRange(std::string rangeheader, std::map& offsetmap, off_t& requestsize, off_t filesize) { std::vector tokens; if (rangeheader.substr(0, 6) != "bytes=") { // this is an illegal header return false; } else { rangeheader.erase(0, 6); } eos::common::StringConversion::Tokenize(rangeheader, tokens, ","); // decode the string parts for (size_t i = 0; i < tokens.size(); i++) { eos_static_debug("decoding %s", tokens[i].c_str()); off_t start = 0; off_t stop = 0; off_t length = 0; size_t mpos = tokens[i].find("-"); if (mpos == std::string::npos) { // there must always be a '-' return false; } std::string sstop = tokens[i]; std::string sstart = tokens[i]; sstart.erase(mpos); sstop.erase(0, mpos + 1); if (sstart.length()) { start = strtoull(sstart.c_str(), 0, 10); } else { start = 0; } if (sstop.length()) { stop = strtoull(sstop.c_str(), 0, 10); } else { if (filesize > 0) { stop = filesize - 1; } else { stop = 0; } } if (!sstart.length()) { // case '-X' = the last X bytes start = filesize - stop; stop = filesize - 1; } if ((start > filesize) || (stop > filesize)) { return false; } if (stop >= start) { length = (stop - start) + 1; } else { continue; } if (offsetmap.count(start)) { if (offsetmap[start] < length) { // a previous block has been replaced with a longer one offsetmap[start] = length; } } else { offsetmap[start] = length; } } // now merge overlapping requests bool merged = true; while (merged) { requestsize = 0; if (offsetmap.begin() == offsetmap.end()) { // if there is nothing in the map just return with error eos_static_err("msg=\"range map is empty\""); return false; } for (auto it = offsetmap.begin(); it != offsetmap.end(); it++) { eos_static_debug("offsetmap %llu:%llu", it->first, it->second); auto next = it; next++; if (next != offsetmap.end()) { // check if we have two overlapping requests if ((it->first + it->second) >= (next->first)) { merged = true; // merge this two it->second = next->first + next->second - it->first; offsetmap.erase(next); break; } else { merged = false; } } else { merged = false; } // compute the total size requestsize += it->second; } } return true; } /*----------------------------------------------------------------------------*/ EOSFSTNAMESPACE_END