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