Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for Microsoft Azure Blob Storage
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2017-2018, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_port.h"
30 : #include "cpl_http.h"
31 : #include "cpl_minixml.h"
32 : #include "cpl_time.h"
33 : #include "cpl_vsil_curl_priv.h"
34 : #include "cpl_vsil_curl_class.h"
35 :
36 : #include <errno.h>
37 :
38 : #include <algorithm>
39 : #include <set>
40 : #include <map>
41 : #include <memory>
42 :
43 : #include "cpl_azure.h"
44 :
45 : // #define DEBUG_VERBOSE 1
46 :
47 : #ifndef HAVE_CURL
48 :
49 : void VSIInstallAzureFileHandler(void)
50 : {
51 : // Not supported
52 : }
53 :
54 : #else
55 :
56 : //! @cond Doxygen_Suppress
57 : #ifndef DOXYGEN_SKIP
58 :
59 : #define ENABLE_DEBUG 0
60 :
61 : #define unchecked_curl_easy_setopt(handle, opt, param) \
62 : CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
63 :
64 : namespace cpl
65 : {
66 :
67 : const char GDAL_MARKER_FOR_DIR[] = ".gdal_marker_for_dir";
68 :
69 : /************************************************************************/
70 : /* VSIDIRAz */
71 : /************************************************************************/
72 :
73 : struct VSIDIRAz : public VSIDIRWithMissingDirSynthesis
74 : {
75 : int nRecurseDepth = 0;
76 :
77 : std::string osNextMarker{};
78 : int nPos = 0;
79 :
80 : std::string osBucket{};
81 : std::string osObjectKey{};
82 : IVSIS3LikeFSHandler *poFS = nullptr;
83 : std::unique_ptr<IVSIS3LikeHandleHelper> poHandleHelper{};
84 : int nMaxFiles = 0;
85 : bool bCacheEntries = true;
86 : bool m_bSynthetizeMissingDirectories = false;
87 : std::string m_osFilterPrefix{};
88 :
89 37 : explicit VSIDIRAz(IVSIS3LikeFSHandler *poFSIn) : poFS(poFSIn)
90 : {
91 37 : }
92 :
93 : VSIDIRAz(const VSIDIRAz &) = delete;
94 : VSIDIRAz &operator=(const VSIDIRAz &) = delete;
95 :
96 : const VSIDIREntry *NextDirEntry() override;
97 :
98 : bool IssueListDir();
99 : bool AnalyseAzureFileList(const std::string &osBaseURL, const char *pszXML);
100 : void clear();
101 : };
102 :
103 : /************************************************************************/
104 : /* clear() */
105 : /************************************************************************/
106 :
107 39 : void VSIDIRAz::clear()
108 : {
109 39 : osNextMarker.clear();
110 39 : nPos = 0;
111 39 : aoEntries.clear();
112 39 : }
113 :
114 : /************************************************************************/
115 : /* AnalyseAzureFileList() */
116 : /************************************************************************/
117 :
118 29 : bool VSIDIRAz::AnalyseAzureFileList(const std::string &osBaseURL,
119 : const char *pszXML)
120 : {
121 : #if DEBUG_VERBOSE
122 : CPLDebug("AZURE", "%s", pszXML);
123 : #endif
124 :
125 29 : CPLXMLNode *psTree = CPLParseXMLString(pszXML);
126 29 : if (psTree == nullptr)
127 0 : return false;
128 : CPLXMLNode *psEnumerationResults =
129 29 : CPLGetXMLNode(psTree, "=EnumerationResults");
130 :
131 29 : bool bOK = false;
132 29 : if (psEnumerationResults)
133 : {
134 58 : CPLString osPrefix = CPLGetXMLValue(psEnumerationResults, "Prefix", "");
135 29 : if (osPrefix.empty())
136 : {
137 : // in the case of an empty bucket
138 11 : bOK = true;
139 : }
140 18 : else if (osPrefix.endsWith(m_osFilterPrefix))
141 : {
142 18 : osPrefix.resize(osPrefix.size() - m_osFilterPrefix.size());
143 : }
144 :
145 29 : CPLXMLNode *psBlobs = CPLGetXMLNode(psEnumerationResults, "Blobs");
146 29 : if (psBlobs == nullptr)
147 : {
148 6 : psBlobs = CPLGetXMLNode(psEnumerationResults, "Containers");
149 6 : if (psBlobs != nullptr)
150 6 : bOK = true;
151 : }
152 :
153 58 : std::string GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH("/");
154 29 : GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH += GDAL_MARKER_FOR_DIR;
155 :
156 : // Count the number of occurrences of a path. Can be 1 or 2. 2 in the
157 : // case that both a filename and directory exist
158 58 : std::map<std::string, int> aoNameCount;
159 29 : for (CPLXMLNode *psIter = psBlobs ? psBlobs->psChild : nullptr;
160 56 : psIter != nullptr; psIter = psIter->psNext)
161 : {
162 27 : if (psIter->eType != CXT_Element)
163 0 : continue;
164 27 : if (strcmp(psIter->pszValue, "Blob") == 0)
165 : {
166 21 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
167 21 : if (pszKey && strstr(pszKey, GDAL_MARKER_FOR_DIR) != nullptr)
168 : {
169 9 : bOK = true;
170 9 : if (nRecurseDepth < 0)
171 : {
172 3 : if (strcmp(pszKey + osPrefix.size(),
173 3 : GDAL_MARKER_FOR_DIR) == 0)
174 1 : continue;
175 2 : char *pszName = CPLStrdup(pszKey + osPrefix.size());
176 2 : char *pszMarker = strstr(
177 : pszName,
178 : GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH.c_str());
179 2 : if (pszMarker)
180 2 : *pszMarker = '\0';
181 2 : aoNameCount[pszName]++;
182 2 : CPLFree(pszName);
183 8 : }
184 : }
185 12 : else if (pszKey && strlen(pszKey) > osPrefix.size())
186 : {
187 12 : bOK = true;
188 12 : aoNameCount[pszKey + osPrefix.size()]++;
189 : }
190 : }
191 6 : else if (strcmp(psIter->pszValue, "BlobPrefix") == 0 ||
192 4 : strcmp(psIter->pszValue, "Container") == 0)
193 : {
194 6 : bOK = true;
195 :
196 6 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
197 12 : if (pszKey &&
198 6 : strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
199 : {
200 12 : std::string osKey = pszKey;
201 6 : if (!osKey.empty() && osKey.back() == '/')
202 1 : osKey.resize(osKey.size() - 1);
203 6 : if (osKey.size() > osPrefix.size())
204 : {
205 6 : aoNameCount[osKey.c_str() + osPrefix.size()]++;
206 : }
207 : }
208 : }
209 : }
210 :
211 29 : for (CPLXMLNode *psIter = psBlobs ? psBlobs->psChild : nullptr;
212 56 : psIter != nullptr; psIter = psIter->psNext)
213 : {
214 27 : if (psIter->eType != CXT_Element)
215 0 : continue;
216 27 : if (strcmp(psIter->pszValue, "Blob") == 0)
217 : {
218 21 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
219 21 : if (pszKey && strstr(pszKey, GDAL_MARKER_FOR_DIR) != nullptr)
220 : {
221 9 : if (nRecurseDepth < 0)
222 : {
223 3 : if (strcmp(pszKey + osPrefix.size(),
224 3 : GDAL_MARKER_FOR_DIR) == 0)
225 1 : continue;
226 2 : aoEntries.push_back(
227 4 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
228 2 : auto &entry = aoEntries.back();
229 2 : entry->pszName = CPLStrdup(pszKey + osPrefix.size());
230 2 : char *pszMarker = strstr(
231 2 : entry->pszName,
232 : GDAL_MARKER_FOR_DIR_WITH_LEADING_SLASH.c_str());
233 2 : if (pszMarker)
234 2 : *pszMarker = '\0';
235 2 : entry->nMode = S_IFDIR;
236 2 : entry->bModeKnown = true;
237 8 : }
238 : }
239 12 : else if (pszKey && strlen(pszKey) > osPrefix.size())
240 : {
241 24 : const std::string osKeySuffix = pszKey + osPrefix.size();
242 12 : if (m_bSynthetizeMissingDirectories)
243 : {
244 0 : const auto nLastSlashPos = osKeySuffix.rfind('/');
245 0 : if (nLastSlashPos != std::string::npos &&
246 0 : (m_aosSubpathsStack.empty() ||
247 0 : osKeySuffix.compare(0, nLastSlashPos,
248 0 : m_aosSubpathsStack.back()) !=
249 : 0))
250 : {
251 : const bool bAddEntryForThisSubdir =
252 0 : nLastSlashPos != osKeySuffix.size() - 1;
253 0 : SynthetizeMissingDirectories(
254 0 : osKeySuffix.substr(0, nLastSlashPos),
255 : bAddEntryForThisSubdir);
256 : }
257 : }
258 :
259 12 : aoEntries.push_back(
260 24 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
261 12 : auto &entry = aoEntries.back();
262 12 : entry->pszName = CPLStrdup(osKeySuffix.c_str());
263 24 : entry->nSize =
264 12 : static_cast<GUIntBig>(CPLAtoGIntBig(CPLGetXMLValue(
265 : psIter, "Properties.Content-Length", "0")));
266 12 : entry->bSizeKnown = true;
267 12 : entry->nMode = S_IFREG;
268 12 : entry->bModeKnown = true;
269 :
270 24 : std::string ETag = CPLGetXMLValue(psIter, "Etag", "");
271 12 : if (!ETag.empty())
272 : {
273 0 : entry->papszExtra = CSLSetNameValue(
274 0 : entry->papszExtra, "ETag", ETag.c_str());
275 : }
276 :
277 : int nYear, nMonth, nDay, nHour, nMinute, nSecond;
278 12 : if (CPLParseRFC822DateTime(
279 : CPLGetXMLValue(psIter, "Properties.Last-Modified",
280 : ""),
281 : &nYear, &nMonth, &nDay, &nHour, &nMinute, &nSecond,
282 12 : nullptr, nullptr))
283 : {
284 : struct tm brokendowntime;
285 9 : brokendowntime.tm_year = nYear - 1900;
286 9 : brokendowntime.tm_mon = nMonth - 1;
287 9 : brokendowntime.tm_mday = nDay;
288 9 : brokendowntime.tm_hour = nHour;
289 9 : brokendowntime.tm_min = nMinute;
290 9 : brokendowntime.tm_sec = nSecond < 0 ? 0 : nSecond;
291 9 : entry->nMTime = CPLYMDHMSToUnixTime(&brokendowntime);
292 9 : entry->bMTimeKnown = true;
293 : }
294 :
295 12 : if (bCacheEntries)
296 : {
297 20 : FileProp prop;
298 10 : prop.eExists = EXIST_YES;
299 10 : prop.bHasComputedFileSize = true;
300 10 : prop.fileSize = entry->nSize;
301 10 : prop.bIsDirectory = false;
302 10 : prop.mTime = static_cast<time_t>(entry->nMTime);
303 10 : prop.ETag = std::move(ETag);
304 10 : prop.nMode = entry->nMode;
305 :
306 : std::string osCachedFilename =
307 20 : osBaseURL + "/" + CPLAWSURLEncode(osPrefix, false) +
308 40 : CPLAWSURLEncode(entry->pszName, false);
309 : #if DEBUG_VERBOSE
310 : CPLDebug("AZURE", "Cache %s", osCachedFilename.c_str());
311 : #endif
312 10 : poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
313 : }
314 : }
315 : }
316 6 : else if (strcmp(psIter->pszValue, "BlobPrefix") == 0 ||
317 4 : strcmp(psIter->pszValue, "Container") == 0)
318 : {
319 6 : const char *pszKey = CPLGetXMLValue(psIter, "Name", nullptr);
320 12 : if (pszKey &&
321 6 : strncmp(pszKey, osPrefix.c_str(), osPrefix.size()) == 0)
322 : {
323 12 : std::string osKey = pszKey;
324 6 : if (!osKey.empty() && osKey.back() == '/')
325 1 : osKey.resize(osKey.size() - 1);
326 6 : if (osKey.size() > osPrefix.size())
327 : {
328 6 : aoEntries.push_back(
329 12 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
330 6 : auto &entry = aoEntries.back();
331 12 : entry->pszName =
332 6 : CPLStrdup(osKey.c_str() + osPrefix.size());
333 6 : if (aoNameCount[entry->pszName] == 2)
334 : {
335 : // Add a / suffix to disambiguish the situation
336 : // Normally we don't suffix directories with /, but
337 : // we have no alternative here
338 0 : std::string osTemp(entry->pszName);
339 0 : osTemp += '/';
340 0 : CPLFree(entry->pszName);
341 0 : entry->pszName = CPLStrdup(osTemp.c_str());
342 : }
343 6 : entry->nMode = S_IFDIR;
344 6 : entry->bModeKnown = true;
345 :
346 6 : if (bCacheEntries)
347 : {
348 10 : FileProp prop;
349 5 : prop.eExists = EXIST_YES;
350 5 : prop.bIsDirectory = true;
351 5 : prop.bHasComputedFileSize = true;
352 5 : prop.fileSize = 0;
353 5 : prop.mTime = 0;
354 5 : prop.nMode = entry->nMode;
355 :
356 : std::string osCachedFilename =
357 10 : osBaseURL + "/" +
358 10 : CPLAWSURLEncode(osPrefix, false) +
359 20 : CPLAWSURLEncode(entry->pszName, false);
360 : #if DEBUG_VERBOSE
361 : CPLDebug("AZURE", "Cache %s",
362 : osCachedFilename.c_str());
363 : #endif
364 5 : poFS->SetCachedFileProp(osCachedFilename.c_str(),
365 : prop);
366 : }
367 : }
368 : }
369 : }
370 :
371 36 : if (nMaxFiles > 0 &&
372 10 : aoEntries.size() > static_cast<unsigned>(nMaxFiles))
373 0 : break;
374 : }
375 :
376 29 : osNextMarker = CPLGetXMLValue(psEnumerationResults, "NextMarker", "");
377 : }
378 29 : CPLDestroyXMLNode(psTree);
379 :
380 29 : return bOK;
381 : }
382 :
383 : /************************************************************************/
384 : /* IssueListDir() */
385 : /************************************************************************/
386 :
387 39 : bool VSIDIRAz::IssueListDir()
388 : {
389 39 : WriteFuncStruct sWriteFuncData;
390 78 : const std::string l_osNextMarker(osNextMarker);
391 39 : clear();
392 :
393 78 : NetworkStatisticsFileSystem oContextFS("/vsiaz/");
394 78 : NetworkStatisticsAction oContextAction("ListBucket");
395 :
396 78 : CPLString osMaxKeys = CPLGetConfigOption("AZURE_MAX_RESULTS", "");
397 39 : const int AZURE_SERVER_LIMIT_SINGLE_REQUEST = 5000;
398 58 : if (nMaxFiles > 0 && nMaxFiles < AZURE_SERVER_LIMIT_SINGLE_REQUEST &&
399 19 : (osMaxKeys.empty() || nMaxFiles < atoi(osMaxKeys.c_str())))
400 : {
401 19 : osMaxKeys.Printf("%d", nMaxFiles);
402 : }
403 :
404 39 : poHandleHelper->ResetQueryParameters();
405 78 : std::string osBaseURL(poHandleHelper->GetURLNoKVP());
406 39 : if (osBaseURL.back() == '/')
407 7 : osBaseURL.pop_back();
408 :
409 39 : CURL *hCurlHandle = curl_easy_init();
410 :
411 39 : poHandleHelper->AddQueryParameter("comp", "list");
412 39 : if (!l_osNextMarker.empty())
413 2 : poHandleHelper->AddQueryParameter("marker", l_osNextMarker);
414 39 : if (!osMaxKeys.empty())
415 19 : poHandleHelper->AddQueryParameter("maxresults", osMaxKeys);
416 :
417 39 : if (!osBucket.empty())
418 : {
419 32 : poHandleHelper->AddQueryParameter("restype", "container");
420 :
421 32 : if (nRecurseDepth == 0)
422 26 : poHandleHelper->AddQueryParameter("delimiter", "/");
423 32 : if (!osObjectKey.empty())
424 52 : poHandleHelper->AddQueryParameter("prefix", osObjectKey + "/" +
425 26 : m_osFilterPrefix);
426 6 : else if (!m_osFilterPrefix.empty())
427 1 : poHandleHelper->AddQueryParameter("prefix", m_osFilterPrefix);
428 : }
429 :
430 78 : std::string osFilename("/vsiaz/");
431 39 : if (!osBucket.empty())
432 : {
433 32 : osFilename += osBucket;
434 32 : if (!osObjectKey.empty())
435 26 : osFilename += osObjectKey;
436 : }
437 : const CPLStringList aosHTTPOptions(
438 78 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
439 :
440 78 : struct curl_slist *headers = VSICurlSetOptions(
441 39 : hCurlHandle, poHandleHelper->GetURL().c_str(), aosHTTPOptions.List());
442 :
443 39 : headers = VSICurlMergeHeaders(
444 39 : headers, poHandleHelper->GetCurlHeaders("GET", headers));
445 39 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
446 :
447 78 : CurlRequestHelper requestHelper;
448 : const long response_code =
449 39 : requestHelper.perform(hCurlHandle, headers, poFS, poHandleHelper.get());
450 :
451 39 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
452 :
453 39 : if (requestHelper.sWriteFuncData.pBuffer == nullptr)
454 : {
455 8 : curl_easy_cleanup(hCurlHandle);
456 8 : return false;
457 : }
458 :
459 31 : bool ret = false;
460 31 : if (response_code != 200)
461 : {
462 2 : CPLDebug("AZURE", "%s", requestHelper.sWriteFuncData.pBuffer);
463 : }
464 : else
465 : {
466 29 : ret = AnalyseAzureFileList(osBaseURL,
467 29 : requestHelper.sWriteFuncData.pBuffer);
468 : }
469 31 : curl_easy_cleanup(hCurlHandle);
470 31 : return ret;
471 : }
472 :
473 : /************************************************************************/
474 : /* NextDirEntry() */
475 : /************************************************************************/
476 :
477 42 : const VSIDIREntry *VSIDIRAz::NextDirEntry()
478 : {
479 : while (true)
480 : {
481 42 : if (nPos < static_cast<int>(aoEntries.size()))
482 : {
483 20 : auto &entry = aoEntries[nPos];
484 20 : nPos++;
485 20 : return entry.get();
486 : }
487 22 : if (osNextMarker.empty())
488 : {
489 20 : return nullptr;
490 : }
491 2 : if (!IssueListDir())
492 : {
493 0 : return nullptr;
494 : }
495 2 : }
496 : }
497 :
498 : /************************************************************************/
499 : /* VSIAzureFSHandler */
500 : /************************************************************************/
501 :
502 : class VSIAzureFSHandler final : public IVSIS3LikeFSHandler
503 : {
504 : CPL_DISALLOW_COPY_ASSIGN(VSIAzureFSHandler)
505 : const std::string m_osPrefix;
506 :
507 : int CreateContainer(const std::string &osDirname);
508 : int DeleteContainer(const std::string &osDirname);
509 :
510 : protected:
511 : VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
512 : std::string GetURLFromFilename(const std::string &osFilename) override;
513 :
514 : VSIAzureBlobHandleHelper *CreateAzHandleHelper(const char *pszURI,
515 : bool bAllowNoObject);
516 :
517 67 : IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
518 : bool bAllowNoObject) override
519 : {
520 67 : return CreateAzHandleHelper(pszURI, bAllowNoObject);
521 : }
522 :
523 : char **GetFileList(const char *pszFilename, int nMaxFiles,
524 : bool *pbGotFileList) override;
525 :
526 : void InvalidateRecursive(const std::string &osDirnameIn);
527 :
528 : int CopyFile(const char *pszSource, const char *pszTarget,
529 : VSILFILE *fpSource, vsi_l_offset nSourceSize,
530 : const char *const *papszOptions,
531 : GDALProgressFunc pProgressFunc, void *pProgressData) override;
532 :
533 : int CopyObject(const char *oldpath, const char *newpath,
534 : CSLConstList papszMetadata) override;
535 : int MkdirInternal(const char *pszDirname, long nMode,
536 : bool bDoStatCheck) override;
537 :
538 : void ClearCache() override;
539 :
540 4 : bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
541 : {
542 4 : return STARTS_WITH(pszHeaderName, "x-ms-");
543 : }
544 :
545 : VSIVirtualHandleUniquePtr
546 : CreateWriteHandle(const char *pszFilename,
547 : CSLConstList papszOptions) override;
548 :
549 : public:
550 1228 : explicit VSIAzureFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
551 : {
552 1228 : }
553 :
554 1704 : ~VSIAzureFSHandler() override = default;
555 :
556 1330 : std::string GetFSPrefix() const override
557 : {
558 1330 : return m_osPrefix;
559 : }
560 :
561 65 : const char *GetDebugKey() const override
562 : {
563 65 : return "AZURE";
564 : }
565 :
566 : int Unlink(const char *pszFilename) override;
567 : int *UnlinkBatch(CSLConstList papszFiles) override;
568 : int Mkdir(const char *, long) override;
569 : int Rmdir(const char *) override;
570 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
571 : int nFlags) override;
572 :
573 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
574 : CSLConstList papszOptions) override;
575 :
576 : bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
577 : const char *pszDomain,
578 : CSLConstList papszOptions) override;
579 :
580 : const char *GetOptions() override;
581 :
582 : char *GetSignedURL(const char *pszFilename,
583 : CSLConstList papszOptions) override;
584 :
585 : char **GetFileList(const char *pszFilename, int nMaxFiles,
586 : bool bCacheEntries, bool *pbGotFileList);
587 :
588 : VSIDIR *OpenDir(const char *pszPath, int nRecurseDepth,
589 : const char *const *papszOptions) override;
590 :
591 : // Block list upload
592 : std::string PutBlock(const std::string &osFilename, int nPartNumber,
593 : const void *pabyBuffer, size_t nBufferSize,
594 : IVSIS3LikeHandleHelper *poS3HandleHelper,
595 : int nMaxRetry, double dfRetryDelay,
596 : CSLConstList papszOptions);
597 : bool PutBlockList(const std::string &osFilename,
598 : const std::vector<std::string> &aosBlockIds,
599 : IVSIS3LikeHandleHelper *poS3HandleHelper, int nMaxRetry,
600 : double dfRetryDelay);
601 :
602 : // Multipart upload (mapping of S3 interface to PutBlock/PutBlockList)
603 :
604 2 : bool SupportsParallelMultipartUpload() const override
605 : {
606 2 : return true;
607 : }
608 :
609 : std::string
610 2 : InitiateMultipartUpload(const std::string & /* osFilename */,
611 : IVSIS3LikeHandleHelper *, int /* nMaxRetry */,
612 : double /* dfRetryDelay */,
613 : CSLConstList /* papszOptions */) override
614 : {
615 2 : return "dummy";
616 : }
617 :
618 4 : std::string UploadPart(const std::string &osFilename, int nPartNumber,
619 : const std::string & /* osUploadID */,
620 : vsi_l_offset /* nPosition */, const void *pabyBuffer,
621 : size_t nBufferSize,
622 : IVSIS3LikeHandleHelper *poS3HandleHelper,
623 : int nMaxRetry, double dfRetryDelay,
624 : CSLConstList papszOptions) override
625 : {
626 : return PutBlock(osFilename, nPartNumber, pabyBuffer, nBufferSize,
627 : poS3HandleHelper, nMaxRetry, dfRetryDelay,
628 4 : papszOptions);
629 : }
630 :
631 2 : bool CompleteMultipart(const std::string &osFilename,
632 : const std::string & /* osUploadID */,
633 : const std::vector<std::string> &aosEtags,
634 : vsi_l_offset /* nTotalSize */,
635 : IVSIS3LikeHandleHelper *poS3HandleHelper,
636 : int nMaxRetry, double dfRetryDelay) override
637 : {
638 2 : return PutBlockList(osFilename, aosEtags, poS3HandleHelper, nMaxRetry,
639 2 : dfRetryDelay);
640 : }
641 :
642 0 : bool AbortMultipart(const std::string & /* osFilename */,
643 : const std::string & /* osUploadID */,
644 : IVSIS3LikeHandleHelper * /*poS3HandleHelper */,
645 : int /* nMaxRetry */, double /* dfRetryDelay */) override
646 : {
647 0 : return true;
648 : }
649 :
650 : std::string
651 : GetStreamingFilename(const std::string &osFilename) const override;
652 :
653 0 : VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
654 : {
655 0 : return new VSIAzureFSHandler(pszPrefix);
656 : }
657 : };
658 :
659 : /************************************************************************/
660 : /* VSIAzureHandle */
661 : /************************************************************************/
662 :
663 : class VSIAzureHandle final : public VSICurlHandle
664 : {
665 : CPL_DISALLOW_COPY_ASSIGN(VSIAzureHandle)
666 :
667 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
668 :
669 : protected:
670 : virtual struct curl_slist *
671 : GetCurlHeaders(const std::string &osVerb,
672 : const struct curl_slist *psExistingHeaders) override;
673 : virtual bool IsDirectoryFromExists(const char *pszVerb,
674 : int response_code) override;
675 :
676 : public:
677 : VSIAzureHandle(VSIAzureFSHandler *poFS, const char *pszFilename,
678 : VSIAzureBlobHandleHelper *poHandleHelper);
679 : };
680 :
681 : /************************************************************************/
682 : /* VSIAzureWriteHandle */
683 : /************************************************************************/
684 :
685 : class VSIAzureWriteHandle final : public VSIAppendWriteHandle
686 : {
687 : CPL_DISALLOW_COPY_ASSIGN(VSIAzureWriteHandle)
688 :
689 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
690 : CPLStringList m_aosOptions{};
691 : CPLStringList m_aosHTTPOptions{};
692 :
693 : bool Send(bool bIsLastBlock) override;
694 : bool SendInternal(bool bInitOnly, bool bIsLastBlock);
695 :
696 : void InvalidateParentDirectory();
697 :
698 : public:
699 : VSIAzureWriteHandle(VSIAzureFSHandler *poFS, const char *pszFilename,
700 : VSIAzureBlobHandleHelper *poHandleHelper,
701 : CSLConstList papszOptions);
702 : virtual ~VSIAzureWriteHandle();
703 : };
704 :
705 : /************************************************************************/
706 : /* CreateFileHandle() */
707 : /************************************************************************/
708 :
709 52 : VSICurlHandle *VSIAzureFSHandler::CreateFileHandle(const char *pszFilename)
710 : {
711 : VSIAzureBlobHandleHelper *poHandleHelper =
712 104 : VSIAzureBlobHandleHelper::BuildFromURI(
713 156 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
714 52 : if (poHandleHelper == nullptr)
715 4 : return nullptr;
716 48 : return new VSIAzureHandle(this, pszFilename, poHandleHelper);
717 : }
718 :
719 : /************************************************************************/
720 : /* CreateWriteHandle() */
721 : /************************************************************************/
722 :
723 : VSIVirtualHandleUniquePtr
724 12 : VSIAzureFSHandler::CreateWriteHandle(const char *pszFilename,
725 : CSLConstList papszOptions)
726 : {
727 : VSIAzureBlobHandleHelper *poHandleHelper =
728 24 : VSIAzureBlobHandleHelper::BuildFromURI(
729 36 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
730 12 : if (poHandleHelper == nullptr)
731 0 : return nullptr;
732 : auto poHandle = std::make_unique<VSIAzureWriteHandle>(
733 24 : this, pszFilename, poHandleHelper, papszOptions);
734 12 : if (!poHandle->IsOK())
735 : {
736 0 : return nullptr;
737 : }
738 12 : return VSIVirtualHandleUniquePtr(poHandle.release());
739 : }
740 :
741 : /************************************************************************/
742 : /* Stat() */
743 : /************************************************************************/
744 :
745 31 : int VSIAzureFSHandler::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
746 : int nFlags)
747 : {
748 31 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
749 0 : return -1;
750 :
751 31 : if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
752 3 : return VSICurlFilesystemHandlerBase::Stat(pszFilename, pStatBuf,
753 3 : nFlags);
754 :
755 56 : std::string osFilename(pszFilename);
756 :
757 56 : if ((osFilename.find('/', GetFSPrefix().size()) == std::string::npos ||
758 60 : osFilename.find('/', GetFSPrefix().size()) == osFilename.size() - 1) &&
759 4 : CPLGetConfigOption("AZURE_SAS", nullptr) != nullptr)
760 : {
761 : // On "/vsiaz/container", a HEAD or GET request fails to authenticate
762 : // when SAS is used, so use directory listing instead.
763 0 : char **papszRet = ReadDirInternal(osFilename.c_str(), 100, nullptr);
764 0 : int nRet = papszRet ? 0 : -1;
765 0 : if (nRet == 0)
766 : {
767 0 : pStatBuf->st_mtime = 0;
768 0 : pStatBuf->st_size = 0;
769 0 : pStatBuf->st_mode = S_IFDIR;
770 :
771 0 : FileProp cachedFileProp;
772 0 : GetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
773 : cachedFileProp);
774 0 : cachedFileProp.eExists = EXIST_YES;
775 0 : cachedFileProp.bIsDirectory = true;
776 0 : cachedFileProp.bHasComputedFileSize = true;
777 0 : SetCachedFileProp(GetURLFromFilename(osFilename.c_str()).c_str(),
778 : cachedFileProp);
779 : }
780 0 : CSLDestroy(papszRet);
781 0 : return nRet;
782 : }
783 :
784 28 : if (osFilename.find('/', GetFSPrefix().size()) == std::string::npos)
785 : {
786 2 : osFilename += "/";
787 : }
788 :
789 28 : if (osFilename.size() > GetFSPrefix().size())
790 : {
791 : // Special case for container
792 28 : std::string osFilenameWithoutEndSlash(osFilename);
793 28 : if (osFilenameWithoutEndSlash.back() == '/')
794 14 : osFilenameWithoutEndSlash.resize(osFilenameWithoutEndSlash.size() -
795 : 1);
796 28 : if (osFilenameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
797 : std::string::npos)
798 : {
799 4 : char **papszFileList = ReadDir(GetFSPrefix().c_str());
800 4 : const int nIdx = CSLFindString(
801 : papszFileList,
802 8 : osFilenameWithoutEndSlash.substr(GetFSPrefix().size()).c_str());
803 4 : CSLDestroy(papszFileList);
804 4 : if (nIdx >= 0)
805 : {
806 2 : pStatBuf->st_mtime = 0;
807 2 : pStatBuf->st_size = 0;
808 2 : pStatBuf->st_mode = S_IFDIR;
809 2 : return 0;
810 : }
811 : }
812 : }
813 :
814 26 : return VSICurlFilesystemHandlerBase::Stat(osFilename.c_str(), pStatBuf,
815 26 : nFlags);
816 : }
817 :
818 : /************************************************************************/
819 : /* GetFileMetadata() */
820 : /************************************************************************/
821 :
822 4 : char **VSIAzureFSHandler::GetFileMetadata(const char *pszFilename,
823 : const char *pszDomain,
824 : CSLConstList papszOptions)
825 : {
826 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
827 0 : return nullptr;
828 :
829 4 : if (pszDomain == nullptr ||
830 4 : (!EQUAL(pszDomain, "TAGS") && !EQUAL(pszDomain, "METADATA")))
831 : {
832 1 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
833 1 : pszFilename, pszDomain, papszOptions);
834 : }
835 :
836 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
837 6 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
838 3 : if (poHandleHelper == nullptr)
839 : {
840 0 : return nullptr;
841 : }
842 :
843 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
844 6 : NetworkStatisticsAction oContextAction("GetFileMetadata");
845 :
846 : bool bRetry;
847 : // coverity[tainted_data]
848 3 : double dfRetryDelay = CPLAtof(
849 : VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
850 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
851 : const int nMaxRetry =
852 3 : atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
853 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
854 3 : int nRetryCount = 0;
855 3 : bool bError = true;
856 :
857 6 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
858 :
859 3 : CPLStringList aosMetadata;
860 3 : do
861 : {
862 3 : bRetry = false;
863 3 : CURL *hCurlHandle = curl_easy_init();
864 3 : if (EQUAL(pszDomain, "METADATA"))
865 2 : poHandleHelper->AddQueryParameter("comp", "metadata");
866 : else
867 1 : poHandleHelper->AddQueryParameter("comp", "tags");
868 :
869 : struct curl_slist *headers =
870 3 : VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
871 : aosHTTPOptions.List());
872 :
873 3 : headers = VSICurlMergeHeaders(
874 3 : headers, poHandleHelper->GetCurlHeaders("GET", headers));
875 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
876 :
877 6 : CurlRequestHelper requestHelper;
878 3 : const long response_code = requestHelper.perform(
879 : hCurlHandle, headers, this, poHandleHelper.get());
880 :
881 3 : NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
882 :
883 3 : if (response_code != 200 ||
884 2 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
885 : {
886 : // Look if we should attempt a retry
887 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
888 : static_cast<int>(response_code), dfRetryDelay,
889 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
890 : requestHelper.szCurlErrBuf);
891 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
892 : {
893 0 : CPLError(CE_Warning, CPLE_AppDefined,
894 : "HTTP error code: %d - %s. "
895 : "Retrying again in %.1f secs",
896 : static_cast<int>(response_code),
897 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
898 0 : CPLSleep(dfRetryDelay);
899 0 : dfRetryDelay = dfNewRetryDelay;
900 0 : nRetryCount++;
901 0 : bRetry = true;
902 : }
903 : else
904 : {
905 1 : CPLDebug(GetDebugKey(), "GetFileMetadata failed on %s: %s",
906 : pszFilename,
907 1 : requestHelper.sWriteFuncData.pBuffer
908 : ? requestHelper.sWriteFuncData.pBuffer
909 : : "(null)");
910 1 : }
911 : }
912 : else
913 : {
914 2 : if (EQUAL(pszDomain, "METADATA"))
915 : {
916 2 : char **papszHeaders = CSLTokenizeString2(
917 1 : requestHelper.sWriteFuncHeaderData.pBuffer, "\r\n", 0);
918 6 : for (int i = 0; papszHeaders[i]; ++i)
919 : {
920 5 : char *pszKey = nullptr;
921 : const char *pszValue =
922 5 : CPLParseNameValue(papszHeaders[i], &pszKey);
923 : // Content-Length is returned as 0
924 5 : if (pszKey && pszValue && !EQUAL(pszKey, "Content-Length"))
925 : {
926 3 : aosMetadata.SetNameValue(pszKey, pszValue);
927 : }
928 5 : CPLFree(pszKey);
929 : }
930 1 : CSLDestroy(papszHeaders);
931 : }
932 : else
933 : {
934 : CPLXMLNode *psXML =
935 1 : CPLParseXMLString(requestHelper.sWriteFuncData.pBuffer);
936 1 : if (psXML)
937 : {
938 1 : CPLXMLNode *psTagSet = CPLGetXMLNode(psXML, "=Tags.TagSet");
939 1 : if (psTagSet)
940 : {
941 2 : for (CPLXMLNode *psIter = psTagSet->psChild; psIter;
942 1 : psIter = psIter->psNext)
943 : {
944 1 : if (psIter->eType == CXT_Element &&
945 1 : strcmp(psIter->pszValue, "Tag") == 0)
946 : {
947 : const char *pszKey =
948 1 : CPLGetXMLValue(psIter, "Key", "");
949 : const char *pszValue =
950 1 : CPLGetXMLValue(psIter, "Value", "");
951 1 : aosMetadata.SetNameValue(pszKey, pszValue);
952 : }
953 : }
954 : }
955 1 : CPLDestroyXMLNode(psXML);
956 : }
957 : }
958 2 : bError = false;
959 : }
960 :
961 3 : curl_easy_cleanup(hCurlHandle);
962 : } while (bRetry);
963 3 : return bError ? nullptr : CSLDuplicate(aosMetadata.List());
964 : }
965 :
966 : /************************************************************************/
967 : /* SetFileMetadata() */
968 : /************************************************************************/
969 :
970 4 : bool VSIAzureFSHandler::SetFileMetadata(const char *pszFilename,
971 : CSLConstList papszMetadata,
972 : const char *pszDomain,
973 : CSLConstList /* papszOptions */)
974 : {
975 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
976 0 : return false;
977 :
978 4 : if (pszDomain == nullptr ||
979 4 : !(EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "METADATA") ||
980 1 : EQUAL(pszDomain, "TAGS")))
981 : {
982 0 : CPLError(CE_Failure, CPLE_NotSupported,
983 : "Only PROPERTIES, METADATA and TAGS domain are supported");
984 0 : return false;
985 : }
986 :
987 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
988 8 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
989 4 : if (poHandleHelper == nullptr)
990 : {
991 0 : return false;
992 : }
993 :
994 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
995 8 : NetworkStatisticsAction oContextAction("SetFileMetadata");
996 :
997 : bool bRetry;
998 : // coverity[tainted_data]
999 4 : double dfRetryDelay = CPLAtof(
1000 : VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
1001 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1002 : const int nMaxRetry =
1003 4 : atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
1004 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1005 4 : int nRetryCount = 0;
1006 :
1007 4 : bool bRet = false;
1008 :
1009 : // Compose XML content for TAGS
1010 8 : std::string osXML;
1011 4 : if (EQUAL(pszDomain, "TAGS"))
1012 : {
1013 1 : CPLXMLNode *psXML = CPLCreateXMLNode(nullptr, CXT_Element, "?xml");
1014 1 : CPLAddXMLAttributeAndValue(psXML, "version", "1.0");
1015 1 : CPLAddXMLAttributeAndValue(psXML, "encoding", "UTF-8");
1016 1 : CPLXMLNode *psTags = CPLCreateXMLNode(nullptr, CXT_Element, "Tags");
1017 1 : psXML->psNext = psTags;
1018 1 : CPLXMLNode *psTagSet = CPLCreateXMLNode(psTags, CXT_Element, "TagSet");
1019 2 : for (int i = 0; papszMetadata && papszMetadata[i]; ++i)
1020 : {
1021 1 : char *pszKey = nullptr;
1022 1 : const char *pszValue = CPLParseNameValue(papszMetadata[i], &pszKey);
1023 1 : if (pszKey && pszValue)
1024 : {
1025 : CPLXMLNode *psTag =
1026 1 : CPLCreateXMLNode(psTagSet, CXT_Element, "Tag");
1027 1 : CPLCreateXMLElementAndValue(psTag, "Key", pszKey);
1028 1 : CPLCreateXMLElementAndValue(psTag, "Value", pszValue);
1029 : }
1030 1 : CPLFree(pszKey);
1031 : }
1032 :
1033 1 : char *pszXML = CPLSerializeXMLTree(psXML);
1034 1 : osXML = pszXML;
1035 1 : CPLFree(pszXML);
1036 1 : CPLDestroyXMLNode(psXML);
1037 : }
1038 :
1039 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
1040 :
1041 4 : do
1042 : {
1043 4 : bRetry = false;
1044 4 : CURL *hCurlHandle = curl_easy_init();
1045 :
1046 4 : if (EQUAL(pszDomain, "PROPERTIES"))
1047 1 : poHandleHelper->AddQueryParameter("comp", "properties");
1048 3 : else if (EQUAL(pszDomain, "METADATA"))
1049 2 : poHandleHelper->AddQueryParameter("comp", "metadata");
1050 : else
1051 1 : poHandleHelper->AddQueryParameter("comp", "tags");
1052 :
1053 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1054 4 : if (!osXML.empty())
1055 : {
1056 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
1057 : osXML.c_str());
1058 : }
1059 :
1060 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1061 4 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1062 : aosHTTPOptions.List()));
1063 :
1064 8 : CPLStringList aosList;
1065 4 : if (EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "METADATA"))
1066 : {
1067 6 : for (CSLConstList papszIter = papszMetadata;
1068 6 : papszIter && *papszIter; ++papszIter)
1069 : {
1070 3 : char *pszKey = nullptr;
1071 3 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
1072 3 : if (pszKey && pszValue)
1073 : {
1074 : const char *pszHeader =
1075 3 : CPLSPrintf("%s: %s", pszKey, pszValue);
1076 3 : aosList.AddString(pszHeader);
1077 3 : headers = curl_slist_append(headers, pszHeader);
1078 : }
1079 3 : CPLFree(pszKey);
1080 : }
1081 : }
1082 :
1083 8 : CPLString osContentLength;
1084 : osContentLength.Printf("Content-Length: %d",
1085 4 : static_cast<int>(osXML.size()));
1086 4 : headers = curl_slist_append(headers, osContentLength.c_str());
1087 4 : if (!osXML.empty())
1088 : {
1089 1 : headers = curl_slist_append(
1090 : headers, "Content-Type: application/xml; charset=UTF-8");
1091 1 : headers = VSICurlMergeHeaders(
1092 1 : headers, poHandleHelper->GetCurlHeaders(
1093 1 : "PUT", headers, osXML.c_str(), osXML.size()));
1094 : }
1095 : else
1096 : {
1097 3 : headers = VSICurlMergeHeaders(
1098 3 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
1099 : }
1100 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1101 :
1102 4 : NetworkStatisticsLogger::LogPUT(osXML.size());
1103 :
1104 8 : CurlRequestHelper requestHelper;
1105 4 : const long response_code = requestHelper.perform(
1106 : hCurlHandle, headers, this, poHandleHelper.get());
1107 :
1108 4 : if (response_code != 200 && response_code != 204)
1109 : {
1110 : // Look if we should attempt a retry
1111 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1112 : static_cast<int>(response_code), dfRetryDelay,
1113 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1114 : requestHelper.szCurlErrBuf);
1115 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1116 : {
1117 0 : CPLError(CE_Warning, CPLE_AppDefined,
1118 : "HTTP error code: %d - %s. "
1119 : "Retrying again in %.1f secs",
1120 : static_cast<int>(response_code),
1121 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
1122 0 : CPLSleep(dfRetryDelay);
1123 0 : dfRetryDelay = dfNewRetryDelay;
1124 0 : nRetryCount++;
1125 0 : bRetry = true;
1126 : }
1127 : else
1128 : {
1129 1 : CPLDebug(GetDebugKey(), "SetFileMetadata on %s failed: %s",
1130 : pszFilename,
1131 1 : requestHelper.sWriteFuncData.pBuffer
1132 : ? requestHelper.sWriteFuncData.pBuffer
1133 : : "(null)");
1134 1 : }
1135 : }
1136 : else
1137 : {
1138 3 : bRet = true;
1139 : }
1140 :
1141 4 : curl_easy_cleanup(hCurlHandle);
1142 : } while (bRetry);
1143 4 : return bRet;
1144 : }
1145 :
1146 : /************************************************************************/
1147 : /* GetStreamingFilename() */
1148 : /************************************************************************/
1149 :
1150 : std::string
1151 0 : VSIAzureFSHandler::GetStreamingFilename(const std::string &osFilename) const
1152 : {
1153 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
1154 0 : return "/vsiaz_streaming/" + osFilename.substr(GetFSPrefix().size());
1155 0 : return osFilename;
1156 : }
1157 :
1158 : /************************************************************************/
1159 : /* GetAzureBufferSize() */
1160 : /************************************************************************/
1161 :
1162 19 : int GetAzureBufferSize()
1163 : {
1164 : int nBufferSize;
1165 19 : int nChunkSizeMB = atoi(CPLGetConfigOption("VSIAZ_CHUNK_SIZE", "4"));
1166 19 : if (nChunkSizeMB <= 0 || nChunkSizeMB > 4)
1167 0 : nBufferSize = 4 * 1024 * 1024;
1168 : else
1169 19 : nBufferSize = nChunkSizeMB * 1024 * 1024;
1170 :
1171 : // For testing only !
1172 : const char *pszChunkSizeBytes =
1173 19 : CPLGetConfigOption("VSIAZ_CHUNK_SIZE_BYTES", nullptr);
1174 19 : if (pszChunkSizeBytes)
1175 6 : nBufferSize = atoi(pszChunkSizeBytes);
1176 19 : if (nBufferSize <= 0 || nBufferSize > 4 * 1024 * 1024)
1177 0 : nBufferSize = 4 * 1024 * 1024;
1178 19 : return nBufferSize;
1179 : }
1180 :
1181 : /************************************************************************/
1182 : /* VSIAzureWriteHandle() */
1183 : /************************************************************************/
1184 :
1185 12 : VSIAzureWriteHandle::VSIAzureWriteHandle(
1186 : VSIAzureFSHandler *poFS, const char *pszFilename,
1187 12 : VSIAzureBlobHandleHelper *poHandleHelper, CSLConstList papszOptions)
1188 12 : : VSIAppendWriteHandle(poFS, poFS->GetFSPrefix().c_str(), pszFilename,
1189 : GetAzureBufferSize()),
1190 : m_poHandleHelper(poHandleHelper), m_aosOptions(papszOptions),
1191 24 : m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename))
1192 : {
1193 12 : }
1194 :
1195 : /************************************************************************/
1196 : /* ~VSIAzureWriteHandle() */
1197 : /************************************************************************/
1198 :
1199 24 : VSIAzureWriteHandle::~VSIAzureWriteHandle()
1200 : {
1201 12 : Close();
1202 24 : }
1203 :
1204 : /************************************************************************/
1205 : /* InvalidateParentDirectory() */
1206 : /************************************************************************/
1207 :
1208 11 : void VSIAzureWriteHandle::InvalidateParentDirectory()
1209 : {
1210 11 : m_poFS->InvalidateCachedData(m_poHandleHelper->GetURLNoKVP().c_str());
1211 :
1212 22 : std::string osFilenameWithoutSlash(m_osFilename);
1213 11 : if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/')
1214 0 : osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() - 1);
1215 11 : m_poFS->InvalidateDirContent(CPLGetDirname(osFilenameWithoutSlash.c_str()));
1216 11 : }
1217 :
1218 : /************************************************************************/
1219 : /* Send() */
1220 : /************************************************************************/
1221 :
1222 12 : bool VSIAzureWriteHandle::Send(bool bIsLastBlock)
1223 : {
1224 12 : if (!bIsLastBlock)
1225 : {
1226 4 : CPLAssert(m_nBufferOff == m_nBufferSize);
1227 4 : if (m_nCurOffset == static_cast<vsi_l_offset>(m_nBufferSize))
1228 : {
1229 : // First full buffer ? Then create the blob empty
1230 4 : if (!SendInternal(true, false))
1231 1 : return false;
1232 : }
1233 : }
1234 11 : return SendInternal(false, bIsLastBlock);
1235 : }
1236 :
1237 : /************************************************************************/
1238 : /* SendInternal() */
1239 : /************************************************************************/
1240 :
1241 15 : bool VSIAzureWriteHandle::SendInternal(bool bInitOnly, bool bIsLastBlock)
1242 : {
1243 30 : NetworkStatisticsFileSystem oContextFS("/vsiaz/");
1244 30 : NetworkStatisticsFile oContextFile(m_osFilename.c_str());
1245 15 : NetworkStatisticsAction oContextAction("Write");
1246 :
1247 15 : bool bSuccess = true;
1248 15 : const bool bSingleBlock =
1249 23 : bIsLastBlock &&
1250 8 : (m_nCurOffset <= static_cast<vsi_l_offset>(m_nBufferSize));
1251 :
1252 : // coverity[tainted_data]
1253 15 : double dfRetryDelay = CPLAtof(
1254 : VSIGetPathSpecificOption(m_osFilename.c_str(), "GDAL_HTTP_RETRY_DELAY",
1255 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1256 15 : const int nMaxRetry = atoi(
1257 : VSIGetPathSpecificOption(m_osFilename.c_str(), "GDAL_HTTP_MAX_RETRY",
1258 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1259 15 : int nRetryCount = 0;
1260 15 : bool bHasAlreadyHandled409 = false;
1261 : bool bRetry;
1262 :
1263 20 : do
1264 : {
1265 20 : bRetry = false;
1266 :
1267 20 : m_nBufferOffReadCallback = 0;
1268 20 : CURL *hCurlHandle = curl_easy_init();
1269 :
1270 20 : m_poHandleHelper->ResetQueryParameters();
1271 20 : if (!bSingleBlock && !bInitOnly)
1272 : {
1273 7 : m_poHandleHelper->AddQueryParameter("comp", "appendblock");
1274 : }
1275 :
1276 20 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
1277 20 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
1278 : ReadCallBackBuffer);
1279 20 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA,
1280 : static_cast<VSIAppendWriteHandle *>(this));
1281 :
1282 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1283 20 : CPLHTTPSetOptions(hCurlHandle, m_poHandleHelper->GetURL().c_str(),
1284 20 : m_aosHTTPOptions.List()));
1285 40 : headers = VSICurlSetCreationHeadersFromOptions(
1286 20 : headers, m_aosOptions.List(), m_osFilename.c_str());
1287 :
1288 40 : CPLString osContentLength; // leave it in this scope
1289 20 : if (bSingleBlock)
1290 : {
1291 8 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1292 : m_nBufferOff);
1293 8 : if (m_nBufferOff)
1294 4 : headers = curl_slist_append(headers, "Expect: 100-continue");
1295 8 : osContentLength.Printf("Content-Length: %d", m_nBufferOff);
1296 8 : headers = curl_slist_append(headers, osContentLength.c_str());
1297 8 : headers = curl_slist_append(headers, "x-ms-blob-type: BlockBlob");
1298 : }
1299 12 : else if (bInitOnly)
1300 : {
1301 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
1302 5 : headers = curl_slist_append(headers, "Content-Length: 0");
1303 5 : headers = curl_slist_append(headers, "x-ms-blob-type: AppendBlob");
1304 : }
1305 : else
1306 : {
1307 7 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1308 : m_nBufferOff);
1309 7 : osContentLength.Printf("Content-Length: %d", m_nBufferOff);
1310 7 : headers = curl_slist_append(headers, osContentLength.c_str());
1311 7 : vsi_l_offset nStartOffset = m_nCurOffset - m_nBufferOff;
1312 7 : const char *pszAppendPos = CPLSPrintf(
1313 : "x-ms-blob-condition-appendpos: " CPL_FRMT_GUIB, nStartOffset);
1314 7 : headers = curl_slist_append(headers, pszAppendPos);
1315 : }
1316 :
1317 20 : headers = VSICurlMergeHeaders(
1318 : headers, m_poHandleHelper->GetCurlHeaders("PUT", headers));
1319 20 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1320 :
1321 40 : CurlRequestHelper requestHelper;
1322 20 : const long response_code = requestHelper.perform(
1323 20 : hCurlHandle, headers, m_poFS, m_poHandleHelper.get());
1324 :
1325 20 : NetworkStatisticsLogger::LogPUT(m_nBufferOff);
1326 :
1327 20 : if (!bHasAlreadyHandled409 && response_code == 409)
1328 : {
1329 1 : bHasAlreadyHandled409 = true;
1330 1 : CPLDebug(cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->GetDebugKey(),
1331 : "%s",
1332 1 : requestHelper.sWriteFuncData.pBuffer
1333 : ? requestHelper.sWriteFuncData.pBuffer
1334 : : "(null)");
1335 :
1336 : // The blob type is invalid for this operation
1337 : // Delete the file, and retry
1338 2 : if (cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->DeleteObject(
1339 2 : m_osFilename.c_str()) == 0)
1340 : {
1341 1 : bRetry = true;
1342 : }
1343 : }
1344 19 : else if (response_code != 201)
1345 : {
1346 : // Look if we should attempt a retry
1347 16 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1348 : static_cast<int>(response_code), dfRetryDelay,
1349 8 : requestHelper.sWriteFuncHeaderData.pBuffer,
1350 : requestHelper.szCurlErrBuf);
1351 8 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1352 : {
1353 4 : CPLError(CE_Warning, CPLE_AppDefined,
1354 : "HTTP error code: %d - %s. "
1355 : "Retrying again in %.1f secs",
1356 : static_cast<int>(response_code),
1357 4 : m_poHandleHelper->GetURL().c_str(), dfRetryDelay);
1358 4 : CPLSleep(dfRetryDelay);
1359 4 : dfRetryDelay = dfNewRetryDelay;
1360 4 : nRetryCount++;
1361 4 : bRetry = true;
1362 : }
1363 : else
1364 : {
1365 4 : CPLDebug(
1366 : cpl::down_cast<VSIAzureFSHandler *>(m_poFS)->GetDebugKey(),
1367 : "%s",
1368 4 : requestHelper.sWriteFuncData.pBuffer
1369 : ? requestHelper.sWriteFuncData.pBuffer
1370 : : "(null)");
1371 4 : CPLError(CE_Failure, CPLE_AppDefined, "PUT of %s failed",
1372 : m_osFilename.c_str());
1373 4 : bSuccess = false;
1374 : }
1375 : }
1376 : else
1377 : {
1378 11 : InvalidateParentDirectory();
1379 : }
1380 :
1381 20 : curl_easy_cleanup(hCurlHandle);
1382 : } while (bRetry);
1383 :
1384 30 : return bSuccess;
1385 : }
1386 :
1387 : /************************************************************************/
1388 : /* ClearCache() */
1389 : /************************************************************************/
1390 :
1391 246 : void VSIAzureFSHandler::ClearCache()
1392 : {
1393 246 : IVSIS3LikeFSHandler::ClearCache();
1394 :
1395 246 : VSIAzureBlobHandleHelper::ClearCache();
1396 246 : }
1397 :
1398 : /************************************************************************/
1399 : /* GetURLFromFilename() */
1400 : /************************************************************************/
1401 :
1402 45 : std::string VSIAzureFSHandler::GetURLFromFilename(const std::string &osFilename)
1403 : {
1404 : std::string osFilenameWithoutPrefix =
1405 90 : osFilename.substr(GetFSPrefix().size());
1406 : VSIAzureBlobHandleHelper *poHandleHelper =
1407 45 : VSIAzureBlobHandleHelper::BuildFromURI(osFilenameWithoutPrefix.c_str(),
1408 90 : GetFSPrefix().c_str());
1409 45 : if (poHandleHelper == nullptr)
1410 0 : return std::string();
1411 90 : std::string osURL(poHandleHelper->GetURLNoKVP());
1412 45 : delete poHandleHelper;
1413 45 : return osURL;
1414 : }
1415 :
1416 : /************************************************************************/
1417 : /* CreateAzHandleHelper() */
1418 : /************************************************************************/
1419 :
1420 : VSIAzureBlobHandleHelper *
1421 74 : VSIAzureFSHandler::CreateAzHandleHelper(const char *pszURI, bool)
1422 : {
1423 74 : return VSIAzureBlobHandleHelper::BuildFromURI(pszURI,
1424 148 : GetFSPrefix().c_str());
1425 : }
1426 :
1427 : /************************************************************************/
1428 : /* InvalidateRecursive() */
1429 : /************************************************************************/
1430 :
1431 6 : void VSIAzureFSHandler::InvalidateRecursive(const std::string &osDirnameIn)
1432 : {
1433 : // As Azure directories disappear as soon there is no remaining file
1434 : // we may need to invalidate the whole hierarchy
1435 12 : std::string osDirname(osDirnameIn);
1436 16 : while (osDirname.size() > GetFSPrefix().size())
1437 : {
1438 10 : InvalidateDirContent(osDirname.c_str());
1439 10 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1440 10 : osDirname = CPLGetDirname(osDirname.c_str());
1441 : }
1442 6 : }
1443 :
1444 : /************************************************************************/
1445 : /* Unlink() */
1446 : /************************************************************************/
1447 :
1448 4 : int VSIAzureFSHandler::Unlink(const char *pszFilename)
1449 : {
1450 4 : int ret = IVSIS3LikeFSHandler::Unlink(pszFilename);
1451 4 : if (ret != 0)
1452 1 : return ret;
1453 :
1454 3 : InvalidateRecursive(CPLGetDirname(pszFilename));
1455 3 : return 0;
1456 : }
1457 :
1458 : /************************************************************************/
1459 : /* UnlinkBatch() */
1460 : /************************************************************************/
1461 :
1462 3 : int *VSIAzureFSHandler::UnlinkBatch(CSLConstList papszFiles)
1463 : {
1464 : // Implemented using
1465 : // https://learn.microsoft.com/en-us/rest/api/storageservices/blob-batch
1466 : auto poHandleHelper =
1467 6 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper("", true));
1468 :
1469 : int *panRet =
1470 3 : static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
1471 :
1472 3 : const char *pszFirstFilename =
1473 3 : papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
1474 3 : if (!poHandleHelper || pszFirstFilename == nullptr)
1475 0 : return panRet;
1476 :
1477 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1478 6 : NetworkStatisticsAction oContextAction("UnlinkBatch");
1479 :
1480 : // coverity[tainted_data]
1481 3 : double dfRetryDelay = CPLAtof(
1482 : VSIGetPathSpecificOption(pszFirstFilename, "GDAL_HTTP_RETRY_DELAY",
1483 3 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1484 : const int nMaxRetry =
1485 3 : atoi(VSIGetPathSpecificOption(pszFirstFilename, "GDAL_HTTP_MAX_RETRY",
1486 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1487 :
1488 : // For debug / testing only
1489 : const int nBatchSize =
1490 3 : std::max(1, std::min(256, atoi(CPLGetConfigOption(
1491 3 : "CPL_VSIAZ_UNLINK_BATCH_SIZE", "256"))));
1492 6 : std::string osPOSTContent;
1493 :
1494 : const CPLStringList aosHTTPOptions(
1495 3 : CPLHTTPGetOptionsFromEnv(pszFirstFilename));
1496 :
1497 3 : int nFilesInBatch = 0;
1498 3 : int nFirstIDInBatch = 0;
1499 :
1500 5 : const auto DoPOST = [this, panRet, &nFilesInBatch, &dfRetryDelay, nMaxRetry,
1501 : &aosHTTPOptions, &poHandleHelper, &osPOSTContent,
1502 72 : &nFirstIDInBatch](int nLastID)
1503 : {
1504 5 : osPOSTContent += "--batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589--\r\n";
1505 :
1506 : #ifdef DEBUG_VERBOSE
1507 : CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
1508 : #endif
1509 :
1510 : // Run request
1511 5 : int nRetryCount = 0;
1512 : bool bRetry;
1513 5 : std::string osResponse;
1514 5 : do
1515 : {
1516 5 : poHandleHelper->AddQueryParameter("comp", "batch");
1517 :
1518 5 : bRetry = false;
1519 5 : CURL *hCurlHandle = curl_easy_init();
1520 :
1521 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1522 : "POST");
1523 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
1524 : osPOSTContent.c_str());
1525 :
1526 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1527 5 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1528 : aosHTTPOptions.List()));
1529 5 : headers = curl_slist_append(
1530 : headers, "Content-Type: multipart/mixed; "
1531 : "boundary=batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589");
1532 5 : headers = curl_slist_append(
1533 : headers, CPLSPrintf("Content-Length: %d",
1534 5 : static_cast<int>(osPOSTContent.size())));
1535 5 : headers = VSICurlMergeHeaders(
1536 5 : headers, poHandleHelper->GetCurlHeaders("POST", headers));
1537 :
1538 10 : CurlRequestHelper requestHelper;
1539 5 : const long response_code = requestHelper.perform(
1540 : hCurlHandle, headers, this, poHandleHelper.get());
1541 :
1542 5 : NetworkStatisticsLogger::LogPOST(
1543 : osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
1544 :
1545 5 : if (response_code != 202 ||
1546 5 : requestHelper.sWriteFuncData.pBuffer == nullptr)
1547 : {
1548 : // Look if we should attempt a retry
1549 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1550 : static_cast<int>(response_code), dfRetryDelay,
1551 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1552 : requestHelper.szCurlErrBuf);
1553 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1554 : {
1555 0 : CPLError(CE_Warning, CPLE_AppDefined,
1556 : "HTTP error code: %d - %s. "
1557 : "Retrying again in %.1f secs",
1558 : static_cast<int>(response_code),
1559 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
1560 0 : CPLSleep(dfRetryDelay);
1561 0 : dfRetryDelay = dfNewRetryDelay;
1562 0 : nRetryCount++;
1563 0 : bRetry = true;
1564 : }
1565 : else
1566 : {
1567 0 : CPLDebug(GetDebugKey(), "%s",
1568 0 : requestHelper.sWriteFuncData.pBuffer
1569 : ? requestHelper.sWriteFuncData.pBuffer
1570 : : "(null)");
1571 0 : CPLError(CE_Failure, CPLE_AppDefined,
1572 : "DeleteObjects failed");
1573 0 : }
1574 : }
1575 : else
1576 : {
1577 : #ifdef DEBUG_VERBOSE
1578 : CPLDebug(GetDebugKey(), "%s",
1579 : requestHelper.sWriteFuncData.pBuffer);
1580 : #endif
1581 5 : osResponse = requestHelper.sWriteFuncData.pBuffer;
1582 : }
1583 :
1584 5 : curl_easy_cleanup(hCurlHandle);
1585 : } while (bRetry);
1586 :
1587 : // Mark deleted files
1588 14 : for (int j = nFirstIDInBatch; j <= nLastID; j++)
1589 : {
1590 9 : auto nPos = osResponse.find(CPLSPrintf("Content-ID: <%d>", j));
1591 9 : if (nPos != std::string::npos)
1592 : {
1593 7 : nPos = osResponse.find("HTTP/1.1 ", nPos);
1594 7 : if (nPos != std::string::npos)
1595 : {
1596 : const char *pszHTTPCode =
1597 7 : osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
1598 7 : panRet[j] = (atoi(pszHTTPCode) == 202) ? 1 : 0;
1599 : }
1600 : }
1601 : }
1602 :
1603 5 : osPOSTContent.clear();
1604 5 : nFilesInBatch = 0;
1605 5 : nFirstIDInBatch = nLastID;
1606 5 : };
1607 :
1608 10 : for (int i = 0; papszFiles && papszFiles[i]; i++)
1609 : {
1610 7 : CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
1611 :
1612 14 : std::string osAuthorization;
1613 14 : std::string osXMSDate;
1614 : {
1615 : auto poTmpHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
1616 7 : VSIAzureBlobHandleHelper::BuildFromURI(papszFiles[i] +
1617 14 : GetFSPrefix().size(),
1618 21 : GetFSPrefix().c_str()));
1619 : // x-ms-version must not be included in the subrequests...
1620 7 : poTmpHandleHelper->SetIncludeMSVersion(false);
1621 7 : CURL *hCurlHandle = curl_easy_init();
1622 : struct curl_slist *subrequest_headers =
1623 14 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
1624 7 : hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
1625 : aosHTTPOptions.List()));
1626 7 : subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
1627 : "DELETE", subrequest_headers, nullptr, 0);
1628 21 : for (struct curl_slist *iter = subrequest_headers; iter;
1629 14 : iter = iter->next)
1630 : {
1631 14 : if (STARTS_WITH_CI(iter->data, "Authorization: "))
1632 : {
1633 7 : osAuthorization = iter->data;
1634 : }
1635 7 : else if (STARTS_WITH_CI(iter->data, "x-ms-date: "))
1636 : {
1637 7 : osXMSDate = iter->data;
1638 : }
1639 : }
1640 7 : curl_slist_free_all(subrequest_headers);
1641 7 : curl_easy_cleanup(hCurlHandle);
1642 : }
1643 :
1644 14 : std::string osSubrequest;
1645 7 : osSubrequest += "--batch_ec2ce0a7-deaf-11ed-9ad8-3fabe5ecd589\r\n";
1646 7 : osSubrequest += "Content-Type: application/http\r\n";
1647 7 : osSubrequest += CPLSPrintf("Content-ID: <%d>\r\n", i);
1648 7 : osSubrequest += "Content-Transfer-Encoding: binary\r\n";
1649 7 : osSubrequest += "\r\n";
1650 7 : osSubrequest += "DELETE /";
1651 7 : osSubrequest += (papszFiles[i] + GetFSPrefix().size());
1652 7 : osSubrequest += " HTTP/1.1\r\n";
1653 7 : osSubrequest += osXMSDate;
1654 7 : osSubrequest += "\r\n";
1655 7 : osSubrequest += osAuthorization;
1656 7 : osSubrequest += "\r\n";
1657 7 : osSubrequest += "Content-Length: 0\r\n";
1658 7 : osSubrequest += "\r\n";
1659 7 : osSubrequest += "\r\n";
1660 :
1661 : // The size of the body for a batch request can't exceed 4 MB.
1662 : // Add some margin for the end boundary delimiter.
1663 11 : if (i > nFirstIDInBatch &&
1664 4 : osPOSTContent.size() + osSubrequest.size() > 4 * 1024 * 1024 - 100)
1665 : {
1666 1 : DoPOST(i - 1);
1667 : }
1668 :
1669 7 : osPOSTContent += osSubrequest;
1670 7 : nFilesInBatch++;
1671 :
1672 7 : if (nFilesInBatch == nBatchSize || papszFiles[i + 1] == nullptr)
1673 : {
1674 4 : DoPOST(i);
1675 : }
1676 : }
1677 3 : return panRet;
1678 : }
1679 :
1680 : /************************************************************************/
1681 : /* Mkdir() */
1682 : /************************************************************************/
1683 :
1684 5 : int VSIAzureFSHandler::MkdirInternal(const char *pszDirname, long /* nMode */,
1685 : bool bDoStatCheck)
1686 : {
1687 5 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1688 1 : return -1;
1689 :
1690 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1691 8 : NetworkStatisticsAction oContextAction("Mkdir");
1692 :
1693 8 : std::string osDirname(pszDirname);
1694 4 : if (!osDirname.empty() && osDirname.back() != '/')
1695 4 : osDirname += "/";
1696 :
1697 4 : if (bDoStatCheck)
1698 : {
1699 : VSIStatBufL sStat;
1700 5 : if (VSIStatL(osDirname.c_str(), &sStat) == 0 &&
1701 1 : sStat.st_mode == S_IFDIR)
1702 : {
1703 1 : CPLDebug(GetDebugKey(), "Directory %s already exists",
1704 : osDirname.c_str());
1705 1 : errno = EEXIST;
1706 1 : return -1;
1707 : }
1708 : }
1709 :
1710 6 : std::string osDirnameWithoutEndSlash(osDirname);
1711 3 : osDirnameWithoutEndSlash.resize(osDirnameWithoutEndSlash.size() - 1);
1712 9 : if (osDirnameWithoutEndSlash.size() > GetFSPrefix().size() &&
1713 6 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1714 : std::string::npos)
1715 : {
1716 1 : return CreateContainer(osDirnameWithoutEndSlash);
1717 : }
1718 :
1719 2 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1720 2 : InvalidateCachedData(
1721 4 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1722 2 : InvalidateDirContent(CPLGetDirname(osDirnameWithoutEndSlash.c_str()));
1723 :
1724 2 : VSILFILE *fp = VSIFOpenL((osDirname + GDAL_MARKER_FOR_DIR).c_str(), "wb");
1725 2 : if (fp != nullptr)
1726 : {
1727 2 : CPLErrorReset();
1728 2 : VSIFCloseL(fp);
1729 2 : return CPLGetLastErrorType() == CPLE_None ? 0 : -1;
1730 : }
1731 : else
1732 : {
1733 0 : return -1;
1734 : }
1735 : }
1736 :
1737 5 : int VSIAzureFSHandler::Mkdir(const char *pszDirname, long nMode)
1738 : {
1739 5 : return MkdirInternal(pszDirname, nMode, true);
1740 : }
1741 :
1742 : /************************************************************************/
1743 : /* CreateContainer() */
1744 : /************************************************************************/
1745 :
1746 1 : int VSIAzureFSHandler::CreateContainer(const std::string &osDirname)
1747 : {
1748 2 : std::string osDirnameWithoutPrefix = osDirname.substr(GetFSPrefix().size());
1749 : auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1750 2 : CreateHandleHelper(osDirnameWithoutPrefix.c_str(), false));
1751 1 : if (poS3HandleHelper == nullptr)
1752 : {
1753 0 : return -1;
1754 : }
1755 :
1756 1 : int nRet = 0;
1757 :
1758 : bool bRetry;
1759 :
1760 : // coverity[tainted_data]
1761 1 : double dfRetryDelay = CPLAtof(
1762 : VSIGetPathSpecificOption(osDirname.c_str(), "GDAL_HTTP_RETRY_DELAY",
1763 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1764 : const int nMaxRetry =
1765 1 : atoi(VSIGetPathSpecificOption(osDirname.c_str(), "GDAL_HTTP_MAX_RETRY",
1766 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1767 1 : int nRetryCount = 0;
1768 :
1769 : const CPLStringList aosHTTPOptions(
1770 1 : CPLHTTPGetOptionsFromEnv(osDirname.c_str()));
1771 :
1772 1 : do
1773 : {
1774 1 : poS3HandleHelper->AddQueryParameter("restype", "container");
1775 :
1776 1 : bRetry = false;
1777 1 : CURL *hCurlHandle = curl_easy_init();
1778 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1779 :
1780 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1781 1 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
1782 : aosHTTPOptions.List()));
1783 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1784 1 : headers = VSICurlMergeHeaders(
1785 1 : headers, poS3HandleHelper->GetCurlHeaders("PUT", headers));
1786 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1787 :
1788 2 : CurlRequestHelper requestHelper;
1789 1 : const long response_code = requestHelper.perform(
1790 : hCurlHandle, headers, this, poS3HandleHelper.get());
1791 :
1792 1 : NetworkStatisticsLogger::LogPUT(0);
1793 :
1794 1 : if (response_code != 201)
1795 : {
1796 : // Look if we should attempt a retry
1797 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1798 : static_cast<int>(response_code), dfRetryDelay,
1799 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1800 : requestHelper.szCurlErrBuf);
1801 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1802 : {
1803 0 : CPLError(CE_Warning, CPLE_AppDefined,
1804 : "HTTP error code: %d - %s. "
1805 : "Retrying again in %.1f secs",
1806 : static_cast<int>(response_code),
1807 0 : poS3HandleHelper->GetURL().c_str(), dfRetryDelay);
1808 0 : CPLSleep(dfRetryDelay);
1809 0 : dfRetryDelay = dfNewRetryDelay;
1810 0 : nRetryCount++;
1811 0 : bRetry = true;
1812 : }
1813 : else
1814 : {
1815 0 : CPLDebug(GetDebugKey(), "%s",
1816 0 : requestHelper.sWriteFuncData.pBuffer
1817 : ? requestHelper.sWriteFuncData.pBuffer
1818 : : "(null)");
1819 0 : CPLError(CE_Failure, CPLE_AppDefined,
1820 : "Creation of container %s failed", osDirname.c_str());
1821 0 : nRet = -1;
1822 : }
1823 : }
1824 : else
1825 : {
1826 1 : InvalidateCachedData(poS3HandleHelper->GetURLNoKVP().c_str());
1827 1 : InvalidateDirContent(GetFSPrefix().c_str());
1828 : }
1829 :
1830 1 : curl_easy_cleanup(hCurlHandle);
1831 : } while (bRetry);
1832 :
1833 1 : return nRet;
1834 : }
1835 :
1836 : /************************************************************************/
1837 : /* Rmdir() */
1838 : /************************************************************************/
1839 :
1840 9 : int VSIAzureFSHandler::Rmdir(const char *pszDirname)
1841 : {
1842 9 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1843 1 : return -1;
1844 :
1845 16 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1846 16 : NetworkStatisticsAction oContextAction("Rmdir");
1847 :
1848 16 : std::string osDirname(pszDirname);
1849 8 : if (!osDirname.empty() && osDirname.back() != '/')
1850 8 : osDirname += "/";
1851 :
1852 : VSIStatBufL sStat;
1853 8 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1854 : {
1855 2 : InvalidateCachedData(
1856 4 : GetURLFromFilename(osDirname.substr(0, osDirname.size() - 1))
1857 : .c_str());
1858 : // The directory might have not been created by GDAL, and thus lacking
1859 : // the GDAL marker file, so do not turn non-existence as an error
1860 2 : return 0;
1861 : }
1862 6 : else if (sStat.st_mode != S_IFDIR)
1863 : {
1864 0 : CPLDebug(GetDebugKey(), "%s is not a directory", pszDirname);
1865 0 : errno = ENOTDIR;
1866 0 : return -1;
1867 : }
1868 :
1869 6 : char **papszFileList = ReadDirEx(osDirname.c_str(), 1);
1870 6 : bool bEmptyDir =
1871 10 : (papszFileList != nullptr && EQUAL(papszFileList[0], ".") &&
1872 4 : papszFileList[1] == nullptr);
1873 6 : CSLDestroy(papszFileList);
1874 6 : if (!bEmptyDir)
1875 : {
1876 2 : CPLDebug(GetDebugKey(), "%s is not empty", pszDirname);
1877 2 : errno = ENOTEMPTY;
1878 2 : return -1;
1879 : }
1880 :
1881 8 : std::string osDirnameWithoutEndSlash(osDirname);
1882 4 : osDirnameWithoutEndSlash.resize(osDirnameWithoutEndSlash.size() - 1);
1883 12 : if (osDirnameWithoutEndSlash.size() > GetFSPrefix().size() &&
1884 8 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1885 : std::string::npos)
1886 : {
1887 1 : return DeleteContainer(osDirnameWithoutEndSlash);
1888 : }
1889 :
1890 3 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1891 3 : InvalidateCachedData(
1892 6 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1893 3 : InvalidateRecursive(CPLGetDirname(osDirnameWithoutEndSlash.c_str()));
1894 3 : if (osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1895 : std::string::npos)
1896 : {
1897 0 : CPLDebug(GetDebugKey(), "%s is a container", pszDirname);
1898 0 : errno = ENOTDIR;
1899 0 : return -1;
1900 : }
1901 :
1902 3 : if (DeleteObject((osDirname + GDAL_MARKER_FOR_DIR).c_str()) == 0)
1903 3 : return 0;
1904 : // The directory might have not been created by GDAL, and thus lacking the
1905 : // GDAL marker file, so check if is there, and if not, return success.
1906 0 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1907 0 : return 0;
1908 0 : return -1;
1909 : }
1910 :
1911 : /************************************************************************/
1912 : /* DeleteContainer() */
1913 : /************************************************************************/
1914 :
1915 1 : int VSIAzureFSHandler::DeleteContainer(const std::string &osDirname)
1916 : {
1917 2 : std::string osDirnameWithoutPrefix = osDirname.substr(GetFSPrefix().size());
1918 : auto poS3HandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1919 2 : CreateHandleHelper(osDirnameWithoutPrefix.c_str(), false));
1920 1 : if (poS3HandleHelper == nullptr)
1921 : {
1922 0 : return -1;
1923 : }
1924 :
1925 1 : int nRet = 0;
1926 :
1927 : bool bRetry;
1928 :
1929 : // coverity[tainted_data]
1930 1 : double dfRetryDelay = CPLAtof(
1931 : VSIGetPathSpecificOption(osDirname.c_str(), "GDAL_HTTP_RETRY_DELAY",
1932 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1933 : const int nMaxRetry =
1934 1 : atoi(VSIGetPathSpecificOption(osDirname.c_str(), "GDAL_HTTP_MAX_RETRY",
1935 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1936 1 : int nRetryCount = 0;
1937 :
1938 : const CPLStringList aosHTTPOptions(
1939 1 : CPLHTTPGetOptionsFromEnv(osDirname.c_str()));
1940 :
1941 1 : do
1942 : {
1943 1 : poS3HandleHelper->AddQueryParameter("restype", "container");
1944 :
1945 1 : bRetry = false;
1946 1 : CURL *hCurlHandle = curl_easy_init();
1947 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1948 : "DELETE");
1949 :
1950 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1951 1 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
1952 : aosHTTPOptions.List()));
1953 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1954 1 : headers = VSICurlMergeHeaders(
1955 1 : headers, poS3HandleHelper->GetCurlHeaders("DELETE", headers));
1956 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1957 :
1958 2 : CurlRequestHelper requestHelper;
1959 1 : const long response_code = requestHelper.perform(
1960 : hCurlHandle, headers, this, poS3HandleHelper.get());
1961 :
1962 1 : NetworkStatisticsLogger::LogPUT(0);
1963 :
1964 1 : if (response_code != 202)
1965 : {
1966 : // Look if we should attempt a retry
1967 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1968 : static_cast<int>(response_code), dfRetryDelay,
1969 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1970 : requestHelper.szCurlErrBuf);
1971 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1972 : {
1973 0 : CPLError(CE_Warning, CPLE_AppDefined,
1974 : "HTTP error code: %d - %s. "
1975 : "Retrying again in %.1f secs",
1976 : static_cast<int>(response_code),
1977 0 : poS3HandleHelper->GetURL().c_str(), dfRetryDelay);
1978 0 : CPLSleep(dfRetryDelay);
1979 0 : dfRetryDelay = dfNewRetryDelay;
1980 0 : nRetryCount++;
1981 0 : bRetry = true;
1982 : }
1983 : else
1984 : {
1985 0 : CPLDebug(GetDebugKey(), "%s",
1986 0 : requestHelper.sWriteFuncData.pBuffer
1987 : ? requestHelper.sWriteFuncData.pBuffer
1988 : : "(null)");
1989 0 : CPLError(CE_Failure, CPLE_AppDefined,
1990 : "Deletion of container %s failed", osDirname.c_str());
1991 0 : nRet = -1;
1992 : }
1993 : }
1994 : else
1995 : {
1996 1 : InvalidateCachedData(poS3HandleHelper->GetURLNoKVP().c_str());
1997 1 : InvalidateDirContent(GetFSPrefix().c_str());
1998 : }
1999 :
2000 1 : curl_easy_cleanup(hCurlHandle);
2001 : } while (bRetry);
2002 :
2003 1 : return nRet;
2004 : }
2005 :
2006 : /************************************************************************/
2007 : /* CopyFile() */
2008 : /************************************************************************/
2009 :
2010 3 : int VSIAzureFSHandler::CopyFile(const char *pszSource, const char *pszTarget,
2011 : VSILFILE *fpSource, vsi_l_offset nSourceSize,
2012 : CSLConstList papszOptions,
2013 : GDALProgressFunc pProgressFunc,
2014 : void *pProgressData)
2015 : {
2016 6 : const std::string osPrefix(GetFSPrefix());
2017 8 : if ((STARTS_WITH(pszSource, "/vsis3/") ||
2018 2 : STARTS_WITH(pszSource, "/vsigs/") ||
2019 2 : STARTS_WITH(pszSource, "/vsiadls/") ||
2020 6 : STARTS_WITH(pszSource, "/vsicurl/")) &&
2021 1 : STARTS_WITH(pszTarget, osPrefix.c_str()))
2022 : {
2023 2 : std::string osMsg("Copying of");
2024 1 : osMsg += pszSource;
2025 :
2026 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2027 1 : NetworkStatisticsAction oContextAction("CopyFile");
2028 :
2029 1 : bool bRet = CopyObject(pszSource, pszTarget, papszOptions) == 0;
2030 1 : if (bRet && pProgressFunc)
2031 : {
2032 0 : bRet = pProgressFunc(1.0, osMsg.c_str(), pProgressData) != 0;
2033 : }
2034 1 : return bRet ? 0 : -1;
2035 : }
2036 :
2037 2 : return IVSIS3LikeFSHandler::CopyFile(pszSource, pszTarget, fpSource,
2038 : nSourceSize, papszOptions,
2039 2 : pProgressFunc, pProgressData);
2040 : }
2041 :
2042 : /************************************************************************/
2043 : /* CopyObject() */
2044 : /************************************************************************/
2045 :
2046 4 : int VSIAzureFSHandler::CopyObject(const char *oldpath, const char *newpath,
2047 : CSLConstList /* papszMetadata */)
2048 : {
2049 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2050 8 : NetworkStatisticsAction oContextAction("CopyObject");
2051 :
2052 12 : std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
2053 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
2054 8 : CreateAzHandleHelper(osTargetNameWithoutPrefix.c_str(), false));
2055 4 : if (poHandleHelper == nullptr)
2056 : {
2057 0 : return -1;
2058 : }
2059 :
2060 8 : std::string osSourceHeader("x-ms-copy-source: ");
2061 4 : bool bUseSourceSignedURL = true;
2062 4 : if (STARTS_WITH(oldpath, GetFSPrefix().c_str()))
2063 : {
2064 6 : std::string osSourceNameWithoutPrefix = oldpath + GetFSPrefix().size();
2065 : auto poHandleHelperSource = std::unique_ptr<VSIAzureBlobHandleHelper>(
2066 3 : CreateAzHandleHelper(osSourceNameWithoutPrefix.c_str(), false));
2067 3 : if (poHandleHelperSource == nullptr)
2068 : {
2069 0 : return -1;
2070 : }
2071 : // We can use a unsigned source URL only if
2072 : // the source and target are in the same bucket
2073 3 : if (poHandleHelper->GetStorageAccount() ==
2074 9 : poHandleHelperSource->GetStorageAccount() &&
2075 3 : poHandleHelper->GetBucket() == poHandleHelperSource->GetBucket())
2076 : {
2077 2 : bUseSourceSignedURL = false;
2078 2 : osSourceHeader += poHandleHelperSource->GetURLNoKVP();
2079 : }
2080 : }
2081 :
2082 4 : if (bUseSourceSignedURL)
2083 : {
2084 : VSIStatBufL sStat;
2085 : // This has the effect of making sure that the S3 region is correct
2086 : // if copying from /vsis3/
2087 2 : if (VSIStatExL(oldpath, &sStat, VSI_STAT_EXISTS_FLAG) != 0)
2088 : {
2089 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s does not exist", oldpath);
2090 0 : return -1;
2091 : }
2092 :
2093 2 : char *pszSignedURL = VSIGetSignedURL(oldpath, nullptr);
2094 2 : if (!pszSignedURL)
2095 : {
2096 0 : CPLError(CE_Failure, CPLE_AppDefined,
2097 : "Cannot get signed URL for %s", oldpath);
2098 0 : return -1;
2099 : }
2100 2 : osSourceHeader += pszSignedURL;
2101 2 : VSIFree(pszSignedURL);
2102 : }
2103 :
2104 4 : int nRet = 0;
2105 :
2106 : bool bRetry;
2107 :
2108 : // coverity[tainted_data]
2109 4 : double dfRetryDelay = CPLAtof(
2110 : VSIGetPathSpecificOption(oldpath, "GDAL_HTTP_RETRY_DELAY",
2111 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
2112 4 : const int nMaxRetry = atoi(VSIGetPathSpecificOption(
2113 : oldpath, "GDAL_HTTP_MAX_RETRY", CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
2114 4 : int nRetryCount = 0;
2115 :
2116 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
2117 :
2118 4 : do
2119 : {
2120 4 : bRetry = false;
2121 4 : CURL *hCurlHandle = curl_easy_init();
2122 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
2123 :
2124 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2125 4 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
2126 : aosHTTPOptions.List()));
2127 4 : headers = curl_slist_append(headers, osSourceHeader.c_str());
2128 4 : headers = VSICurlSetContentTypeFromExt(headers, newpath);
2129 4 : headers = curl_slist_append(headers, "Content-Length: 0");
2130 4 : headers = VSICurlMergeHeaders(
2131 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
2132 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
2133 :
2134 8 : CurlRequestHelper requestHelper;
2135 4 : const long response_code = requestHelper.perform(
2136 4 : hCurlHandle, headers, this, poHandleHelper.get());
2137 :
2138 4 : NetworkStatisticsLogger::LogPUT(0);
2139 :
2140 4 : if (response_code != 202)
2141 : {
2142 : // Look if we should attempt a retry
2143 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
2144 : static_cast<int>(response_code), dfRetryDelay,
2145 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2146 : requestHelper.szCurlErrBuf);
2147 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
2148 : {
2149 0 : CPLError(CE_Warning, CPLE_AppDefined,
2150 : "HTTP error code: %d - %s. "
2151 : "Retrying again in %.1f secs",
2152 : static_cast<int>(response_code),
2153 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
2154 0 : CPLSleep(dfRetryDelay);
2155 0 : dfRetryDelay = dfNewRetryDelay;
2156 0 : nRetryCount++;
2157 0 : bRetry = true;
2158 : }
2159 : else
2160 : {
2161 0 : CPLDebug(GetDebugKey(), "%s",
2162 0 : requestHelper.sWriteFuncData.pBuffer
2163 : ? requestHelper.sWriteFuncData.pBuffer
2164 : : "(null)");
2165 0 : CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
2166 : oldpath, newpath);
2167 0 : nRet = -1;
2168 : }
2169 : }
2170 : else
2171 : {
2172 4 : InvalidateCachedData(poHandleHelper->GetURLNoKVP().c_str());
2173 :
2174 8 : std::string osFilenameWithoutSlash(newpath);
2175 8 : if (!osFilenameWithoutSlash.empty() &&
2176 4 : osFilenameWithoutSlash.back() == '/')
2177 0 : osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() -
2178 : 1);
2179 :
2180 4 : InvalidateDirContent(CPLGetDirname(osFilenameWithoutSlash.c_str()));
2181 : }
2182 :
2183 4 : curl_easy_cleanup(hCurlHandle);
2184 : } while (bRetry);
2185 :
2186 4 : return nRet;
2187 : }
2188 :
2189 : /************************************************************************/
2190 : /* PutBlock() */
2191 : /************************************************************************/
2192 :
2193 4 : std::string VSIAzureFSHandler::PutBlock(
2194 : const std::string &osFilename, int nPartNumber, const void *pabyBuffer,
2195 : size_t nBufferSize, IVSIS3LikeHandleHelper *poS3HandleHelper, int nMaxRetry,
2196 : double dfRetryDelay, CSLConstList papszOptions)
2197 : {
2198 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2199 8 : NetworkStatisticsFile oContextFile(osFilename.c_str());
2200 8 : NetworkStatisticsAction oContextAction("PutBlock");
2201 :
2202 : bool bRetry;
2203 4 : int nRetryCount = 0;
2204 4 : std::string osBlockId(CPLSPrintf("%012d", nPartNumber));
2205 :
2206 : const std::string osContentLength(
2207 8 : CPLSPrintf("Content-Length: %d", static_cast<int>(nBufferSize)));
2208 :
2209 4 : bool bHasAlreadyHandled409 = false;
2210 :
2211 : const CPLStringList aosHTTPOptions(
2212 8 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
2213 :
2214 5 : do
2215 : {
2216 5 : bRetry = false;
2217 :
2218 5 : poS3HandleHelper->AddQueryParameter("comp", "block");
2219 5 : poS3HandleHelper->AddQueryParameter("blockid", osBlockId);
2220 :
2221 5 : CURL *hCurlHandle = curl_easy_init();
2222 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
2223 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
2224 : PutData::ReadCallBackBuffer);
2225 5 : PutData putData;
2226 5 : putData.pabyData = static_cast<const GByte *>(pabyBuffer);
2227 5 : putData.nOff = 0;
2228 5 : putData.nTotalSize = nBufferSize;
2229 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
2230 5 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
2231 : nBufferSize);
2232 :
2233 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2234 5 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2235 : aosHTTPOptions.List()));
2236 5 : headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
2237 : osFilename.c_str());
2238 5 : headers = curl_slist_append(headers, osContentLength.c_str());
2239 5 : headers = VSICurlMergeHeaders(
2240 : headers, poS3HandleHelper->GetCurlHeaders("PUT", headers,
2241 5 : pabyBuffer, nBufferSize));
2242 :
2243 10 : CurlRequestHelper requestHelper;
2244 : const long response_code =
2245 5 : requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
2246 :
2247 5 : NetworkStatisticsLogger::LogPUT(nBufferSize);
2248 :
2249 5 : if (!bHasAlreadyHandled409 && response_code == 409)
2250 : {
2251 1 : bHasAlreadyHandled409 = true;
2252 1 : CPLDebug(GetDebugKey(), "%s",
2253 1 : requestHelper.sWriteFuncData.pBuffer
2254 : ? requestHelper.sWriteFuncData.pBuffer
2255 : : "(null)");
2256 :
2257 : // The blob type is invalid for this operation
2258 : // Delete the file, and retry
2259 1 : if (DeleteObject(osFilename.c_str()) == 0)
2260 : {
2261 1 : bRetry = true;
2262 : }
2263 : }
2264 4 : else if ((response_code != 200 && response_code != 201) ||
2265 4 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
2266 : {
2267 : // Look if we should attempt a retry
2268 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
2269 : static_cast<int>(response_code), dfRetryDelay,
2270 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2271 : requestHelper.szCurlErrBuf);
2272 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
2273 : {
2274 0 : CPLError(CE_Warning, CPLE_AppDefined,
2275 : "HTTP error code: %d - %s. "
2276 : "Retrying again in %.1f secs",
2277 : static_cast<int>(response_code),
2278 0 : poS3HandleHelper->GetURL().c_str(), dfRetryDelay);
2279 0 : CPLSleep(dfRetryDelay);
2280 0 : dfRetryDelay = dfNewRetryDelay;
2281 0 : nRetryCount++;
2282 0 : bRetry = true;
2283 : }
2284 : else
2285 : {
2286 0 : CPLDebug(GetDebugKey(), "%s",
2287 0 : requestHelper.sWriteFuncData.pBuffer
2288 : ? requestHelper.sWriteFuncData.pBuffer
2289 : : "(null)");
2290 0 : CPLError(CE_Failure, CPLE_AppDefined,
2291 : "PutBlock(%d) of %s failed", nPartNumber,
2292 : osFilename.c_str());
2293 0 : osBlockId.clear();
2294 : }
2295 : }
2296 :
2297 5 : curl_easy_cleanup(hCurlHandle);
2298 : } while (bRetry);
2299 :
2300 8 : return osBlockId;
2301 : }
2302 :
2303 : /************************************************************************/
2304 : /* PutBlockList() */
2305 : /************************************************************************/
2306 :
2307 2 : bool VSIAzureFSHandler::PutBlockList(
2308 : const std::string &osFilename, const std::vector<std::string> &aosBlockIds,
2309 : IVSIS3LikeHandleHelper *poS3HandleHelper, int nMaxRetry,
2310 : double dfRetryDelay)
2311 : {
2312 2 : bool bSuccess = true;
2313 :
2314 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2315 4 : NetworkStatisticsFile oContextFile(osFilename.c_str());
2316 4 : NetworkStatisticsAction oContextAction("PutBlockList");
2317 :
2318 : std::string osXML =
2319 4 : "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<BlockList>\n";
2320 6 : for (const auto &osBlockId : aosBlockIds)
2321 : {
2322 4 : osXML += "<Latest>" + osBlockId + "</Latest>\n";
2323 : }
2324 2 : osXML += "</BlockList>\n";
2325 :
2326 : const std::string osContentLength(
2327 4 : CPLSPrintf("Content-Length: %d", static_cast<int>(osXML.size())));
2328 :
2329 : const CPLStringList aosHTTPOptions(
2330 2 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
2331 :
2332 2 : int nRetryCount = 0;
2333 : bool bRetry;
2334 2 : do
2335 : {
2336 2 : bRetry = false;
2337 :
2338 2 : poS3HandleHelper->AddQueryParameter("comp", "blocklist");
2339 :
2340 2 : PutData putData;
2341 2 : putData.pabyData = reinterpret_cast<const GByte *>(osXML.data());
2342 2 : putData.nOff = 0;
2343 2 : putData.nTotalSize = osXML.size();
2344 :
2345 2 : CURL *hCurlHandle = curl_easy_init();
2346 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
2347 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
2348 : PutData::ReadCallBackBuffer);
2349 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
2350 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
2351 : static_cast<int>(osXML.size()));
2352 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
2353 :
2354 : struct curl_slist *headers = static_cast<struct curl_slist *>(
2355 2 : CPLHTTPSetOptions(hCurlHandle, poS3HandleHelper->GetURL().c_str(),
2356 : aosHTTPOptions.List()));
2357 2 : headers = curl_slist_append(headers, osContentLength.c_str());
2358 2 : headers = VSICurlMergeHeaders(
2359 : headers, poS3HandleHelper->GetCurlHeaders(
2360 2 : "PUT", headers, osXML.c_str(), osXML.size()));
2361 :
2362 4 : CurlRequestHelper requestHelper;
2363 : const long response_code =
2364 2 : requestHelper.perform(hCurlHandle, headers, this, poS3HandleHelper);
2365 :
2366 2 : NetworkStatisticsLogger::LogPUT(osXML.size());
2367 :
2368 2 : if (response_code != 201)
2369 : {
2370 : // Look if we should attempt a retry
2371 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
2372 : static_cast<int>(response_code), dfRetryDelay,
2373 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
2374 : requestHelper.szCurlErrBuf);
2375 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
2376 : {
2377 0 : CPLError(CE_Warning, CPLE_AppDefined,
2378 : "HTTP error code: %d - %s. "
2379 : "Retrying again in %.1f secs",
2380 : static_cast<int>(response_code),
2381 0 : poS3HandleHelper->GetURL().c_str(), dfRetryDelay);
2382 0 : CPLSleep(dfRetryDelay);
2383 0 : dfRetryDelay = dfNewRetryDelay;
2384 0 : nRetryCount++;
2385 0 : bRetry = true;
2386 : }
2387 : else
2388 : {
2389 0 : CPLDebug(GetDebugKey(), "%s",
2390 0 : requestHelper.sWriteFuncData.pBuffer
2391 : ? requestHelper.sWriteFuncData.pBuffer
2392 : : "(null)");
2393 0 : CPLError(CE_Failure, CPLE_AppDefined,
2394 : "PutBlockList of %s failed", osFilename.c_str());
2395 0 : bSuccess = false;
2396 : }
2397 : }
2398 :
2399 2 : curl_easy_cleanup(hCurlHandle);
2400 : } while (bRetry);
2401 :
2402 4 : return bSuccess;
2403 : }
2404 :
2405 : /************************************************************************/
2406 : /* GetFileList() */
2407 : /************************************************************************/
2408 :
2409 18 : char **VSIAzureFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
2410 : bool *pbGotFileList)
2411 : {
2412 18 : return GetFileList(pszDirname, nMaxFiles, true, pbGotFileList);
2413 : }
2414 :
2415 31 : char **VSIAzureFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
2416 : bool bCacheEntries, bool *pbGotFileList)
2417 : {
2418 : if (ENABLE_DEBUG)
2419 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
2420 :
2421 31 : *pbGotFileList = false;
2422 :
2423 : char **papszOptions =
2424 31 : CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
2425 31 : papszOptions = CSLSetNameValue(papszOptions, "CACHE_ENTRIES",
2426 : bCacheEntries ? "YES" : "NO");
2427 31 : auto dir = OpenDir(pszDirname, 0, papszOptions);
2428 31 : CSLDestroy(papszOptions);
2429 31 : if (!dir)
2430 : {
2431 12 : return nullptr;
2432 : }
2433 38 : CPLStringList aosFileList;
2434 : while (true)
2435 : {
2436 27 : auto entry = dir->NextDirEntry();
2437 27 : if (!entry)
2438 : {
2439 15 : break;
2440 : }
2441 12 : aosFileList.AddString(entry->pszName);
2442 :
2443 12 : if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
2444 4 : break;
2445 8 : }
2446 19 : delete dir;
2447 19 : *pbGotFileList = true;
2448 19 : return aosFileList.StealList();
2449 : }
2450 :
2451 : /************************************************************************/
2452 : /* GetOptions() */
2453 : /************************************************************************/
2454 :
2455 2 : const char *VSIAzureFSHandler::GetOptions()
2456 : {
2457 : static std::string osOptions(
2458 2 : std::string("<Options>") +
2459 : " <Option name='AZURE_STORAGE_CONNECTION_STRING' type='string' "
2460 : "description='Connection string that contains account name and "
2461 : "secret key'/>"
2462 : " <Option name='AZURE_STORAGE_ACCOUNT' type='string' "
2463 : "description='Storage account. To use with AZURE_STORAGE_ACCESS_KEY'/>"
2464 : " <Option name='AZURE_STORAGE_ACCESS_KEY' type='string' "
2465 : "description='Secret key'/>"
2466 : " <Option name='AZURE_NO_SIGN_REQUEST' type='boolean' "
2467 : "description='Whether to disable signing of requests' default='NO'/>"
2468 : " <Option name='VSIAZ_CHUNK_SIZE' type='int' "
2469 : "description='Size in MB for chunks of files that are uploaded' "
2470 3 : "default='4' min='1' max='4'/>" +
2471 3 : VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
2472 2 : return osOptions.c_str();
2473 : }
2474 :
2475 : /************************************************************************/
2476 : /* GetSignedURL() */
2477 : /************************************************************************/
2478 :
2479 8 : char *VSIAzureFSHandler::GetSignedURL(const char *pszFilename,
2480 : CSLConstList papszOptions)
2481 : {
2482 8 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2483 0 : return nullptr;
2484 :
2485 : VSIAzureBlobHandleHelper *poHandleHelper =
2486 16 : VSIAzureBlobHandleHelper::BuildFromURI(
2487 24 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str(),
2488 : papszOptions);
2489 8 : if (poHandleHelper == nullptr)
2490 : {
2491 2 : return nullptr;
2492 : }
2493 :
2494 12 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
2495 :
2496 6 : delete poHandleHelper;
2497 6 : return CPLStrdup(osRet.c_str());
2498 : }
2499 :
2500 : /************************************************************************/
2501 : /* OpenDir() */
2502 : /************************************************************************/
2503 :
2504 37 : VSIDIR *VSIAzureFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
2505 : const char *const *papszOptions)
2506 : {
2507 37 : if (nRecurseDepth > 0)
2508 : {
2509 0 : return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
2510 0 : papszOptions);
2511 : }
2512 :
2513 37 : if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
2514 0 : return nullptr;
2515 :
2516 74 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2517 74 : NetworkStatisticsAction oContextAction("OpenDir");
2518 :
2519 111 : std::string osDirnameWithoutPrefix = pszPath + GetFSPrefix().size();
2520 37 : if (!osDirnameWithoutPrefix.empty() && osDirnameWithoutPrefix.back() == '/')
2521 : {
2522 0 : osDirnameWithoutPrefix.resize(osDirnameWithoutPrefix.size() - 1);
2523 : }
2524 :
2525 74 : std::string osBucket(osDirnameWithoutPrefix);
2526 74 : std::string osObjectKey;
2527 37 : size_t nSlashPos = osDirnameWithoutPrefix.find('/');
2528 37 : if (nSlashPos != std::string::npos)
2529 : {
2530 25 : osBucket = osDirnameWithoutPrefix.substr(0, nSlashPos);
2531 25 : osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
2532 : }
2533 :
2534 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
2535 74 : CreateHandleHelper(osBucket.c_str(), true));
2536 37 : if (poHandleHelper == nullptr)
2537 : {
2538 0 : return nullptr;
2539 : }
2540 :
2541 37 : VSIDIRAz *dir = new VSIDIRAz(this);
2542 37 : dir->nRecurseDepth = nRecurseDepth;
2543 37 : dir->poFS = this;
2544 37 : dir->poHandleHelper = std::move(poHandleHelper);
2545 37 : dir->osBucket = std::move(osBucket);
2546 37 : dir->osObjectKey = std::move(osObjectKey);
2547 37 : dir->nMaxFiles = atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
2548 37 : dir->bCacheEntries =
2549 37 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "YES"));
2550 37 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
2551 37 : dir->m_bSynthetizeMissingDirectories = CPLTestBool(CSLFetchNameValueDef(
2552 : papszOptions, "SYNTHETIZE_MISSING_DIRECTORIES", "NO"));
2553 37 : if (!dir->IssueListDir())
2554 : {
2555 13 : delete dir;
2556 13 : return nullptr;
2557 : }
2558 :
2559 24 : return dir;
2560 : }
2561 :
2562 : /************************************************************************/
2563 : /* VSIAzureHandle() */
2564 : /************************************************************************/
2565 :
2566 48 : VSIAzureHandle::VSIAzureHandle(VSIAzureFSHandler *poFSIn,
2567 : const char *pszFilename,
2568 48 : VSIAzureBlobHandleHelper *poHandleHelper)
2569 48 : : VSICurlHandle(poFSIn, pszFilename, poHandleHelper->GetURLNoKVP().c_str()),
2570 96 : m_poHandleHelper(poHandleHelper)
2571 : {
2572 48 : m_osQueryString = poHandleHelper->GetSASQueryString();
2573 48 : }
2574 :
2575 : /************************************************************************/
2576 : /* GetCurlHeaders() */
2577 : /************************************************************************/
2578 :
2579 : struct curl_slist *
2580 38 : VSIAzureHandle::GetCurlHeaders(const std::string &osVerb,
2581 : const struct curl_slist *psExistingHeaders)
2582 : {
2583 38 : return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
2584 : }
2585 :
2586 : /************************************************************************/
2587 : /* IsDirectoryFromExists() */
2588 : /************************************************************************/
2589 :
2590 23 : bool VSIAzureHandle::IsDirectoryFromExists(const char * /*pszVerb*/,
2591 : int response_code)
2592 : {
2593 23 : if (response_code != 404)
2594 10 : return false;
2595 :
2596 26 : std::string osDirname(m_osFilename);
2597 26 : if (osDirname.size() > poFS->GetFSPrefix().size() &&
2598 13 : osDirname.back() == '/')
2599 10 : osDirname.resize(osDirname.size() - 1);
2600 : bool bIsDir;
2601 13 : if (poFS->ExistsInCacheDirList(osDirname, &bIsDir))
2602 0 : return bIsDir;
2603 :
2604 13 : bool bGotFileList = false;
2605 : char **papszDirContent =
2606 13 : reinterpret_cast<VSIAzureFSHandler *>(poFS)->GetFileList(
2607 : osDirname.c_str(), 1, false, &bGotFileList);
2608 13 : CSLDestroy(papszDirContent);
2609 13 : return bGotFileList;
2610 : }
2611 :
2612 : } /* end of namespace cpl */
2613 :
2614 : #endif // DOXYGEN_SKIP
2615 : //! @endcond
2616 :
2617 : /************************************************************************/
2618 : /* VSIInstallAzureFileHandler() */
2619 : /************************************************************************/
2620 :
2621 : /*!
2622 : \brief Install /vsiaz/ Microsoft Azure Blob file system handler
2623 : (requires libcurl)
2624 :
2625 : \verbatim embed:rst
2626 : See :ref:`/vsiaz/ documentation <vsiaz>`
2627 : \endverbatim
2628 :
2629 : @since GDAL 2.3
2630 : */
2631 :
2632 1228 : void VSIInstallAzureFileHandler(void)
2633 : {
2634 1228 : VSIFileManager::InstallHandler("/vsiaz/",
2635 1228 : new cpl::VSIAzureFSHandler("/vsiaz/"));
2636 1228 : }
2637 :
2638 : #endif /* HAVE_CURL */
|