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