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