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