Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for Microsoft Azure Data Lake Storage
5 : *Gen2 Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2020, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_port.h"
30 : #include "cpl_http.h"
31 : #include "cpl_json.h"
32 : #include "cpl_time.h"
33 : #include "cpl_vsil_curl_priv.h"
34 : #include "cpl_vsil_curl_class.h"
35 :
36 : #include <errno.h>
37 :
38 : #include <algorithm>
39 : #include <set>
40 : #include <map>
41 : #include <memory>
42 :
43 : #include "cpl_azure.h"
44 :
45 : #ifndef HAVE_CURL
46 :
47 : void VSIInstallADLSFileHandler(void)
48 : {
49 : // Not supported
50 : }
51 :
52 : #else
53 :
54 : //! @cond Doxygen_Suppress
55 : #ifndef DOXYGEN_SKIP
56 :
57 : #define ENABLE_DEBUG 0
58 :
59 : #define unchecked_curl_easy_setopt(handle, opt, param) \
60 : CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
61 :
62 : namespace cpl
63 : {
64 :
65 : /************************************************************************/
66 : /* GetContinuationToken() */
67 : /************************************************************************/
68 :
69 19 : static std::string GetContinuationToken(const char *pszHeaders)
70 : {
71 19 : std::string osContinuation;
72 19 : if (pszHeaders)
73 : {
74 19 : const char *pszContinuation = strstr(pszHeaders, "x-ms-continuation: ");
75 19 : if (pszContinuation)
76 : {
77 11 : pszContinuation += strlen("x-ms-continuation: ");
78 11 : const char *pszEOL = strstr(pszContinuation, "\r\n");
79 11 : if (pszEOL)
80 : {
81 : osContinuation.assign(pszContinuation,
82 11 : pszEOL - pszContinuation);
83 : }
84 : }
85 : }
86 19 : return osContinuation;
87 : }
88 :
89 : /************************************************************************/
90 : /* RemoveTrailingSlash() */
91 : /************************************************************************/
92 :
93 49 : static std::string RemoveTrailingSlash(const std::string &osFilename)
94 : {
95 49 : std::string osWithoutSlash(osFilename);
96 49 : if (!osWithoutSlash.empty() && osWithoutSlash.back() == '/')
97 2 : osWithoutSlash.resize(osWithoutSlash.size() - 1);
98 49 : return osWithoutSlash;
99 : }
100 :
101 : /************************************************************************/
102 : /* VSIDIRADLS */
103 : /************************************************************************/
104 :
105 : class VSIADLSFSHandler;
106 :
107 : struct VSIDIRADLS : public VSIDIR
108 : {
109 : int m_nRecurseDepth = 0;
110 :
111 : struct Iterator
112 : {
113 : std::string m_osNextMarker{};
114 : std::vector<std::unique_ptr<VSIDIREntry>> m_aoEntries{};
115 : int m_nPos = 0;
116 :
117 17 : void clear()
118 : {
119 17 : m_osNextMarker.clear();
120 17 : m_nPos = 0;
121 17 : m_aoEntries.clear();
122 17 : }
123 : };
124 :
125 : Iterator m_oIterWithinFilesystem{};
126 : Iterator m_oIterFromRoot{};
127 :
128 : // Backup file system listing when doing a recursive OpenDir() from
129 : // the account root
130 : bool m_bRecursiveRequestFromAccountRoot = false;
131 :
132 : std::string m_osFilesystem{};
133 : std::string m_osObjectKey{};
134 : VSIADLSFSHandler *m_poFS = nullptr;
135 : int m_nMaxFiles = 0;
136 : bool m_bCacheEntries = true;
137 : std::string
138 : m_osFilterPrefix{}; // client-side only. No server-side option in
139 :
140 : // https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/list
141 :
142 10 : explicit VSIDIRADLS(VSIADLSFSHandler *poFSIn) : m_poFS(poFSIn)
143 : {
144 10 : }
145 :
146 : VSIDIRADLS(const VSIDIRADLS &) = delete;
147 : VSIDIRADLS &operator=(const VSIDIRADLS &) = delete;
148 :
149 : const VSIDIREntry *NextDirEntry() override;
150 :
151 : bool IssueListDir();
152 : bool AnalysePathList(const std::string &osBaseURL, const char *pszJSON);
153 : bool AnalyseFilesystemList(const std::string &osBaseURL,
154 : const char *pszJSON);
155 : void clear();
156 : };
157 :
158 : /************************************************************************/
159 : /* VSIADLSFSHandler */
160 : /************************************************************************/
161 :
162 : class VSIADLSFSHandler final : public IVSIS3LikeFSHandler
163 : {
164 : CPL_DISALLOW_COPY_ASSIGN(VSIADLSFSHandler)
165 :
166 : protected:
167 : VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
168 : std::string GetURLFromFilename(const std::string &osFilename) override;
169 :
170 : char **GetFileList(const char *pszFilename, int nMaxFiles,
171 : bool *pbGotFileList) override;
172 :
173 : int CopyObject(const char *oldpath, const char *newpath,
174 : CSLConstList papszMetadata) override;
175 : int MkdirInternal(const char *pszDirname, long nMode,
176 : bool bDoStatCheck) override;
177 : int RmdirInternal(const char *pszDirname, bool bRecursive);
178 :
179 : void ClearCache() override;
180 :
181 2 : bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
182 : {
183 2 : return STARTS_WITH(pszHeaderName, "x-ms-");
184 : }
185 :
186 : VSIVirtualHandleUniquePtr
187 : CreateWriteHandle(const char *pszFilename,
188 : CSLConstList papszOptions) override;
189 :
190 : public:
191 1227 : VSIADLSFSHandler() = default;
192 1702 : ~VSIADLSFSHandler() override = default;
193 :
194 648 : std::string GetFSPrefix() const override
195 : {
196 648 : return "/vsiadls/";
197 : }
198 :
199 35 : const char *GetDebugKey() const override
200 : {
201 35 : return "ADLS";
202 : }
203 :
204 : int Rename(const char *oldpath, const char *newpath) override;
205 : int Unlink(const char *pszFilename) override;
206 : int Mkdir(const char *, long) override;
207 : int Rmdir(const char *) override;
208 : int RmdirRecursive(const char *pszDirname) override;
209 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
210 : int nFlags) override;
211 :
212 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
213 : CSLConstList papszOptions) override;
214 :
215 : bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
216 : const char *pszDomain,
217 : CSLConstList papszOptions) override;
218 :
219 : const char *GetOptions() override;
220 :
221 : char *GetSignedURL(const char *pszFilename,
222 : CSLConstList papszOptions) override;
223 :
224 : char **GetFileList(const char *pszFilename, int nMaxFiles,
225 : bool bCacheEntries, bool *pbGotFileList);
226 :
227 : VSIDIR *OpenDir(const char *pszPath, int nRecurseDepth,
228 : const char *const *papszOptions) override;
229 :
230 : enum class Event
231 : {
232 : CREATE_FILE,
233 : APPEND_DATA,
234 : FLUSH
235 : };
236 :
237 : // Block list upload
238 : bool UploadFile(const std::string &osFilename, Event event,
239 : vsi_l_offset nPosition, const void *pabyBuffer,
240 : size_t nBufferSize,
241 : IVSIS3LikeHandleHelper *poS3HandleHelper, int nMaxRetry,
242 : double dfRetryDelay, CSLConstList papszOptions);
243 :
244 : // Multipart upload (mapping of S3 interface)
245 1 : bool SupportsParallelMultipartUpload() const override
246 : {
247 1 : return true;
248 : }
249 :
250 1 : std::string InitiateMultipartUpload(
251 : const std::string &osFilename, IVSIS3LikeHandleHelper *poS3HandleHelper,
252 : int nMaxRetry, double dfRetryDelay, CSLConstList papszOptions) override
253 : {
254 1 : return UploadFile(osFilename, Event::CREATE_FILE, 0, nullptr, 0,
255 : poS3HandleHelper, nMaxRetry, dfRetryDelay,
256 : papszOptions)
257 : ? std::string("dummy")
258 1 : : std::string();
259 : }
260 :
261 2 : std::string UploadPart(const std::string &osFilename, int /* nPartNumber */,
262 : const std::string & /* osUploadID */,
263 : vsi_l_offset nPosition, const void *pabyBuffer,
264 : size_t nBufferSize,
265 : IVSIS3LikeHandleHelper *poS3HandleHelper,
266 : int nMaxRetry, double dfRetryDelay,
267 : CSLConstList /* papszOptions */) override
268 : {
269 2 : return UploadFile(osFilename, Event::APPEND_DATA, nPosition, pabyBuffer,
270 : nBufferSize, poS3HandleHelper, nMaxRetry,
271 : dfRetryDelay, nullptr)
272 : ? std::string("dummy")
273 2 : : std::string();
274 : }
275 :
276 1 : bool CompleteMultipart(const std::string &osFilename,
277 : const std::string & /* osUploadID */,
278 : const std::vector<std::string> & /* aosEtags */,
279 : vsi_l_offset nTotalSize,
280 : IVSIS3LikeHandleHelper *poS3HandleHelper,
281 : int nMaxRetry, double dfRetryDelay) override
282 : {
283 1 : return UploadFile(osFilename, Event::FLUSH, nTotalSize, nullptr, 0,
284 1 : poS3HandleHelper, nMaxRetry, dfRetryDelay, nullptr);
285 : }
286 :
287 0 : bool AbortMultipart(const std::string & /* osFilename */,
288 : const std::string & /* osUploadID */,
289 : IVSIS3LikeHandleHelper * /*poS3HandleHelper */,
290 : int /* nMaxRetry */, double /* dfRetryDelay */) override
291 : {
292 0 : return true;
293 : }
294 :
295 : IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
296 : bool bAllowNoObject) override;
297 :
298 : std::string
299 : GetStreamingFilename(const std::string &osFilename) const override;
300 : };
301 :
302 : /************************************************************************/
303 : /* VSIADLSWriteHandle */
304 : /************************************************************************/
305 :
306 : class VSIADLSWriteHandle final : public VSIAppendWriteHandle
307 : {
308 : CPL_DISALLOW_COPY_ASSIGN(VSIADLSWriteHandle)
309 :
310 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
311 : bool m_bCreated = false;
312 :
313 : bool Send(bool bIsLastBlock) override;
314 :
315 : bool SendInternal(VSIADLSFSHandler::Event event, CSLConstList papszOptions);
316 :
317 : void InvalidateParentDirectory();
318 :
319 : public:
320 : VSIADLSWriteHandle(VSIADLSFSHandler *poFS, const char *pszFilename,
321 : VSIAzureBlobHandleHelper *poHandleHelper);
322 : virtual ~VSIADLSWriteHandle();
323 :
324 : bool CreateFile(CSLConstList papszOptions);
325 : };
326 :
327 : /************************************************************************/
328 : /* clear() */
329 : /************************************************************************/
330 :
331 17 : void VSIDIRADLS::clear()
332 : {
333 17 : if (!m_osFilesystem.empty())
334 12 : m_oIterWithinFilesystem.clear();
335 : else
336 5 : m_oIterFromRoot.clear();
337 17 : }
338 :
339 : /************************************************************************/
340 : /* GetUnixTimeFromRFC822() */
341 : /************************************************************************/
342 :
343 17 : static GIntBig GetUnixTimeFromRFC822(const char *pszRFC822DateTime)
344 : {
345 : int nYear, nMonth, nDay, nHour, nMinute, nSecond;
346 17 : if (CPLParseRFC822DateTime(pszRFC822DateTime, &nYear, &nMonth, &nDay,
347 17 : &nHour, &nMinute, &nSecond, nullptr, nullptr))
348 : {
349 : struct tm brokendowntime;
350 11 : brokendowntime.tm_year = nYear - 1900;
351 11 : brokendowntime.tm_mon = nMonth - 1;
352 11 : brokendowntime.tm_mday = nDay;
353 11 : brokendowntime.tm_hour = nHour;
354 11 : brokendowntime.tm_min = nMinute;
355 11 : brokendowntime.tm_sec = nSecond < 0 ? 0 : nSecond;
356 11 : return CPLYMDHMSToUnixTime(&brokendowntime);
357 : }
358 6 : return GINTBIG_MIN;
359 : }
360 :
361 : /************************************************************************/
362 : /* AnalysePathList() */
363 : /************************************************************************/
364 :
365 11 : bool VSIDIRADLS::AnalysePathList(const std::string &osBaseURL,
366 : const char *pszJSON)
367 : {
368 : #if DEBUG_VERBOSE
369 : CPLDebug(m_poFS->GetDebugKey(), "%s", pszJSON);
370 : #endif
371 :
372 22 : CPLJSONDocument oDoc;
373 11 : if (!oDoc.LoadMemory(pszJSON))
374 0 : return false;
375 :
376 33 : auto oPaths = oDoc.GetRoot().GetArray("paths");
377 11 : if (!oPaths.IsValid())
378 : {
379 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find paths[]");
380 0 : return false;
381 : }
382 :
383 23 : for (const auto &oPath : oPaths)
384 : {
385 12 : m_oIterWithinFilesystem.m_aoEntries.push_back(
386 24 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
387 12 : auto &entry = m_oIterWithinFilesystem.m_aoEntries.back();
388 :
389 : // Returns relative path to the filesystem, so for example
390 : // "mydir/foo.bin" for
391 : // https://{account}.dfs.core.windows.net/{filesystem}/mydir/foo.bin
392 24 : const std::string osName(oPath.GetString("name"));
393 17 : if (!m_osObjectKey.empty() &&
394 17 : STARTS_WITH(osName.c_str(), (m_osObjectKey + "/").c_str()))
395 10 : entry->pszName =
396 5 : CPLStrdup(osName.substr(m_osObjectKey.size() + 1).c_str());
397 7 : else if (m_bRecursiveRequestFromAccountRoot && !m_osFilesystem.empty())
398 3 : entry->pszName = CPLStrdup((m_osFilesystem + '/' + osName).c_str());
399 : else
400 4 : entry->pszName = CPLStrdup(osName.c_str());
401 12 : entry->nSize = static_cast<GUIntBig>(oPath.GetLong("contentLength"));
402 12 : entry->bSizeKnown = true;
403 12 : entry->nMode =
404 24 : oPath.GetString("isDirectory") == "true" ? S_IFDIR : S_IFREG;
405 24 : entry->nMode |=
406 12 : VSICurlParseUnixPermissions(oPath.GetString("permissions").c_str());
407 12 : entry->bModeKnown = true;
408 :
409 24 : std::string ETag = oPath.GetString("etag");
410 12 : if (!ETag.empty())
411 : {
412 0 : entry->papszExtra =
413 0 : CSLSetNameValue(entry->papszExtra, "ETag", ETag.c_str());
414 : }
415 :
416 : const GIntBig nMTime =
417 12 : GetUnixTimeFromRFC822(oPath.GetString("lastModified").c_str());
418 12 : if (nMTime != GINTBIG_MIN)
419 : {
420 11 : entry->nMTime = nMTime;
421 11 : entry->bMTimeKnown = true;
422 : }
423 :
424 12 : if (m_bCacheEntries)
425 : {
426 24 : FileProp prop;
427 12 : prop.eExists = EXIST_YES;
428 12 : prop.bHasComputedFileSize = true;
429 12 : prop.fileSize = entry->nSize;
430 12 : prop.bIsDirectory = CPL_TO_BOOL(VSI_ISDIR(entry->nMode));
431 12 : prop.nMode = entry->nMode;
432 12 : prop.mTime = static_cast<time_t>(entry->nMTime);
433 12 : prop.ETag = std::move(ETag);
434 :
435 : std::string osCachedFilename =
436 36 : osBaseURL + "/" + CPLAWSURLEncode(osName, false);
437 : #if DEBUG_VERBOSE
438 : CPLDebug(m_poFS->GetDebugKey(), "Cache %s",
439 : osCachedFilename.c_str());
440 : #endif
441 12 : m_poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
442 : }
443 :
444 12 : if (m_nMaxFiles > 0 && m_oIterWithinFilesystem.m_aoEntries.size() >
445 0 : static_cast<unsigned>(m_nMaxFiles))
446 : {
447 0 : break;
448 : }
449 : }
450 :
451 11 : return true;
452 : }
453 :
454 : /************************************************************************/
455 : /* AnalysePathList() */
456 : /************************************************************************/
457 :
458 5 : bool VSIDIRADLS::AnalyseFilesystemList(const std::string &osBaseURL,
459 : const char *pszJSON)
460 : {
461 : #if DEBUG_VERBOSE
462 : CPLDebug(m_poFS->GetDebugKey(), "%s", pszJSON);
463 : #endif
464 :
465 10 : CPLJSONDocument oDoc;
466 5 : if (!oDoc.LoadMemory(pszJSON))
467 0 : return false;
468 :
469 15 : auto oPaths = oDoc.GetRoot().GetArray("filesystems");
470 5 : if (!oPaths.IsValid())
471 : {
472 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find filesystems[]");
473 0 : return false;
474 : }
475 :
476 10 : for (const auto &oPath : oPaths)
477 : {
478 5 : m_oIterFromRoot.m_aoEntries.push_back(
479 10 : std::unique_ptr<VSIDIREntry>(new VSIDIREntry()));
480 5 : auto &entry = m_oIterFromRoot.m_aoEntries.back();
481 :
482 10 : const std::string osName(oPath.GetString("name"));
483 5 : entry->pszName = CPLStrdup(osName.c_str());
484 5 : entry->nSize = 0;
485 5 : entry->bSizeKnown = true;
486 5 : entry->nMode = S_IFDIR;
487 5 : entry->bModeKnown = true;
488 :
489 10 : std::string ETag = oPath.GetString("etag");
490 5 : if (!ETag.empty())
491 : {
492 0 : entry->papszExtra =
493 0 : CSLSetNameValue(entry->papszExtra, "ETag", ETag.c_str());
494 : }
495 :
496 : const GIntBig nMTime =
497 5 : GetUnixTimeFromRFC822(oPath.GetString("lastModified").c_str());
498 5 : if (nMTime != GINTBIG_MIN)
499 : {
500 0 : entry->nMTime = nMTime;
501 0 : entry->bMTimeKnown = true;
502 : }
503 :
504 5 : if (m_bCacheEntries)
505 : {
506 10 : FileProp prop;
507 5 : prop.eExists = EXIST_YES;
508 5 : prop.bHasComputedFileSize = true;
509 5 : prop.fileSize = 0;
510 5 : prop.bIsDirectory = true;
511 5 : prop.mTime = static_cast<time_t>(entry->nMTime);
512 5 : prop.ETag = std::move(ETag);
513 :
514 : std::string osCachedFilename =
515 10 : osBaseURL + CPLAWSURLEncode(osName, false);
516 : #if DEBUG_VERBOSE
517 : CPLDebug(m_poFS->GetDebugKey(), "Cache %s",
518 : osCachedFilename.c_str());
519 : #endif
520 5 : m_poFS->SetCachedFileProp(osCachedFilename.c_str(), prop);
521 : }
522 :
523 5 : if (m_nMaxFiles > 0 && m_oIterFromRoot.m_aoEntries.size() >
524 0 : static_cast<unsigned>(m_nMaxFiles))
525 : {
526 0 : break;
527 : }
528 : }
529 :
530 5 : return true;
531 : }
532 :
533 : /************************************************************************/
534 : /* IssueListDir() */
535 : /************************************************************************/
536 :
537 17 : bool VSIDIRADLS::IssueListDir()
538 : {
539 17 : WriteFuncStruct sWriteFuncData;
540 :
541 : auto &oIter =
542 17 : !m_osFilesystem.empty() ? m_oIterWithinFilesystem : m_oIterFromRoot;
543 34 : const std::string l_osNextMarker(oIter.m_osNextMarker);
544 17 : clear();
545 :
546 34 : NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str());
547 34 : NetworkStatisticsAction oContextAction("ListBucket");
548 :
549 34 : CPLString osMaxKeys = CPLGetConfigOption("AZURE_MAX_RESULTS", "");
550 17 : const int AZURE_SERVER_LIMIT_SINGLE_REQUEST = 5000;
551 17 : if (m_nMaxFiles > 0 && m_nMaxFiles < AZURE_SERVER_LIMIT_SINGLE_REQUEST &&
552 0 : (osMaxKeys.empty() || m_nMaxFiles < atoi(osMaxKeys)))
553 : {
554 0 : osMaxKeys.Printf("%d", m_nMaxFiles);
555 : }
556 :
557 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
558 34 : m_poFS->CreateHandleHelper(m_osFilesystem.c_str(), true));
559 17 : if (poHandleHelper == nullptr)
560 : {
561 0 : return false;
562 : }
563 :
564 34 : const std::string osBaseURL(poHandleHelper->GetURLNoKVP());
565 :
566 17 : CURL *hCurlHandle = curl_easy_init();
567 :
568 17 : if (!l_osNextMarker.empty())
569 4 : poHandleHelper->AddQueryParameter("continuation", l_osNextMarker);
570 17 : if (!osMaxKeys.empty())
571 0 : poHandleHelper->AddQueryParameter("maxresults", osMaxKeys);
572 17 : if (!m_osFilesystem.empty())
573 : {
574 12 : poHandleHelper->AddQueryParameter("resource", "filesystem");
575 24 : poHandleHelper->AddQueryParameter(
576 12 : "recursive", m_nRecurseDepth == 0 ? "false" : "true");
577 12 : if (!m_osObjectKey.empty())
578 4 : poHandleHelper->AddQueryParameter("directory", m_osObjectKey);
579 : }
580 : else
581 : {
582 5 : poHandleHelper->AddQueryParameter("resource", "account");
583 : }
584 :
585 34 : std::string osFilename("/vsiadls/");
586 17 : if (!m_osFilesystem.empty())
587 : {
588 12 : osFilename += m_osFilesystem;
589 12 : if (!m_osObjectKey.empty())
590 4 : osFilename += m_osObjectKey;
591 : }
592 : const CPLStringList aosHTTPOptions(
593 34 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
594 :
595 34 : struct curl_slist *headers = VSICurlSetOptions(
596 17 : hCurlHandle, poHandleHelper->GetURL().c_str(), aosHTTPOptions.List());
597 17 : headers = VSICurlMergeHeaders(
598 17 : headers, poHandleHelper->GetCurlHeaders("GET", headers));
599 17 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
600 :
601 17 : CurlRequestHelper requestHelper;
602 17 : const long response_code = requestHelper.perform(
603 17 : hCurlHandle, headers, m_poFS, poHandleHelper.get());
604 :
605 17 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
606 :
607 17 : bool ret = false;
608 17 : if (response_code != 200)
609 : {
610 1 : CPLDebug(m_poFS->GetDebugKey(), "%s",
611 1 : requestHelper.sWriteFuncData.pBuffer
612 : ? requestHelper.sWriteFuncData.pBuffer
613 : : "(null)");
614 : }
615 : else
616 : {
617 16 : if (!m_osFilesystem.empty())
618 : {
619 : // https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/list
620 11 : ret = AnalysePathList(osBaseURL,
621 11 : requestHelper.sWriteFuncData.pBuffer);
622 : }
623 : else
624 : {
625 : // https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/filesystem/list
626 5 : ret = AnalyseFilesystemList(osBaseURL,
627 5 : requestHelper.sWriteFuncData.pBuffer);
628 : }
629 :
630 : // Get continuation token for response headers
631 : oIter.m_osNextMarker =
632 16 : GetContinuationToken(requestHelper.sWriteFuncHeaderData.pBuffer);
633 : }
634 :
635 17 : curl_easy_cleanup(hCurlHandle);
636 17 : return ret;
637 : }
638 :
639 : /************************************************************************/
640 : /* NextDirEntry() */
641 : /************************************************************************/
642 :
643 33 : const VSIDIREntry *VSIDIRADLS::NextDirEntry()
644 : {
645 : while (true)
646 : {
647 : auto &oIter =
648 33 : !m_osFilesystem.empty() ? m_oIterWithinFilesystem : m_oIterFromRoot;
649 33 : if (oIter.m_nPos < static_cast<int>(oIter.m_aoEntries.size()))
650 : {
651 17 : auto &entry = oIter.m_aoEntries[oIter.m_nPos];
652 17 : oIter.m_nPos++;
653 17 : if (m_bRecursiveRequestFromAccountRoot)
654 : {
655 : // If we just read an entry from the account root, it is a
656 : // filesystem name, and we want the next iteration to read
657 : // into it.
658 6 : if (m_osFilesystem.empty())
659 : {
660 3 : m_osFilesystem = entry->pszName;
661 3 : if (!IssueListDir())
662 : {
663 0 : return nullptr;
664 : }
665 : }
666 : }
667 19 : if (!m_osFilterPrefix.empty() &&
668 2 : !STARTS_WITH(entry->pszName, m_osFilterPrefix.c_str()))
669 : {
670 1 : continue;
671 : }
672 16 : return entry.get();
673 : }
674 16 : if (oIter.m_osNextMarker.empty())
675 : {
676 12 : if (m_bRecursiveRequestFromAccountRoot)
677 : {
678 : // If we have no more entries at the filesystem level, go back
679 : // to the root level.
680 4 : if (!m_osFilesystem.empty())
681 : {
682 3 : m_osFilesystem.clear();
683 3 : continue;
684 : }
685 : }
686 9 : return nullptr;
687 : }
688 4 : if (!IssueListDir())
689 : {
690 0 : return nullptr;
691 : }
692 8 : }
693 : }
694 :
695 : /************************************************************************/
696 : /* VSIADLSHandle */
697 : /************************************************************************/
698 :
699 : class VSIADLSHandle final : public VSICurlHandle
700 : {
701 : CPL_DISALLOW_COPY_ASSIGN(VSIADLSHandle)
702 :
703 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
704 :
705 : protected:
706 : virtual struct curl_slist *
707 : GetCurlHeaders(const std::string &osVerb,
708 : const struct curl_slist *psExistingHeaders) override;
709 :
710 : public:
711 : VSIADLSHandle(VSIADLSFSHandler *poFS, const char *pszFilename,
712 : VSIAzureBlobHandleHelper *poHandleHelper);
713 : };
714 :
715 : /************************************************************************/
716 : /* CreateFileHandle() */
717 : /************************************************************************/
718 :
719 25 : VSICurlHandle *VSIADLSFSHandler::CreateFileHandle(const char *pszFilename)
720 : {
721 : VSIAzureBlobHandleHelper *poHandleHelper =
722 50 : VSIAzureBlobHandleHelper::BuildFromURI(
723 75 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
724 25 : if (poHandleHelper == nullptr)
725 0 : return nullptr;
726 25 : return new VSIADLSHandle(this, pszFilename, poHandleHelper);
727 : }
728 :
729 : /************************************************************************/
730 : /* CreateWriteHandle() */
731 : /************************************************************************/
732 :
733 : VSIVirtualHandleUniquePtr
734 7 : VSIADLSFSHandler::CreateWriteHandle(const char *pszFilename,
735 : CSLConstList papszOptions)
736 : {
737 : VSIAzureBlobHandleHelper *poHandleHelper =
738 14 : VSIAzureBlobHandleHelper::BuildFromURI(
739 21 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
740 7 : if (poHandleHelper == nullptr)
741 0 : return nullptr;
742 : auto poHandle =
743 14 : std::make_unique<VSIADLSWriteHandle>(this, pszFilename, poHandleHelper);
744 7 : if (!poHandle->CreateFile(papszOptions))
745 : {
746 1 : return nullptr;
747 : }
748 6 : return VSIVirtualHandleUniquePtr(poHandle.release());
749 : }
750 :
751 : /************************************************************************/
752 : /* Stat() */
753 : /************************************************************************/
754 :
755 27 : int VSIADLSFSHandler::Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
756 : int nFlags)
757 : {
758 27 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
759 0 : return -1;
760 :
761 27 : if ((nFlags & VSI_STAT_CACHE_ONLY) != 0)
762 2 : return VSICurlFilesystemHandlerBase::Stat(pszFilename, pStatBuf,
763 2 : nFlags);
764 :
765 75 : const std::string osFilenameWithoutSlash(RemoveTrailingSlash(pszFilename));
766 :
767 : // Stat("/vsiadls/") ?
768 25 : if (osFilenameWithoutSlash + "/" == GetFSPrefix())
769 : {
770 : // List file systems (stop at the first one), to confirm that the
771 : // account is correct
772 0 : bool bGotFileList = false;
773 0 : CSLDestroy(GetFileList(GetFSPrefix().c_str(), 1, false, &bGotFileList));
774 0 : if (bGotFileList)
775 : {
776 0 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
777 0 : pStatBuf->st_mode = S_IFDIR;
778 0 : return 0;
779 : }
780 0 : return -1;
781 : }
782 :
783 50 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
784 :
785 : // Stat("/vsiadls/filesystem") ?
786 75 : if (osFilenameWithoutSlash.size() > GetFSPrefix().size() &&
787 50 : osFilenameWithoutSlash.substr(GetFSPrefix().size()).find('/') ==
788 : std::string::npos)
789 : {
790 : // Use
791 : // https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/filesystem/getproperties
792 :
793 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
794 6 : NetworkStatisticsAction oContextAction("GetProperties");
795 :
796 : const std::string osFilesystem(
797 6 : osFilenameWithoutSlash.substr(GetFSPrefix().size()));
798 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
799 6 : CreateHandleHelper(osFilesystem.c_str(), true));
800 3 : if (poHandleHelper == nullptr)
801 : {
802 0 : return -1;
803 : }
804 :
805 3 : CURL *hCurlHandle = curl_easy_init();
806 :
807 3 : poHandleHelper->AddQueryParameter("resource", "filesystem");
808 :
809 : struct curl_slist *headers =
810 3 : VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
811 : aosHTTPOptions.List());
812 :
813 3 : headers = VSICurlMergeHeaders(
814 3 : headers, poHandleHelper->GetCurlHeaders("HEAD", headers));
815 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
816 :
817 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 1);
818 :
819 6 : CurlRequestHelper requestHelper;
820 3 : const long response_code = requestHelper.perform(
821 : hCurlHandle, headers, this, poHandleHelper.get());
822 :
823 3 : NetworkStatisticsLogger::LogHEAD();
824 :
825 3 : if (response_code != 200 ||
826 3 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
827 : {
828 0 : curl_easy_cleanup(hCurlHandle);
829 0 : return -1;
830 : }
831 :
832 3 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
833 3 : pStatBuf->st_mode = S_IFDIR;
834 :
835 3 : const char *pszLastModified = strstr(
836 : requestHelper.sWriteFuncHeaderData.pBuffer, "Last-Modified: ");
837 3 : if (pszLastModified)
838 : {
839 0 : pszLastModified += strlen("Last-Modified: ");
840 0 : const char *pszEOL = strstr(pszLastModified, "\r\n");
841 0 : if (pszEOL)
842 : {
843 0 : std::string osLastModified;
844 : osLastModified.assign(pszLastModified,
845 0 : pszEOL - pszLastModified);
846 :
847 : const GIntBig nMTime =
848 0 : GetUnixTimeFromRFC822(osLastModified.c_str());
849 0 : if (nMTime != GINTBIG_MIN)
850 : {
851 0 : pStatBuf->st_mtime = static_cast<time_t>(nMTime);
852 : }
853 : }
854 : }
855 :
856 3 : curl_easy_cleanup(hCurlHandle);
857 :
858 3 : return 0;
859 : }
860 :
861 22 : return VSICurlFilesystemHandlerBase::Stat(osFilenameWithoutSlash.c_str(),
862 22 : pStatBuf, nFlags);
863 : }
864 :
865 : /************************************************************************/
866 : /* GetFileMetadata() */
867 : /************************************************************************/
868 :
869 4 : char **VSIADLSFSHandler::GetFileMetadata(const char *pszFilename,
870 : const char *pszDomain,
871 : CSLConstList papszOptions)
872 : {
873 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
874 0 : return nullptr;
875 :
876 4 : if (pszDomain == nullptr ||
877 4 : (!EQUAL(pszDomain, "STATUS") && !EQUAL(pszDomain, "ACL")))
878 : {
879 1 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
880 1 : pszFilename, pszDomain, papszOptions);
881 : }
882 :
883 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
884 6 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
885 3 : if (poHandleHelper == nullptr)
886 : {
887 0 : return nullptr;
888 : }
889 :
890 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
891 6 : NetworkStatisticsAction oContextAction("GetFileMetadata");
892 :
893 : bool bRetry;
894 : // coverity[tainted_data]
895 3 : double dfRetryDelay = CPLAtof(
896 : VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
897 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
898 : const int nMaxRetry =
899 3 : atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
900 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
901 3 : int nRetryCount = 0;
902 3 : bool bError = true;
903 :
904 6 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
905 :
906 3 : CPLStringList aosMetadata;
907 3 : do
908 : {
909 3 : bRetry = false;
910 3 : CURL *hCurlHandle = curl_easy_init();
911 3 : poHandleHelper->AddQueryParameter("action", EQUAL(pszDomain, "STATUS")
912 : ? "getStatus"
913 : : "getAccessControl");
914 :
915 : struct curl_slist *headers =
916 3 : VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
917 : aosHTTPOptions.List());
918 :
919 3 : headers = VSICurlMergeHeaders(
920 3 : headers, poHandleHelper->GetCurlHeaders("HEAD", headers));
921 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
922 :
923 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 1);
924 :
925 6 : CurlRequestHelper requestHelper;
926 3 : const long response_code = requestHelper.perform(
927 : hCurlHandle, headers, this, poHandleHelper.get());
928 :
929 3 : NetworkStatisticsLogger::LogHEAD();
930 :
931 3 : if (response_code != 200 ||
932 2 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
933 : {
934 : // Look if we should attempt a retry
935 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
936 : static_cast<int>(response_code), dfRetryDelay,
937 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
938 : requestHelper.szCurlErrBuf);
939 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
940 : {
941 0 : CPLError(CE_Warning, CPLE_AppDefined,
942 : "HTTP error code: %d - %s. "
943 : "Retrying again in %.1f secs",
944 : static_cast<int>(response_code),
945 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
946 0 : CPLSleep(dfRetryDelay);
947 0 : dfRetryDelay = dfNewRetryDelay;
948 0 : nRetryCount++;
949 0 : bRetry = true;
950 : }
951 : else
952 : {
953 1 : CPLDebug(GetDebugKey(), "GetFileMetadata failed on %s: %s",
954 : pszFilename,
955 1 : requestHelper.sWriteFuncData.pBuffer
956 : ? requestHelper.sWriteFuncData.pBuffer
957 : : "(null)");
958 1 : }
959 : }
960 : else
961 : {
962 4 : char **papszHeaders = CSLTokenizeString2(
963 2 : requestHelper.sWriteFuncHeaderData.pBuffer, "\r\n", 0);
964 12 : for (int i = 0; papszHeaders[i]; ++i)
965 : {
966 10 : char *pszKey = nullptr;
967 : const char *pszValue =
968 10 : CPLParseNameValue(papszHeaders[i], &pszKey);
969 10 : if (pszKey && pszValue && !EQUAL(pszKey, "Server") &&
970 6 : !EQUAL(pszKey, "Date"))
971 : {
972 4 : aosMetadata.SetNameValue(pszKey, pszValue);
973 : }
974 10 : CPLFree(pszKey);
975 : }
976 2 : CSLDestroy(papszHeaders);
977 2 : bError = false;
978 : }
979 :
980 3 : curl_easy_cleanup(hCurlHandle);
981 : } while (bRetry);
982 3 : return bError ? nullptr : CSLDuplicate(aosMetadata.List());
983 : }
984 :
985 : /************************************************************************/
986 : /* SetFileMetadata() */
987 : /************************************************************************/
988 :
989 4 : bool VSIADLSFSHandler::SetFileMetadata(const char *pszFilename,
990 : CSLConstList papszMetadata,
991 : const char *pszDomain,
992 : CSLConstList papszOptions)
993 : {
994 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
995 0 : return false;
996 :
997 4 : if (pszDomain == nullptr ||
998 4 : !(EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "ACL")))
999 : {
1000 0 : CPLError(CE_Failure, CPLE_NotSupported,
1001 : "Only PROPERTIES and ACL domain are supported");
1002 0 : return false;
1003 : }
1004 :
1005 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1006 8 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
1007 4 : if (poHandleHelper == nullptr)
1008 : {
1009 0 : return false;
1010 : }
1011 :
1012 : const bool bRecursive =
1013 4 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "RECURSIVE", "FALSE"));
1014 4 : const char *pszMode = CSLFetchNameValue(papszOptions, "MODE");
1015 4 : if (!EQUAL(pszDomain, "PROPERTIES") && bRecursive && pszMode == nullptr)
1016 : {
1017 0 : CPLError(CE_Failure, CPLE_AppDefined,
1018 : "For setAccessControlRecursive, the MODE option should be set "
1019 : "to: 'set', 'modify' or 'remove'");
1020 0 : return false;
1021 : }
1022 :
1023 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1024 8 : NetworkStatisticsAction oContextAction("SetFileMetadata");
1025 :
1026 : bool bRetry;
1027 : // coverity[tainted_data]
1028 4 : double dfRetryDelay = CPLAtof(
1029 : VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
1030 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1031 : const int nMaxRetry =
1032 4 : atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
1033 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1034 4 : int nRetryCount = 0;
1035 :
1036 4 : bool bRet = false;
1037 :
1038 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
1039 :
1040 4 : do
1041 : {
1042 4 : bRetry = false;
1043 4 : CURL *hCurlHandle = curl_easy_init();
1044 8 : poHandleHelper->AddQueryParameter(
1045 4 : "action", EQUAL(pszDomain, "PROPERTIES") ? "setProperties"
1046 2 : : bRecursive ? "setAccessControlRecursive"
1047 : : "setAccessControl");
1048 4 : if (pszMode)
1049 : {
1050 2 : poHandleHelper->AddQueryParameter("mode",
1051 2 : CPLString(pszMode).tolower());
1052 : }
1053 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
1054 :
1055 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1056 4 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1057 : aosHTTPOptions.List()));
1058 :
1059 8 : CPLStringList aosList;
1060 8 : for (CSLConstList papszIter = papszMetadata; papszIter && *papszIter;
1061 : ++papszIter)
1062 : {
1063 4 : char *pszKey = nullptr;
1064 4 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
1065 4 : if (pszKey && pszValue)
1066 : {
1067 4 : if ((EQUAL(pszDomain, "PROPERTIES") &&
1068 2 : (EQUAL(pszKey, "x-ms-lease-id") ||
1069 2 : EQUAL(pszKey, "x-ms-cache-control") ||
1070 2 : EQUAL(pszKey, "x-ms-content-type") ||
1071 2 : EQUAL(pszKey, "x-ms-content-disposition") ||
1072 2 : EQUAL(pszKey, "x-ms-content-encoding") ||
1073 2 : EQUAL(pszKey, "x-ms-content-language") ||
1074 2 : EQUAL(pszKey, "x-ms-content-md5") ||
1075 2 : EQUAL(pszKey, "x-ms-properties") ||
1076 0 : EQUAL(pszKey, "x-ms-client-request-id") ||
1077 0 : STARTS_WITH_CI(pszKey, "If-"))) ||
1078 2 : (!EQUAL(pszDomain, "PROPERTIES") && !bRecursive &&
1079 1 : (EQUAL(pszKey, "x-ms-lease-id") ||
1080 1 : EQUAL(pszKey, "x-ms-owner") ||
1081 1 : EQUAL(pszKey, "x-ms-group") ||
1082 1 : EQUAL(pszKey, "x-ms-permissions") ||
1083 1 : EQUAL(pszKey, "x-ms-acl") ||
1084 0 : EQUAL(pszKey, "x-ms-client-request-id") ||
1085 0 : STARTS_WITH_CI(pszKey, "If-"))) ||
1086 1 : (!EQUAL(pszDomain, "PROPERTIES") && bRecursive &&
1087 1 : (EQUAL(pszKey, "x-ms-lease-id") ||
1088 1 : EQUAL(pszKey, "x-ms-acl") ||
1089 0 : EQUAL(pszKey, "x-ms-client-request-id") ||
1090 0 : STARTS_WITH_CI(pszKey, "If-"))))
1091 : {
1092 : const char *pszHeader =
1093 4 : CPLSPrintf("%s: %s", pszKey, pszValue);
1094 4 : aosList.AddString(pszHeader);
1095 4 : headers = curl_slist_append(headers, pszHeader);
1096 : }
1097 : else
1098 : {
1099 0 : CPLDebug(GetDebugKey(), "Ignorizing metadata item %s",
1100 : *papszIter);
1101 : }
1102 : }
1103 4 : CPLFree(pszKey);
1104 : }
1105 :
1106 4 : headers = VSICurlMergeHeaders(
1107 4 : headers, poHandleHelper->GetCurlHeaders("PATCH", headers));
1108 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1109 :
1110 4 : NetworkStatisticsLogger::LogPUT(0);
1111 :
1112 8 : CurlRequestHelper requestHelper;
1113 4 : const long response_code = requestHelper.perform(
1114 : hCurlHandle, headers, this, poHandleHelper.get());
1115 :
1116 4 : if (response_code != 200 && response_code != 202)
1117 : {
1118 : // Look if we should attempt a retry
1119 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1120 : static_cast<int>(response_code), dfRetryDelay,
1121 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1122 : requestHelper.szCurlErrBuf);
1123 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1124 : {
1125 0 : CPLError(CE_Warning, CPLE_AppDefined,
1126 : "HTTP error code: %d - %s. "
1127 : "Retrying again in %.1f secs",
1128 : static_cast<int>(response_code),
1129 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
1130 0 : CPLSleep(dfRetryDelay);
1131 0 : dfRetryDelay = dfNewRetryDelay;
1132 0 : nRetryCount++;
1133 0 : bRetry = true;
1134 : }
1135 : else
1136 : {
1137 1 : CPLDebug(GetDebugKey(), "SetFileMetadata on %s failed: %s",
1138 : pszFilename,
1139 1 : requestHelper.sWriteFuncData.pBuffer
1140 : ? requestHelper.sWriteFuncData.pBuffer
1141 : : "(null)");
1142 1 : }
1143 : }
1144 : else
1145 : {
1146 3 : bRet = true;
1147 : }
1148 :
1149 4 : curl_easy_cleanup(hCurlHandle);
1150 : } while (bRetry);
1151 4 : return bRet;
1152 : }
1153 :
1154 : /************************************************************************/
1155 : /* VSIADLSWriteHandle() */
1156 : /************************************************************************/
1157 :
1158 7 : VSIADLSWriteHandle::VSIADLSWriteHandle(VSIADLSFSHandler *poFS,
1159 : const char *pszFilename,
1160 7 : VSIAzureBlobHandleHelper *poHandleHelper)
1161 7 : : VSIAppendWriteHandle(poFS, poFS->GetFSPrefix().c_str(), pszFilename,
1162 : GetAzureBufferSize()),
1163 14 : m_poHandleHelper(poHandleHelper)
1164 : {
1165 7 : }
1166 :
1167 : /************************************************************************/
1168 : /* ~VSIADLSWriteHandle() */
1169 : /************************************************************************/
1170 :
1171 14 : VSIADLSWriteHandle::~VSIADLSWriteHandle()
1172 : {
1173 7 : Close();
1174 14 : }
1175 :
1176 : /************************************************************************/
1177 : /* InvalidateParentDirectory() */
1178 : /************************************************************************/
1179 :
1180 6 : void VSIADLSWriteHandle::InvalidateParentDirectory()
1181 : {
1182 6 : m_poFS->InvalidateCachedData(m_poHandleHelper->GetURLNoKVP().c_str());
1183 :
1184 12 : const std::string osFilenameWithoutSlash(RemoveTrailingSlash(m_osFilename));
1185 6 : m_poFS->InvalidateDirContent(CPLGetDirname(osFilenameWithoutSlash.c_str()));
1186 6 : }
1187 :
1188 : /************************************************************************/
1189 : /* CreateFile() */
1190 : /************************************************************************/
1191 :
1192 7 : bool VSIADLSWriteHandle::CreateFile(CSLConstList papszOptions)
1193 : {
1194 7 : m_bCreated =
1195 7 : SendInternal(VSIADLSFSHandler::Event::CREATE_FILE, papszOptions);
1196 7 : return m_bCreated;
1197 : }
1198 :
1199 : /************************************************************************/
1200 : /* Send() */
1201 : /************************************************************************/
1202 :
1203 9 : bool VSIADLSWriteHandle::Send(bool bIsLastBlock)
1204 : {
1205 9 : if (!m_bCreated)
1206 1 : return false;
1207 : // If we have a non-empty buffer, append it
1208 15 : if (m_nBufferOff != 0 &&
1209 7 : !SendInternal(VSIADLSFSHandler::Event::APPEND_DATA, nullptr))
1210 1 : return false;
1211 : // If we are the last block, send the flush event
1212 7 : if (bIsLastBlock && !SendInternal(VSIADLSFSHandler::Event::FLUSH, nullptr))
1213 1 : return false;
1214 :
1215 6 : InvalidateParentDirectory();
1216 :
1217 6 : return true;
1218 : }
1219 :
1220 : /************************************************************************/
1221 : /* SendInternal() */
1222 : /************************************************************************/
1223 :
1224 19 : bool VSIADLSWriteHandle::SendInternal(VSIADLSFSHandler::Event event,
1225 : CSLConstList papszOptions)
1226 : {
1227 : // coverity[tainted_data]
1228 19 : double dfRetryDelay = CPLAtof(
1229 : VSIGetPathSpecificOption(m_osFilename.c_str(), "GDAL_HTTP_RETRY_DELAY",
1230 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1231 19 : const int nMaxRetry = atoi(
1232 : VSIGetPathSpecificOption(m_osFilename.c_str(), "GDAL_HTTP_MAX_RETRY",
1233 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1234 :
1235 38 : return cpl::down_cast<VSIADLSFSHandler *>(m_poFS)->UploadFile(
1236 19 : m_osFilename, event,
1237 : event == VSIADLSFSHandler::Event::CREATE_FILE ? 0
1238 : : event == VSIADLSFSHandler::Event::APPEND_DATA
1239 12 : ? m_nCurOffset - m_nBufferOff
1240 : : m_nCurOffset,
1241 19 : m_pabyBuffer, m_nBufferOff, m_poHandleHelper.get(), nMaxRetry,
1242 19 : dfRetryDelay, papszOptions);
1243 : }
1244 :
1245 : /************************************************************************/
1246 : /* ClearCache() */
1247 : /************************************************************************/
1248 :
1249 246 : void VSIADLSFSHandler::ClearCache()
1250 : {
1251 246 : IVSIS3LikeFSHandler::ClearCache();
1252 :
1253 246 : VSIAzureBlobHandleHelper::ClearCache();
1254 246 : }
1255 :
1256 : /************************************************************************/
1257 : /* GetURLFromFilename() */
1258 : /************************************************************************/
1259 :
1260 17 : std::string VSIADLSFSHandler::GetURLFromFilename(const std::string &osFilename)
1261 : {
1262 : std::string osFilenameWithoutPrefix =
1263 34 : osFilename.substr(GetFSPrefix().size());
1264 : VSIAzureBlobHandleHelper *poHandleHelper =
1265 17 : VSIAzureBlobHandleHelper::BuildFromURI(osFilenameWithoutPrefix.c_str(),
1266 34 : GetFSPrefix().c_str());
1267 17 : if (poHandleHelper == nullptr)
1268 0 : return std::string();
1269 34 : std::string osURL(poHandleHelper->GetURLNoKVP());
1270 17 : delete poHandleHelper;
1271 17 : return osURL;
1272 : }
1273 :
1274 : /************************************************************************/
1275 : /* CreateHandleHelper() */
1276 : /************************************************************************/
1277 :
1278 37 : IVSIS3LikeHandleHelper *VSIADLSFSHandler::CreateHandleHelper(const char *pszURI,
1279 : bool)
1280 : {
1281 37 : return VSIAzureBlobHandleHelper::BuildFromURI(pszURI,
1282 74 : GetFSPrefix().c_str());
1283 : }
1284 :
1285 : /************************************************************************/
1286 : /* Rename() */
1287 : /************************************************************************/
1288 :
1289 1 : int VSIADLSFSHandler::Rename(const char *oldpath, const char *newpath)
1290 : {
1291 1 : if (!STARTS_WITH_CI(oldpath, GetFSPrefix().c_str()))
1292 0 : return -1;
1293 1 : if (!STARTS_WITH_CI(newpath, GetFSPrefix().c_str()))
1294 0 : return -1;
1295 :
1296 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1297 2 : NetworkStatisticsAction oContextAction("Rename");
1298 :
1299 : VSIStatBufL sStat;
1300 1 : if (VSIStatL(oldpath, &sStat) != 0)
1301 : {
1302 0 : CPLDebug(GetDebugKey(), "%s is not a object", oldpath);
1303 0 : errno = ENOENT;
1304 0 : return -1;
1305 : }
1306 :
1307 : // POSIX says renaming on the same file is OK
1308 1 : if (strcmp(oldpath, newpath) == 0)
1309 0 : return 0;
1310 :
1311 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1312 2 : CreateHandleHelper(newpath + GetFSPrefix().size(), false));
1313 1 : if (poHandleHelper == nullptr)
1314 : {
1315 0 : return -1;
1316 : }
1317 :
1318 2 : std::string osContinuation;
1319 1 : int nRet = 0;
1320 : bool bRetry;
1321 :
1322 : // coverity[tainted_data]
1323 1 : double dfRetryDelay = CPLAtof(
1324 : VSIGetPathSpecificOption(oldpath, "GDAL_HTTP_RETRY_DELAY",
1325 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1326 1 : const int nMaxRetry = atoi(VSIGetPathSpecificOption(
1327 : oldpath, "GDAL_HTTP_MAX_RETRY", CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1328 1 : int nRetryCount = 0;
1329 :
1330 1 : InvalidateCachedData(GetURLFromFilename(oldpath).c_str());
1331 1 : InvalidateCachedData(GetURLFromFilename(newpath).c_str());
1332 1 : InvalidateDirContent(CPLGetDirname(oldpath));
1333 :
1334 1 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
1335 :
1336 1 : do
1337 : {
1338 1 : bRetry = false;
1339 :
1340 1 : CURL *hCurlHandle = curl_easy_init();
1341 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1342 :
1343 1 : poHandleHelper->ResetQueryParameters();
1344 1 : if (!osContinuation.empty())
1345 0 : poHandleHelper->AddQueryParameter("continuation", osContinuation);
1346 :
1347 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1348 1 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1349 : aosHTTPOptions.List()));
1350 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1351 2 : std::string osRenameSource("x-ms-rename-source: /");
1352 : osRenameSource +=
1353 1 : CPLAWSURLEncode(oldpath + GetFSPrefix().size(), false);
1354 1 : headers = curl_slist_append(headers, osRenameSource.c_str());
1355 1 : headers = VSICurlMergeHeaders(
1356 1 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
1357 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1358 :
1359 2 : CurlRequestHelper requestHelper;
1360 1 : const long response_code = requestHelper.perform(
1361 : hCurlHandle, headers, this, poHandleHelper.get());
1362 :
1363 1 : NetworkStatisticsLogger::LogPUT(0);
1364 :
1365 1 : if (response_code != 201)
1366 : {
1367 : // Look if we should attempt a retry
1368 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1369 : static_cast<int>(response_code), dfRetryDelay,
1370 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1371 : requestHelper.szCurlErrBuf);
1372 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1373 : {
1374 0 : CPLError(CE_Warning, CPLE_AppDefined,
1375 : "HTTP error code: %d - %s. "
1376 : "Retrying again in %.1f secs",
1377 : static_cast<int>(response_code),
1378 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
1379 0 : CPLSleep(dfRetryDelay);
1380 0 : dfRetryDelay = dfNewRetryDelay;
1381 0 : nRetryCount++;
1382 0 : bRetry = true;
1383 : }
1384 : else
1385 : {
1386 0 : CPLDebug(GetDebugKey(), "Renaming of %s failed: %s", oldpath,
1387 0 : requestHelper.sWriteFuncData.pBuffer
1388 : ? requestHelper.sWriteFuncData.pBuffer
1389 : : "(null)");
1390 0 : nRet = -1;
1391 : }
1392 : }
1393 : else
1394 : {
1395 : // Get continuation token for response headers
1396 1 : osContinuation = GetContinuationToken(
1397 1 : requestHelper.sWriteFuncHeaderData.pBuffer);
1398 1 : if (!osContinuation.empty())
1399 : {
1400 0 : nRetryCount = 0;
1401 0 : bRetry = true;
1402 : }
1403 : }
1404 :
1405 1 : curl_easy_cleanup(hCurlHandle);
1406 : } while (bRetry);
1407 :
1408 1 : return nRet;
1409 : }
1410 :
1411 : /************************************************************************/
1412 : /* Unlink() */
1413 : /************************************************************************/
1414 :
1415 2 : int VSIADLSFSHandler::Unlink(const char *pszFilename)
1416 : {
1417 2 : return IVSIS3LikeFSHandler::Unlink(pszFilename);
1418 : }
1419 :
1420 : /************************************************************************/
1421 : /* Mkdir() */
1422 : /************************************************************************/
1423 :
1424 3 : int VSIADLSFSHandler::MkdirInternal(const char *pszDirname, long nMode,
1425 : bool bDoStatCheck)
1426 : {
1427 3 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1428 1 : return -1;
1429 :
1430 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1431 4 : NetworkStatisticsAction oContextAction("Mkdir");
1432 :
1433 4 : const std::string osDirname(pszDirname);
1434 :
1435 2 : if (bDoStatCheck)
1436 : {
1437 : VSIStatBufL sStat;
1438 2 : if (VSIStatL(osDirname.c_str(), &sStat) == 0)
1439 : {
1440 1 : CPLDebug(GetDebugKey(), "Directory or file %s already exists",
1441 : osDirname.c_str());
1442 1 : errno = EEXIST;
1443 1 : return -1;
1444 : }
1445 : }
1446 :
1447 2 : const std::string osDirnameWithoutEndSlash(RemoveTrailingSlash(osDirname));
1448 : auto poHandleHelper =
1449 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
1450 2 : osDirnameWithoutEndSlash.c_str() + GetFSPrefix().size(), false));
1451 1 : if (poHandleHelper == nullptr)
1452 : {
1453 0 : return -1;
1454 : }
1455 :
1456 1 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1457 1 : InvalidateCachedData(
1458 2 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1459 1 : InvalidateDirContent(CPLGetDirname(osDirnameWithoutEndSlash.c_str()));
1460 :
1461 1 : int nRet = 0;
1462 :
1463 : bool bRetry;
1464 :
1465 : // coverity[tainted_data]
1466 1 : double dfRetryDelay = CPLAtof(
1467 : VSIGetPathSpecificOption(pszDirname, "GDAL_HTTP_RETRY_DELAY",
1468 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1469 : const int nMaxRetry =
1470 1 : atoi(VSIGetPathSpecificOption(pszDirname, "GDAL_HTTP_MAX_RETRY",
1471 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1472 1 : int nRetryCount = 0;
1473 :
1474 1 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszDirname));
1475 :
1476 1 : do
1477 : {
1478 1 : bRetry = false;
1479 1 : CURL *hCurlHandle = curl_easy_init();
1480 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1481 :
1482 1 : poHandleHelper->ResetQueryParameters();
1483 2 : poHandleHelper->AddQueryParameter(
1484 1 : "resource", osDirnameWithoutEndSlash.find(
1485 2 : '/', GetFSPrefix().size()) == std::string::npos
1486 : ? "filesystem"
1487 : : "directory");
1488 :
1489 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1490 1 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1491 : aosHTTPOptions.List()));
1492 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1493 2 : CPLString osPermissions; // keep in this scope
1494 1 : if ((nMode & 0777) != 0)
1495 : {
1496 : osPermissions.Printf("x-ms-permissions: 0%03o",
1497 0 : static_cast<int>(nMode));
1498 0 : headers = curl_slist_append(headers, osPermissions.c_str());
1499 : }
1500 1 : if (bDoStatCheck)
1501 : {
1502 1 : headers = curl_slist_append(headers, "If-None-Match: \"*\"");
1503 : }
1504 :
1505 1 : headers = VSICurlMergeHeaders(
1506 1 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
1507 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1508 :
1509 2 : CurlRequestHelper requestHelper;
1510 1 : const long response_code = requestHelper.perform(
1511 : hCurlHandle, headers, this, poHandleHelper.get());
1512 :
1513 1 : NetworkStatisticsLogger::LogPUT(0);
1514 :
1515 1 : if (response_code != 201)
1516 : {
1517 : // Look if we should attempt a retry
1518 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1519 : static_cast<int>(response_code), dfRetryDelay,
1520 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1521 : requestHelper.szCurlErrBuf);
1522 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1523 : {
1524 0 : CPLError(CE_Warning, CPLE_AppDefined,
1525 : "HTTP error code: %d - %s. "
1526 : "Retrying again in %.1f secs",
1527 : static_cast<int>(response_code),
1528 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
1529 0 : CPLSleep(dfRetryDelay);
1530 0 : dfRetryDelay = dfNewRetryDelay;
1531 0 : nRetryCount++;
1532 0 : bRetry = true;
1533 : }
1534 : else
1535 : {
1536 0 : CPLDebug(GetDebugKey(), "Creation of %s failed: %s",
1537 : osDirname.c_str(),
1538 0 : requestHelper.sWriteFuncData.pBuffer
1539 : ? requestHelper.sWriteFuncData.pBuffer
1540 : : "(null)");
1541 0 : nRet = -1;
1542 : }
1543 : }
1544 :
1545 1 : curl_easy_cleanup(hCurlHandle);
1546 : } while (bRetry);
1547 :
1548 1 : return nRet;
1549 : }
1550 :
1551 3 : int VSIADLSFSHandler::Mkdir(const char *pszDirname, long nMode)
1552 : {
1553 3 : return MkdirInternal(pszDirname, nMode, true);
1554 : }
1555 :
1556 : /************************************************************************/
1557 : /* RmdirInternal() */
1558 : /************************************************************************/
1559 :
1560 4 : int VSIADLSFSHandler::RmdirInternal(const char *pszDirname, bool bRecursive)
1561 : {
1562 8 : const std::string osDirname(pszDirname);
1563 : const std::string osDirnameWithoutEndSlash(
1564 12 : RemoveTrailingSlash(osDirname.c_str()));
1565 :
1566 : const bool bIsFileSystem =
1567 4 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1568 4 : std::string::npos;
1569 :
1570 4 : if (!bRecursive && bIsFileSystem)
1571 : {
1572 : // List content, to confirm it is empty first, as filesystem deletion
1573 : // is recursive by default.
1574 0 : bool bGotFileList = false;
1575 0 : CSLDestroy(GetFileList(osDirnameWithoutEndSlash.c_str(), 1, false,
1576 : &bGotFileList));
1577 0 : if (bGotFileList)
1578 : {
1579 0 : CPLDebug(GetDebugKey(), "Cannot delete filesystem with "
1580 : "non-recursive method as it is not empty");
1581 0 : errno = ENOTEMPTY;
1582 0 : return -1;
1583 : }
1584 : }
1585 :
1586 4 : if (!bIsFileSystem)
1587 : {
1588 : VSIStatBufL sStat;
1589 4 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1590 : {
1591 1 : CPLDebug(GetDebugKey(), "Object %s does not exist",
1592 : osDirname.c_str());
1593 1 : errno = ENOENT;
1594 2 : return -1;
1595 : }
1596 3 : if (!VSI_ISDIR(sStat.st_mode))
1597 : {
1598 1 : CPLDebug(GetDebugKey(), "Object %s is not a directory",
1599 : osDirname.c_str());
1600 1 : errno = ENOTDIR;
1601 1 : return -1;
1602 : }
1603 : }
1604 :
1605 : auto poHandleHelper =
1606 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
1607 4 : osDirnameWithoutEndSlash.c_str() + GetFSPrefix().size(), false));
1608 2 : if (poHandleHelper == nullptr)
1609 : {
1610 0 : return -1;
1611 : }
1612 :
1613 2 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1614 2 : InvalidateCachedData(
1615 4 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1616 2 : InvalidateDirContent(CPLGetDirname(osDirnameWithoutEndSlash.c_str()));
1617 2 : if (bRecursive)
1618 : {
1619 1 : PartialClearCache(osDirnameWithoutEndSlash.c_str());
1620 : }
1621 :
1622 4 : std::string osContinuation;
1623 2 : int nRet = 0;
1624 : bool bRetry;
1625 :
1626 : // coverity[tainted_data]
1627 2 : double dfRetryDelay = CPLAtof(
1628 : VSIGetPathSpecificOption(pszDirname, "GDAL_HTTP_RETRY_DELAY",
1629 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1630 : const int nMaxRetry =
1631 2 : atoi(VSIGetPathSpecificOption(pszDirname, "GDAL_HTTP_MAX_RETRY",
1632 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1633 2 : int nRetryCount = 0;
1634 :
1635 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszDirname));
1636 :
1637 2 : do
1638 : {
1639 2 : bRetry = false;
1640 2 : CURL *hCurlHandle = curl_easy_init();
1641 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1642 : "DELETE");
1643 :
1644 2 : poHandleHelper->ResetQueryParameters();
1645 2 : if (bIsFileSystem)
1646 : {
1647 0 : poHandleHelper->AddQueryParameter("resource", "filesystem");
1648 : }
1649 : else
1650 : {
1651 2 : poHandleHelper->AddQueryParameter("recursive",
1652 : bRecursive ? "true" : "false");
1653 2 : if (!osContinuation.empty())
1654 0 : poHandleHelper->AddQueryParameter("continuation",
1655 : osContinuation);
1656 : }
1657 :
1658 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1659 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1660 : aosHTTPOptions.List()));
1661 2 : headers = VSICurlMergeHeaders(
1662 2 : headers, poHandleHelper->GetCurlHeaders("DELETE", headers));
1663 :
1664 4 : CurlRequestHelper requestHelper;
1665 2 : const long response_code = requestHelper.perform(
1666 : hCurlHandle, headers, this, poHandleHelper.get());
1667 :
1668 2 : NetworkStatisticsLogger::LogDELETE();
1669 :
1670 : // 200 for path deletion
1671 : // 202 for filesystem deletion
1672 2 : if (response_code != 200 && response_code != 202)
1673 : {
1674 : // Look if we should attempt a retry
1675 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1676 : static_cast<int>(response_code), dfRetryDelay,
1677 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1678 : requestHelper.szCurlErrBuf);
1679 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1680 : {
1681 0 : CPLError(CE_Warning, CPLE_AppDefined,
1682 : "HTTP error code: %d - %s. "
1683 : "Retrying again in %.1f secs",
1684 : static_cast<int>(response_code),
1685 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
1686 0 : CPLSleep(dfRetryDelay);
1687 0 : dfRetryDelay = dfNewRetryDelay;
1688 0 : nRetryCount++;
1689 0 : bRetry = true;
1690 : }
1691 : else
1692 : {
1693 0 : CPLDebug(GetDebugKey(), "Delete of %s failed: %s",
1694 : osDirname.c_str(),
1695 0 : requestHelper.sWriteFuncData.pBuffer
1696 : ? requestHelper.sWriteFuncData.pBuffer
1697 : : "(null)");
1698 0 : if (requestHelper.sWriteFuncData.pBuffer != nullptr)
1699 : {
1700 0 : VSIError(VSIE_AWSError, "%s",
1701 : requestHelper.sWriteFuncData.pBuffer);
1702 0 : if (strstr(requestHelper.sWriteFuncData.pBuffer,
1703 : "PathNotFound"))
1704 : {
1705 0 : errno = ENOENT;
1706 : }
1707 0 : else if (strstr(requestHelper.sWriteFuncData.pBuffer,
1708 : "DirectoryNotEmpty"))
1709 : {
1710 0 : errno = ENOTEMPTY;
1711 : }
1712 : }
1713 0 : nRet = -1;
1714 0 : }
1715 : }
1716 : else
1717 : {
1718 : // Get continuation token for response headers
1719 2 : osContinuation = GetContinuationToken(
1720 2 : requestHelper.sWriteFuncHeaderData.pBuffer);
1721 2 : if (!osContinuation.empty())
1722 : {
1723 0 : nRetryCount = 0;
1724 0 : bRetry = true;
1725 : }
1726 : }
1727 :
1728 2 : curl_easy_cleanup(hCurlHandle);
1729 : } while (bRetry);
1730 :
1731 2 : return nRet;
1732 : }
1733 :
1734 : /************************************************************************/
1735 : /* Rmdir() */
1736 : /************************************************************************/
1737 :
1738 4 : int VSIADLSFSHandler::Rmdir(const char *pszDirname)
1739 : {
1740 4 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1741 1 : return -1;
1742 :
1743 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1744 6 : NetworkStatisticsAction oContextAction("Rmdir");
1745 :
1746 3 : return RmdirInternal(pszDirname, false);
1747 : }
1748 :
1749 : /************************************************************************/
1750 : /* RmdirRecursive() */
1751 : /************************************************************************/
1752 :
1753 1 : int VSIADLSFSHandler::RmdirRecursive(const char *pszDirname)
1754 : {
1755 1 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1756 0 : return -1;
1757 :
1758 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1759 2 : NetworkStatisticsAction oContextAction("RmdirRecursive");
1760 :
1761 1 : return RmdirInternal(pszDirname, true);
1762 : }
1763 :
1764 : /************************************************************************/
1765 : /* CopyObject() */
1766 : /************************************************************************/
1767 :
1768 4 : int VSIADLSFSHandler::CopyObject(const char *oldpath, const char *newpath,
1769 : CSLConstList /* papszMetadata */)
1770 : {
1771 : // There is no CopyObject in ADLS... So use the base Azure blob one...
1772 :
1773 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1774 8 : NetworkStatisticsAction oContextAction("CopyObject");
1775 :
1776 12 : std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
1777 : auto poAzHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1778 4 : VSIAzureBlobHandleHelper::BuildFromURI(
1779 8 : osTargetNameWithoutPrefix.c_str(), "/vsiaz/"));
1780 4 : if (poAzHandleHelper == nullptr)
1781 : {
1782 0 : return -1;
1783 : }
1784 :
1785 12 : std::string osSourceNameWithoutPrefix = oldpath + GetFSPrefix().size();
1786 : auto poAzHandleHelperSource = std::unique_ptr<IVSIS3LikeHandleHelper>(
1787 4 : VSIAzureBlobHandleHelper::BuildFromURI(
1788 8 : osSourceNameWithoutPrefix.c_str(), "/vsiaz/"));
1789 4 : if (poAzHandleHelperSource == nullptr)
1790 : {
1791 0 : return -1;
1792 : }
1793 :
1794 8 : std::string osSourceHeader("x-ms-copy-source: ");
1795 4 : osSourceHeader += poAzHandleHelperSource->GetURLNoKVP();
1796 :
1797 4 : int nRet = 0;
1798 :
1799 : bool bRetry;
1800 :
1801 : // coverity[tainted_data]
1802 4 : double dfRetryDelay = CPLAtof(
1803 : VSIGetPathSpecificOption(oldpath, "GDAL_HTTP_RETRY_DELAY",
1804 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
1805 4 : const int nMaxRetry = atoi(VSIGetPathSpecificOption(
1806 : oldpath, "GDAL_HTTP_MAX_RETRY", CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
1807 4 : int nRetryCount = 0;
1808 :
1809 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
1810 :
1811 4 : do
1812 : {
1813 4 : bRetry = false;
1814 4 : CURL *hCurlHandle = curl_easy_init();
1815 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1816 :
1817 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1818 4 : CPLHTTPSetOptions(hCurlHandle, poAzHandleHelper->GetURL().c_str(),
1819 : aosHTTPOptions.List()));
1820 4 : headers = curl_slist_append(headers, osSourceHeader.c_str());
1821 4 : headers = curl_slist_append(headers, "Content-Length: 0");
1822 4 : headers = VSICurlSetContentTypeFromExt(headers, newpath);
1823 4 : headers = VSICurlMergeHeaders(
1824 4 : headers, poAzHandleHelper->GetCurlHeaders("PUT", headers));
1825 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1826 :
1827 8 : CurlRequestHelper requestHelper;
1828 4 : const long response_code = requestHelper.perform(
1829 : hCurlHandle, headers, this, poAzHandleHelper.get());
1830 :
1831 4 : NetworkStatisticsLogger::LogPUT(0);
1832 :
1833 4 : if (response_code != 202)
1834 : {
1835 : // Look if we should attempt a retry
1836 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1837 : static_cast<int>(response_code), dfRetryDelay,
1838 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1839 : requestHelper.szCurlErrBuf);
1840 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1841 : {
1842 0 : CPLError(CE_Warning, CPLE_AppDefined,
1843 : "HTTP error code: %d - %s. "
1844 : "Retrying again in %.1f secs",
1845 : static_cast<int>(response_code),
1846 0 : poAzHandleHelper->GetURL().c_str(), dfRetryDelay);
1847 0 : CPLSleep(dfRetryDelay);
1848 0 : dfRetryDelay = dfNewRetryDelay;
1849 0 : nRetryCount++;
1850 0 : bRetry = true;
1851 : }
1852 : else
1853 : {
1854 1 : CPLDebug(GetDebugKey(), "%s",
1855 1 : requestHelper.sWriteFuncData.pBuffer
1856 : ? requestHelper.sWriteFuncData.pBuffer
1857 : : "(null)");
1858 1 : CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
1859 : oldpath, newpath);
1860 1 : nRet = -1;
1861 : }
1862 : }
1863 : else
1864 : {
1865 : auto poADLSHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1866 3 : VSIAzureBlobHandleHelper::BuildFromURI(
1867 9 : osTargetNameWithoutPrefix.c_str(), GetFSPrefix().c_str()));
1868 3 : if (poADLSHandleHelper != nullptr)
1869 3 : InvalidateCachedData(poADLSHandleHelper->GetURLNoKVP().c_str());
1870 :
1871 : const std::string osFilenameWithoutSlash(
1872 9 : RemoveTrailingSlash(newpath));
1873 3 : InvalidateDirContent(CPLGetDirname(osFilenameWithoutSlash.c_str()));
1874 : }
1875 :
1876 4 : curl_easy_cleanup(hCurlHandle);
1877 : } while (bRetry);
1878 :
1879 4 : return nRet;
1880 : }
1881 :
1882 : /************************************************************************/
1883 : /* UploadFile() */
1884 : /************************************************************************/
1885 :
1886 23 : bool VSIADLSFSHandler::UploadFile(const std::string &osFilename, Event event,
1887 : vsi_l_offset nPosition,
1888 : const void *pabyBuffer, size_t nBufferSize,
1889 : IVSIS3LikeHandleHelper *poHandleHelper,
1890 : int nMaxRetry, double dfRetryDelay,
1891 : CSLConstList papszOptions)
1892 : {
1893 46 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1894 46 : NetworkStatisticsFile oContextFile(osFilename.c_str());
1895 46 : NetworkStatisticsAction oContextAction("UploadFile");
1896 :
1897 23 : if (event == Event::CREATE_FILE)
1898 : {
1899 8 : InvalidateCachedData(poHandleHelper->GetURLNoKVP().c_str());
1900 8 : InvalidateDirContent(CPLGetDirname(osFilename.c_str()));
1901 : }
1902 :
1903 : const CPLStringList aosHTTPOptions(
1904 23 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
1905 :
1906 23 : bool bSuccess = true;
1907 23 : int nRetryCount = 0;
1908 : bool bRetry;
1909 23 : do
1910 : {
1911 23 : bRetry = false;
1912 :
1913 23 : CURL *hCurlHandle = curl_easy_init();
1914 :
1915 23 : poHandleHelper->ResetQueryParameters();
1916 23 : if (event == Event::CREATE_FILE)
1917 : {
1918 8 : poHandleHelper->AddQueryParameter("resource", "file");
1919 : }
1920 15 : else if (event == Event::APPEND_DATA)
1921 : {
1922 9 : poHandleHelper->AddQueryParameter("action", "append");
1923 9 : poHandleHelper->AddQueryParameter(
1924 : "position",
1925 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nPosition)));
1926 : }
1927 : else
1928 : {
1929 6 : poHandleHelper->AddQueryParameter("action", "flush");
1930 6 : poHandleHelper->AddQueryParameter("close", "true");
1931 6 : poHandleHelper->AddQueryParameter(
1932 : "position",
1933 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nPosition)));
1934 : }
1935 :
1936 23 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
1937 23 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
1938 : PutData::ReadCallBackBuffer);
1939 23 : PutData putData;
1940 23 : putData.pabyData = static_cast<const GByte *>(pabyBuffer);
1941 23 : putData.nOff = 0;
1942 23 : putData.nTotalSize = nBufferSize;
1943 23 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
1944 :
1945 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1946 23 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1947 : aosHTTPOptions.List()));
1948 23 : headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
1949 : osFilename.c_str());
1950 :
1951 46 : CPLString osContentLength; // leave it in this scope
1952 :
1953 23 : if (event == Event::APPEND_DATA)
1954 : {
1955 9 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1956 : static_cast<int>(nBufferSize));
1957 : // Disable "Expect: 100-continue" which doesn't hurt, but is not
1958 : // needed
1959 9 : headers = curl_slist_append(headers, "Expect:");
1960 : osContentLength.Printf("Content-Length: %d",
1961 9 : static_cast<int>(nBufferSize));
1962 9 : headers = curl_slist_append(headers, osContentLength.c_str());
1963 : }
1964 : else
1965 : {
1966 14 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
1967 14 : headers = curl_slist_append(headers, "Content-Length: 0");
1968 : }
1969 :
1970 23 : const char *pszVerb = (event == Event::CREATE_FILE) ? "PUT" : "PATCH";
1971 23 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, pszVerb);
1972 23 : headers = VSICurlMergeHeaders(
1973 23 : headers, poHandleHelper->GetCurlHeaders(pszVerb, headers));
1974 23 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1975 :
1976 46 : CurlRequestHelper requestHelper;
1977 : const long response_code =
1978 23 : requestHelper.perform(hCurlHandle, headers, this, poHandleHelper);
1979 :
1980 23 : NetworkStatisticsLogger::LogPUT(
1981 : event == Event::APPEND_DATA ? nBufferSize : 0);
1982 :
1983 : // 200 for PATCH flush
1984 : // 201 for PUT create
1985 : // 202 for PATCH append
1986 23 : if (response_code != 200 && response_code != 201 &&
1987 : response_code != 202)
1988 : {
1989 : // Look if we should attempt a retry
1990 6 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1991 : static_cast<int>(response_code), dfRetryDelay,
1992 3 : requestHelper.sWriteFuncHeaderData.pBuffer,
1993 : requestHelper.szCurlErrBuf);
1994 3 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
1995 : {
1996 0 : CPLError(CE_Warning, CPLE_AppDefined,
1997 : "HTTP error code: %d - %s. "
1998 : "Retrying again in %.1f secs",
1999 : static_cast<int>(response_code),
2000 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
2001 0 : CPLSleep(dfRetryDelay);
2002 0 : dfRetryDelay = dfNewRetryDelay;
2003 0 : nRetryCount++;
2004 0 : bRetry = true;
2005 : }
2006 : else
2007 : {
2008 3 : CPLDebug(GetDebugKey(), "%s of %s failed: %s", pszVerb,
2009 : osFilename.c_str(),
2010 3 : requestHelper.sWriteFuncData.pBuffer
2011 : ? requestHelper.sWriteFuncData.pBuffer
2012 : : "(null)");
2013 3 : bSuccess = false;
2014 : }
2015 : }
2016 :
2017 23 : curl_easy_cleanup(hCurlHandle);
2018 : } while (bRetry);
2019 :
2020 46 : return bSuccess;
2021 : }
2022 :
2023 : /************************************************************************/
2024 : /* GetFileList() */
2025 : /************************************************************************/
2026 :
2027 4 : char **VSIADLSFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
2028 : bool *pbGotFileList)
2029 : {
2030 4 : return GetFileList(pszDirname, nMaxFiles, true, pbGotFileList);
2031 : }
2032 :
2033 4 : char **VSIADLSFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
2034 : bool bCacheEntries, bool *pbGotFileList)
2035 : {
2036 : if (ENABLE_DEBUG)
2037 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
2038 :
2039 4 : *pbGotFileList = false;
2040 :
2041 : char **papszOptions =
2042 4 : CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
2043 4 : papszOptions = CSLSetNameValue(papszOptions, "CACHE_ENTRIES",
2044 : bCacheEntries ? "YES" : "NO");
2045 4 : auto dir = OpenDir(pszDirname, 0, papszOptions);
2046 4 : CSLDestroy(papszOptions);
2047 4 : if (!dir)
2048 : {
2049 1 : return nullptr;
2050 : }
2051 6 : CPLStringList aosFileList;
2052 : while (true)
2053 : {
2054 8 : auto entry = dir->NextDirEntry();
2055 8 : if (!entry)
2056 : {
2057 3 : break;
2058 : }
2059 5 : aosFileList.AddString(entry->pszName);
2060 :
2061 5 : if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
2062 0 : break;
2063 5 : }
2064 3 : delete dir;
2065 3 : *pbGotFileList = true;
2066 3 : return aosFileList.StealList();
2067 : }
2068 :
2069 : /************************************************************************/
2070 : /* GetOptions() */
2071 : /************************************************************************/
2072 :
2073 1 : const char *VSIADLSFSHandler::GetOptions()
2074 : {
2075 : static std::string osOptions(
2076 2 : std::string("<Options>") +
2077 : " <Option name='AZURE_STORAGE_CONNECTION_STRING' type='string' "
2078 : "description='Connection string that contains account name and "
2079 : "secret key'/>"
2080 : " <Option name='AZURE_STORAGE_ACCOUNT' type='string' "
2081 : "description='Storage account. To use with AZURE_STORAGE_ACCESS_KEY'/>"
2082 : " <Option name='AZURE_STORAGE_ACCESS_KEY' type='string' "
2083 : "description='Secret key'/>"
2084 : " <Option name='VSIAZ_CHUNK_SIZE' type='int' "
2085 : "description='Size in MB for chunks of files that are uploaded' "
2086 3 : "default='4' min='1' max='4'/>" +
2087 2 : VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
2088 1 : return osOptions.c_str();
2089 : }
2090 :
2091 : /************************************************************************/
2092 : /* GetSignedURL() */
2093 : /************************************************************************/
2094 :
2095 2 : char *VSIADLSFSHandler::GetSignedURL(const char *pszFilename,
2096 : CSLConstList papszOptions)
2097 : {
2098 2 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2099 0 : return nullptr;
2100 :
2101 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
2102 : VSIAzureBlobHandleHelper::BuildFromURI(pszFilename +
2103 2 : GetFSPrefix().size(),
2104 : "/vsiaz/", // use Azure blob
2105 4 : papszOptions));
2106 2 : if (poHandleHelper == nullptr)
2107 : {
2108 1 : return nullptr;
2109 : }
2110 :
2111 2 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
2112 :
2113 1 : return CPLStrdup(osRet.c_str());
2114 : }
2115 :
2116 : /************************************************************************/
2117 : /* OpenDir() */
2118 : /************************************************************************/
2119 :
2120 10 : VSIDIR *VSIADLSFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
2121 : const char *const *papszOptions)
2122 : {
2123 10 : if (nRecurseDepth > 0)
2124 : {
2125 0 : return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
2126 0 : papszOptions);
2127 : }
2128 :
2129 10 : if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
2130 0 : return nullptr;
2131 :
2132 20 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2133 20 : NetworkStatisticsAction oContextAction("OpenDir");
2134 :
2135 : const std::string osDirnameWithoutPrefix =
2136 30 : RemoveTrailingSlash(pszPath + GetFSPrefix().size());
2137 20 : std::string osFilesystem(osDirnameWithoutPrefix);
2138 20 : std::string osObjectKey;
2139 10 : size_t nSlashPos = osDirnameWithoutPrefix.find('/');
2140 10 : if (nSlashPos != std::string::npos)
2141 : {
2142 3 : osFilesystem = osDirnameWithoutPrefix.substr(0, nSlashPos);
2143 3 : osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
2144 : }
2145 :
2146 10 : VSIDIRADLS *dir = new VSIDIRADLS(this);
2147 10 : dir->m_nRecurseDepth = nRecurseDepth;
2148 10 : dir->m_poFS = this;
2149 10 : dir->m_bRecursiveRequestFromAccountRoot =
2150 10 : osFilesystem.empty() && nRecurseDepth < 0;
2151 10 : dir->m_osFilesystem = std::move(osFilesystem);
2152 10 : dir->m_osObjectKey = std::move(osObjectKey);
2153 10 : dir->m_nMaxFiles =
2154 10 : atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
2155 10 : dir->m_bCacheEntries =
2156 10 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "YES"));
2157 10 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
2158 10 : if (!dir->IssueListDir())
2159 : {
2160 1 : delete dir;
2161 1 : return nullptr;
2162 : }
2163 :
2164 9 : return dir;
2165 : }
2166 :
2167 : /************************************************************************/
2168 : /* GetStreamingFilename() */
2169 : /************************************************************************/
2170 :
2171 : std::string
2172 0 : VSIADLSFSHandler::GetStreamingFilename(const std::string &osFilename) const
2173 : {
2174 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
2175 0 : return "/vsiaz_streaming/" + osFilename.substr(GetFSPrefix().size());
2176 0 : return osFilename;
2177 : }
2178 :
2179 : /************************************************************************/
2180 : /* VSIADLSHandle() */
2181 : /************************************************************************/
2182 :
2183 25 : VSIADLSHandle::VSIADLSHandle(VSIADLSFSHandler *poFSIn, const char *pszFilename,
2184 25 : VSIAzureBlobHandleHelper *poHandleHelper)
2185 25 : : VSICurlHandle(poFSIn, pszFilename, poHandleHelper->GetURLNoKVP().c_str()),
2186 50 : m_poHandleHelper(poHandleHelper)
2187 : {
2188 25 : m_osQueryString = poHandleHelper->GetSASQueryString();
2189 25 : }
2190 :
2191 : /************************************************************************/
2192 : /* GetCurlHeaders() */
2193 : /************************************************************************/
2194 :
2195 : struct curl_slist *
2196 21 : VSIADLSHandle::GetCurlHeaders(const std::string &osVerb,
2197 : const struct curl_slist *psExistingHeaders)
2198 : {
2199 21 : return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
2200 : }
2201 :
2202 : } /* end of namespace cpl */
2203 :
2204 : #endif // DOXYGEN_SKIP
2205 : //! @endcond
2206 :
2207 : /************************************************************************/
2208 : /* VSIInstallADLSFileHandler() */
2209 : /************************************************************************/
2210 :
2211 : /*!
2212 : \brief Install /vsiaz/ Microsoft Azure Data Lake Storage Gen2 file system
2213 : handler (requires libcurl)
2214 :
2215 : \verbatim embed:rst
2216 : See :ref:`/vsiadls/ documentation <vsiadls>`
2217 : \endverbatim
2218 :
2219 : @since GDAL 3.3
2220 : */
2221 :
2222 1227 : void VSIInstallADLSFileHandler(void)
2223 : {
2224 1227 : VSIFileManager::InstallHandler("/vsiadls/", new cpl::VSIADLSFSHandler);
2225 1227 : }
2226 :
2227 : #endif /* HAVE_CURL */
|