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