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