LCOV - code coverage report
Current view: top level - port - cpl_vsil_chunked_write_handle.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 151 250 60.4 %
Date: 2024-11-21 22:18:42 Functions: 9 12 75.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Purpose:  Implement a write-only file handle using PUT chunked writing
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_vsil_curl_class.h"
      14             : 
      15             : #ifdef HAVE_CURL
      16             : 
      17             : //! @cond Doxygen_Suppress
      18             : 
      19             : #define unchecked_curl_easy_setopt(handle, opt, param)                         \
      20             :     CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
      21             : 
      22             : namespace cpl
      23             : {
      24             : 
      25             : /************************************************************************/
      26             : /*                        VSIChunkedWriteHandle()                       */
      27             : /************************************************************************/
      28             : 
      29           3 : VSIChunkedWriteHandle::VSIChunkedWriteHandle(
      30             :     IVSIS3LikeFSHandler *poFS, const char *pszFilename,
      31           3 :     IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions)
      32             :     : m_poFS(poFS), m_osFilename(pszFilename),
      33             :       m_poS3HandleHelper(poS3HandleHelper), m_aosOptions(papszOptions),
      34             :       m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)),
      35           3 :       m_oRetryParameters(m_aosHTTPOptions)
      36             : {
      37           3 : }
      38             : 
      39             : /************************************************************************/
      40             : /*                      ~VSIChunkedWriteHandle()                        */
      41             : /************************************************************************/
      42             : 
      43           6 : VSIChunkedWriteHandle::~VSIChunkedWriteHandle()
      44             : {
      45           3 :     VSIChunkedWriteHandle::Close();
      46           3 :     delete m_poS3HandleHelper;
      47             : 
      48           3 :     if (m_hCurlMulti)
      49             :     {
      50           1 :         if (m_hCurl)
      51             :         {
      52           1 :             curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
      53           1 :             curl_easy_cleanup(m_hCurl);
      54             :         }
      55           1 :         VSICURLMultiCleanup(m_hCurlMulti);
      56             :     }
      57           3 :     CPLFree(m_sWriteFuncHeaderData.pBuffer);
      58           6 : }
      59             : 
      60             : /************************************************************************/
      61             : /*                                 Close()                              */
      62             : /************************************************************************/
      63             : 
      64           6 : int VSIChunkedWriteHandle::Close()
      65             : {
      66           6 :     int nRet = 0;
      67           6 :     if (!m_bClosed)
      68             :     {
      69           3 :         m_bClosed = true;
      70           3 :         if (m_hCurlMulti != nullptr)
      71             :         {
      72           1 :             nRet = FinishChunkedTransfer();
      73             :         }
      74             :         else
      75             :         {
      76           2 :             if (!m_bError && !DoEmptyPUT())
      77           0 :                 nRet = -1;
      78             :         }
      79             :     }
      80           6 :     return nRet;
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                    InvalidateParentDirectory()                       */
      85             : /************************************************************************/
      86             : 
      87           3 : void VSIChunkedWriteHandle::InvalidateParentDirectory()
      88             : {
      89           3 :     m_poFS->InvalidateCachedData(m_poS3HandleHelper->GetURL().c_str());
      90             : 
      91           6 :     std::string osFilenameWithoutSlash(m_osFilename);
      92           3 :     if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/')
      93           1 :         osFilenameWithoutSlash.pop_back();
      94           3 :     m_poFS->InvalidateDirContent(CPLGetDirname(osFilenameWithoutSlash.c_str()));
      95           3 : }
      96             : 
      97             : /************************************************************************/
      98             : /*                               Seek()                                 */
      99             : /************************************************************************/
     100             : 
     101           0 : int VSIChunkedWriteHandle::Seek(vsi_l_offset nOffset, int nWhence)
     102             : {
     103           0 :     if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) ||
     104           0 :           (nWhence == SEEK_CUR && nOffset == 0) ||
     105           0 :           (nWhence == SEEK_END && nOffset == 0)))
     106             :     {
     107           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     108             :                  "Seek not supported on writable %s files",
     109           0 :                  m_poFS->GetFSPrefix().c_str());
     110           0 :         m_bError = true;
     111           0 :         return -1;
     112             :     }
     113           0 :     return 0;
     114             : }
     115             : 
     116             : /************************************************************************/
     117             : /*                               Tell()                                 */
     118             : /************************************************************************/
     119             : 
     120           0 : vsi_l_offset VSIChunkedWriteHandle::Tell()
     121             : {
     122           0 :     return m_nCurOffset;
     123             : }
     124             : 
     125             : /************************************************************************/
     126             : /*                               Read()                                 */
     127             : /************************************************************************/
     128             : 
     129           0 : size_t VSIChunkedWriteHandle::Read(void * /* pBuffer */, size_t /* nSize */,
     130             :                                    size_t /* nMemb */)
     131             : {
     132           0 :     CPLError(CE_Failure, CPLE_NotSupported,
     133             :              "Read not supported on writable %s files",
     134           0 :              m_poFS->GetFSPrefix().c_str());
     135           0 :     m_bError = true;
     136           0 :     return 0;
     137             : }
     138             : 
     139             : /************************************************************************/
     140             : /*                      ReadCallBackBufferChunked()                     */
     141             : /************************************************************************/
     142             : 
     143           3 : size_t VSIChunkedWriteHandle::ReadCallBackBufferChunked(char *buffer,
     144             :                                                         size_t size,
     145             :                                                         size_t nitems,
     146             :                                                         void *instream)
     147             : {
     148           3 :     VSIChunkedWriteHandle *poThis =
     149             :         static_cast<VSIChunkedWriteHandle *>(instream);
     150           3 :     if (poThis->m_nChunkedBufferSize == 0)
     151             :     {
     152             :         // CPLDebug("VSIChunkedWriteHandle", "Writing 0 byte (finish)");
     153           1 :         return 0;
     154             :     }
     155           2 :     const size_t nSizeMax = size * nitems;
     156           2 :     size_t nSizeToWrite = nSizeMax;
     157           2 :     size_t nChunkedBufferRemainingSize =
     158           2 :         poThis->m_nChunkedBufferSize - poThis->m_nChunkedBufferOff;
     159           2 :     if (nChunkedBufferRemainingSize < nSizeToWrite)
     160           2 :         nSizeToWrite = nChunkedBufferRemainingSize;
     161           2 :     memcpy(buffer,
     162           2 :            static_cast<const GByte *>(poThis->m_pBuffer) +
     163           2 :                poThis->m_nChunkedBufferOff,
     164             :            nSizeToWrite);
     165           2 :     poThis->m_nChunkedBufferOff += nSizeToWrite;
     166             :     // CPLDebug("VSIChunkedWriteHandle", "Writing %d bytes", nSizeToWrite);
     167           2 :     return nSizeToWrite;
     168             : }
     169             : 
     170             : /************************************************************************/
     171             : /*                               Write()                                */
     172             : /************************************************************************/
     173             : 
     174           2 : size_t VSIChunkedWriteHandle::Write(const void *pBuffer, size_t nSize,
     175             :                                     size_t nMemb)
     176             : {
     177           2 :     if (m_bError)
     178           0 :         return 0;
     179             : 
     180           2 :     const size_t nBytesToWrite = nSize * nMemb;
     181           2 :     if (nBytesToWrite == 0)
     182           0 :         return 0;
     183             : 
     184           2 :     if (m_hCurlMulti == nullptr)
     185             :     {
     186           1 :         m_hCurlMulti = curl_multi_init();
     187             :     }
     188             : 
     189           2 :     WriteFuncStruct sWriteFuncData;
     190           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     191             :     // We can only easily retry at the first chunk of a transfer
     192           2 :     bool bCanRetry = (m_hCurl == nullptr);
     193             :     bool bRetry;
     194           2 :     do
     195             :     {
     196           2 :         bRetry = false;
     197           2 :         struct curl_slist *headers = nullptr;
     198           2 :         if (m_hCurl == nullptr)
     199             :         {
     200           1 :             CURL *hCurlHandle = curl_easy_init();
     201           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     202           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     203             :                                        ReadCallBackBufferChunked);
     204           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, this);
     205             : 
     206           1 :             VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr,
     207             :                                        nullptr);
     208           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA,
     209             :                                        &sWriteFuncData);
     210           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
     211             :                                        VSICurlHandleWriteFunc);
     212             : 
     213           1 :             VSICURLInitWriteFuncStruct(&m_sWriteFuncHeaderData, nullptr,
     214             :                                        nullptr, nullptr);
     215           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA,
     216             :                                        &m_sWriteFuncHeaderData);
     217           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION,
     218             :                                        VSICurlHandleWriteFunc);
     219             : 
     220           1 :             headers = static_cast<struct curl_slist *>(CPLHTTPSetOptions(
     221           1 :                 hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     222           1 :                 m_aosHTTPOptions.List()));
     223           2 :             headers = VSICurlSetCreationHeadersFromOptions(
     224           1 :                 headers, m_aosOptions.List(), m_osFilename.c_str());
     225           1 :             headers = VSICurlMergeHeaders(
     226           1 :                 headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers));
     227           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER,
     228             :                                        headers);
     229             : 
     230           1 :             m_osCurlErrBuf.resize(CURL_ERROR_SIZE + 1);
     231           1 :             unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER,
     232             :                                        &m_osCurlErrBuf[0]);
     233             : 
     234           1 :             curl_multi_add_handle(m_hCurlMulti, hCurlHandle);
     235           1 :             m_hCurl = hCurlHandle;
     236             :         }
     237             : 
     238           2 :         m_pBuffer = pBuffer;
     239           2 :         m_nChunkedBufferOff = 0;
     240           2 :         m_nChunkedBufferSize = nBytesToWrite;
     241             : 
     242           2 :         int repeats = 0;
     243             :         // cppcheck-suppress knownConditionTrueFalse
     244           4 :         while (m_nChunkedBufferOff < m_nChunkedBufferSize && !bRetry)
     245             :         {
     246             :             int still_running;
     247             : 
     248           4 :             memset(&m_osCurlErrBuf[0], 0, m_osCurlErrBuf.size());
     249             : 
     250           4 :             while (curl_multi_perform(m_hCurlMulti, &still_running) ==
     251           4 :                        CURLM_CALL_MULTI_PERFORM &&
     252             :                    // cppcheck-suppress knownConditionTrueFalse
     253           0 :                    m_nChunkedBufferOff < m_nChunkedBufferSize)
     254             :             {
     255             :                 // loop
     256             :             }
     257             :             // cppcheck-suppress knownConditionTrueFalse
     258           4 :             if (!still_running || m_nChunkedBufferOff == m_nChunkedBufferSize)
     259             :                 break;
     260             : 
     261             :             CURLMsg *msg;
     262           0 :             do
     263             :             {
     264           2 :                 int msgq = 0;
     265           2 :                 msg = curl_multi_info_read(m_hCurlMulti, &msgq);
     266           2 :                 if (msg && (msg->msg == CURLMSG_DONE))
     267             :                 {
     268           0 :                     CURL *e = msg->easy_handle;
     269           0 :                     if (e == m_hCurl)
     270             :                     {
     271             :                         long response_code;
     272           0 :                         curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE,
     273             :                                           &response_code);
     274           0 :                         if (response_code != 200 && response_code != 201)
     275             :                         {
     276             :                             // Look if we should attempt a retry
     277           0 :                             if (bCanRetry &&
     278           0 :                                 oRetryContext.CanRetry(
     279             :                                     static_cast<int>(response_code),
     280           0 :                                     m_sWriteFuncHeaderData.pBuffer,
     281             :                                     m_osCurlErrBuf.c_str()))
     282             :                             {
     283           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
     284             :                                          "HTTP error code: %d - %s. "
     285             :                                          "Retrying again in %.1f secs",
     286             :                                          static_cast<int>(response_code),
     287           0 :                                          m_poS3HandleHelper->GetURL().c_str(),
     288             :                                          oRetryContext.GetCurrentDelay());
     289           0 :                                 CPLSleep(oRetryContext.GetCurrentDelay());
     290           0 :                                 bRetry = true;
     291             :                             }
     292           0 :                             else if (sWriteFuncData.pBuffer != nullptr &&
     293           0 :                                      m_poS3HandleHelper->CanRestartOnError(
     294           0 :                                          sWriteFuncData.pBuffer,
     295           0 :                                          m_sWriteFuncHeaderData.pBuffer, false))
     296             :                             {
     297           0 :                                 bRetry = true;
     298             :                             }
     299             :                             else
     300             :                             {
     301           0 :                                 CPLError(CE_Failure, CPLE_AppDefined,
     302             :                                          "Error %d: %s",
     303             :                                          static_cast<int>(response_code),
     304             :                                          m_osCurlErrBuf.c_str());
     305             : 
     306           0 :                                 curl_slist_free_all(headers);
     307           0 :                                 bRetry = false;
     308             :                             }
     309             : 
     310           0 :                             curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     311           0 :                             curl_easy_cleanup(m_hCurl);
     312             : 
     313           0 :                             CPLFree(sWriteFuncData.pBuffer);
     314           0 :                             CPLFree(m_sWriteFuncHeaderData.pBuffer);
     315             : 
     316           0 :                             m_hCurl = nullptr;
     317           0 :                             sWriteFuncData.pBuffer = nullptr;
     318           0 :                             m_sWriteFuncHeaderData.pBuffer = nullptr;
     319           0 :                             if (!bRetry)
     320           0 :                                 return 0;
     321             :                         }
     322             :                     }
     323             :                 }
     324           2 :             } while (msg);
     325             : 
     326           2 :             CPLMultiPerformWait(m_hCurlMulti, repeats);
     327             :         }
     328             : 
     329           2 :         m_nWrittenInPUT += nBytesToWrite;
     330             : 
     331           2 :         curl_slist_free_all(headers);
     332             : 
     333           2 :         m_pBuffer = nullptr;
     334             : 
     335           2 :         if (!bRetry)
     336             :         {
     337             :             long response_code;
     338           2 :             curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     339           2 :             if (response_code != 100)
     340             :             {
     341             :                 // Look if we should attempt a retry
     342           0 :                 if (bCanRetry &&
     343           0 :                     oRetryContext.CanRetry(static_cast<int>(response_code),
     344           0 :                                            m_sWriteFuncHeaderData.pBuffer,
     345             :                                            m_osCurlErrBuf.c_str()))
     346             :                 {
     347           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     348             :                              "HTTP error code: %d - %s. "
     349             :                              "Retrying again in %.1f secs",
     350             :                              static_cast<int>(response_code),
     351           0 :                              m_poS3HandleHelper->GetURL().c_str(),
     352             :                              oRetryContext.GetCurrentDelay());
     353           0 :                     CPLSleep(oRetryContext.GetCurrentDelay());
     354           0 :                     bRetry = true;
     355             :                 }
     356           0 :                 else if (sWriteFuncData.pBuffer != nullptr &&
     357           0 :                          m_poS3HandleHelper->CanRestartOnError(
     358           0 :                              sWriteFuncData.pBuffer,
     359           0 :                              m_sWriteFuncHeaderData.pBuffer, false))
     360             :                 {
     361           0 :                     bRetry = true;
     362             :                 }
     363             :                 else
     364             :                 {
     365           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     366             :                              static_cast<int>(response_code),
     367             :                              m_osCurlErrBuf.c_str());
     368           0 :                     bRetry = false;
     369           0 :                     nMemb = 0;
     370             :                 }
     371             : 
     372           0 :                 curl_multi_remove_handle(m_hCurlMulti, m_hCurl);
     373           0 :                 curl_easy_cleanup(m_hCurl);
     374             : 
     375           0 :                 CPLFree(sWriteFuncData.pBuffer);
     376           0 :                 CPLFree(m_sWriteFuncHeaderData.pBuffer);
     377             : 
     378           0 :                 m_hCurl = nullptr;
     379           0 :                 sWriteFuncData.pBuffer = nullptr;
     380           0 :                 m_sWriteFuncHeaderData.pBuffer = nullptr;
     381             :             }
     382             :         }
     383             :     } while (bRetry);
     384             : 
     385           2 :     m_nCurOffset += nBytesToWrite;
     386             : 
     387           2 :     return nMemb;
     388             : }
     389             : 
     390             : /************************************************************************/
     391             : /*                        FinishChunkedTransfer()                       */
     392             : /************************************************************************/
     393             : 
     394           1 : int VSIChunkedWriteHandle::FinishChunkedTransfer()
     395             : {
     396           1 :     if (m_hCurl == nullptr)
     397           0 :         return -1;
     398             : 
     399           2 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     400           2 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     401           2 :     NetworkStatisticsAction oContextAction("Write");
     402             : 
     403           1 :     NetworkStatisticsLogger::LogPUT(m_nWrittenInPUT);
     404           1 :     m_nWrittenInPUT = 0;
     405             : 
     406           1 :     m_pBuffer = nullptr;
     407           1 :     m_nChunkedBufferOff = 0;
     408           1 :     m_nChunkedBufferSize = 0;
     409             : 
     410           1 :     VSICURLMultiPerform(m_hCurlMulti);
     411             : 
     412             :     long response_code;
     413           1 :     curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code);
     414           1 :     if (response_code == 200 || response_code == 201)
     415             :     {
     416           1 :         InvalidateParentDirectory();
     417             :     }
     418             :     else
     419             :     {
     420           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s",
     421             :                  static_cast<int>(response_code), m_osCurlErrBuf.c_str());
     422           0 :         return -1;
     423             :     }
     424           1 :     return 0;
     425             : }
     426             : 
     427             : /************************************************************************/
     428             : /*                            DoEmptyPUT()                              */
     429             : /************************************************************************/
     430             : 
     431           2 : bool VSIChunkedWriteHandle::DoEmptyPUT()
     432             : {
     433           2 :     bool bSuccess = true;
     434             :     bool bRetry;
     435           4 :     CPLHTTPRetryContext oRetryContext(m_oRetryParameters);
     436             : 
     437           4 :     NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
     438           4 :     NetworkStatisticsFile oContextFile(m_osFilename.c_str());
     439           2 :     NetworkStatisticsAction oContextAction("Write");
     440             : 
     441           2 :     do
     442             :     {
     443           2 :         bRetry = false;
     444             : 
     445           2 :         PutData putData;
     446           2 :         putData.pabyData = nullptr;
     447           2 :         putData.nOff = 0;
     448           2 :         putData.nTotalSize = 0;
     449             : 
     450           2 :         CURL *hCurlHandle = curl_easy_init();
     451           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
     452           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
     453             :                                    PutData::ReadCallBackBuffer);
     454           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
     455           2 :         unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
     456             : 
     457             :         struct curl_slist *headers = static_cast<struct curl_slist *>(
     458           2 :             CPLHTTPSetOptions(hCurlHandle, m_poS3HandleHelper->GetURL().c_str(),
     459           2 :                               m_aosHTTPOptions.List()));
     460           4 :         headers = VSICurlSetCreationHeadersFromOptions(
     461           2 :             headers, m_aosOptions.List(), m_osFilename.c_str());
     462           2 :         headers = VSICurlMergeHeaders(
     463           2 :             headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers, "", 0));
     464           2 :         headers = curl_slist_append(headers, "Expect: 100-continue");
     465             : 
     466           4 :         CurlRequestHelper requestHelper;
     467           4 :         const long response_code = requestHelper.perform(
     468           2 :             hCurlHandle, headers, m_poFS, m_poS3HandleHelper);
     469             : 
     470           2 :         NetworkStatisticsLogger::LogPUT(0);
     471             : 
     472           2 :         if (response_code != 200 && response_code != 201)
     473             :         {
     474             :             // Look if we should attempt a retry
     475           0 :             if (oRetryContext.CanRetry(
     476             :                     static_cast<int>(response_code),
     477           0 :                     requestHelper.sWriteFuncHeaderData.pBuffer,
     478             :                     requestHelper.szCurlErrBuf))
     479             :             {
     480           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     481             :                          "HTTP error code: %d - %s. "
     482             :                          "Retrying again in %.1f secs",
     483             :                          static_cast<int>(response_code),
     484           0 :                          m_poS3HandleHelper->GetURL().c_str(),
     485             :                          oRetryContext.GetCurrentDelay());
     486           0 :                 CPLSleep(oRetryContext.GetCurrentDelay());
     487           0 :                 bRetry = true;
     488             :             }
     489           0 :             else if (requestHelper.sWriteFuncData.pBuffer != nullptr &&
     490           0 :                      m_poS3HandleHelper->CanRestartOnError(
     491           0 :                          requestHelper.sWriteFuncData.pBuffer,
     492           0 :                          requestHelper.sWriteFuncHeaderData.pBuffer, false))
     493             :             {
     494           0 :                 bRetry = true;
     495             :             }
     496             :             else
     497             :             {
     498           0 :                 CPLDebug("S3", "%s",
     499           0 :                          requestHelper.sWriteFuncData.pBuffer
     500             :                              ? requestHelper.sWriteFuncData.pBuffer
     501             :                              : "(null)");
     502           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     503             :                          "DoSinglePartPUT of %s failed", m_osFilename.c_str());
     504           0 :                 bSuccess = false;
     505             :             }
     506             :         }
     507             :         else
     508             :         {
     509           2 :             InvalidateParentDirectory();
     510             :         }
     511             : 
     512           2 :         if (requestHelper.sWriteFuncHeaderData.pBuffer != nullptr)
     513             :         {
     514             :             const char *pzETag =
     515           2 :                 strstr(requestHelper.sWriteFuncHeaderData.pBuffer, "ETag: \"");
     516           2 :             if (pzETag)
     517             :             {
     518           0 :                 pzETag += strlen("ETag: \"");
     519           0 :                 const char *pszEndOfETag = strchr(pzETag, '"');
     520           0 :                 if (pszEndOfETag)
     521             :                 {
     522           0 :                     FileProp oFileProp;
     523           0 :                     oFileProp.eExists = EXIST_YES;
     524           0 :                     oFileProp.fileSize = m_nBufferOff;
     525           0 :                     oFileProp.bHasComputedFileSize = true;
     526           0 :                     oFileProp.ETag.assign(pzETag, pszEndOfETag - pzETag);
     527           0 :                     m_poFS->SetCachedFileProp(
     528           0 :                         m_poFS->GetURLFromFilename(m_osFilename.c_str())
     529             :                             .c_str(),
     530             :                         oFileProp);
     531             :                 }
     532             :             }
     533             :         }
     534             : 
     535           2 :         curl_easy_cleanup(hCurlHandle);
     536             :     } while (bRetry);
     537           4 :     return bSuccess;
     538             : }
     539             : 
     540             : }  // namespace cpl
     541             : 
     542             : //! @endcond
     543             : 
     544             : #endif  // HAVE_CURL

Generated by: LCOV version 1.14