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 1691 : VSIADLSFSHandler() = default;
177 2244 : ~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 = poHandleHelper->GetCurlHeaders("GET", headers);
620 17 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
621 :
622 17 : CurlRequestHelper requestHelper;
623 17 : const long response_code = requestHelper.perform(
624 17 : hCurlHandle, headers, m_poFS, poHandleHelper.get());
625 :
626 17 : NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
627 :
628 17 : bool ret = false;
629 17 : if (response_code != 200)
630 : {
631 1 : CPLDebug(m_poFS->GetDebugKey(), "%s",
632 1 : requestHelper.sWriteFuncData.pBuffer
633 : ? requestHelper.sWriteFuncData.pBuffer
634 : : "(null)");
635 : }
636 : else
637 : {
638 16 : if (!m_osFilesystem.empty())
639 : {
640 : // https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/list
641 11 : ret = AnalysePathList(osBaseURL,
642 11 : requestHelper.sWriteFuncData.pBuffer);
643 : }
644 : else
645 : {
646 : // https://docs.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/filesystem/list
647 5 : ret = AnalyseFilesystemList(osBaseURL,
648 5 : requestHelper.sWriteFuncData.pBuffer);
649 : }
650 :
651 : // Get continuation token for response headers
652 : oIter.m_osNextMarker =
653 16 : GetContinuationToken(requestHelper.sWriteFuncHeaderData.pBuffer);
654 : }
655 :
656 17 : curl_easy_cleanup(hCurlHandle);
657 17 : return ret;
658 : }
659 :
660 : /************************************************************************/
661 : /* NextDirEntry() */
662 : /************************************************************************/
663 :
664 33 : const VSIDIREntry *VSIDIRADLS::NextDirEntry()
665 : {
666 : while (true)
667 : {
668 : auto &oIter =
669 33 : !m_osFilesystem.empty() ? m_oIterWithinFilesystem : m_oIterFromRoot;
670 33 : if (oIter.m_nPos < static_cast<int>(oIter.m_aoEntries.size()))
671 : {
672 17 : auto &entry = oIter.m_aoEntries[oIter.m_nPos];
673 17 : oIter.m_nPos++;
674 17 : if (m_bRecursiveRequestFromAccountRoot)
675 : {
676 : // If we just read an entry from the account root, it is a
677 : // filesystem name, and we want the next iteration to read
678 : // into it.
679 6 : if (m_osFilesystem.empty())
680 : {
681 3 : m_osFilesystem = entry->pszName;
682 3 : if (!IssueListDir())
683 : {
684 0 : return nullptr;
685 : }
686 : }
687 : }
688 19 : if (!m_osFilterPrefix.empty() &&
689 2 : !STARTS_WITH(entry->pszName, m_osFilterPrefix.c_str()))
690 : {
691 1 : continue;
692 : }
693 16 : return entry.get();
694 : }
695 16 : if (oIter.m_osNextMarker.empty())
696 : {
697 12 : if (m_bRecursiveRequestFromAccountRoot)
698 : {
699 : // If we have no more entries at the filesystem level, go back
700 : // to the root level.
701 4 : if (!m_osFilesystem.empty())
702 : {
703 3 : m_osFilesystem.clear();
704 3 : continue;
705 : }
706 : }
707 9 : return nullptr;
708 : }
709 4 : if (!IssueListDir())
710 : {
711 0 : return nullptr;
712 : }
713 8 : }
714 : }
715 :
716 : /************************************************************************/
717 : /* VSIADLSHandle */
718 : /************************************************************************/
719 :
720 : class VSIADLSHandle final : public VSICurlHandle
721 : {
722 : CPL_DISALLOW_COPY_ASSIGN(VSIADLSHandle)
723 :
724 : std::unique_ptr<VSIAzureBlobHandleHelper> m_poHandleHelper{};
725 :
726 : protected:
727 : virtual struct curl_slist *
728 : GetCurlHeaders(const std::string &osVerb,
729 : struct curl_slist *psHeaders) override;
730 : bool CanRestartOnError(const char *, const char *, bool) 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 = poHandleHelper->GetCurlHeaders("HEAD", headers);
836 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
837 :
838 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 1);
839 :
840 6 : CurlRequestHelper requestHelper;
841 3 : const long response_code = requestHelper.perform(
842 : hCurlHandle, headers, this, poHandleHelper.get());
843 :
844 3 : NetworkStatisticsLogger::LogHEAD();
845 :
846 3 : if (response_code != 200 ||
847 3 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
848 : {
849 0 : curl_easy_cleanup(hCurlHandle);
850 0 : return -1;
851 : }
852 :
853 3 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
854 3 : pStatBuf->st_mode = S_IFDIR;
855 :
856 3 : const char *pszLastModified = strstr(
857 : requestHelper.sWriteFuncHeaderData.pBuffer, "Last-Modified: ");
858 3 : if (pszLastModified)
859 : {
860 0 : pszLastModified += strlen("Last-Modified: ");
861 0 : const char *pszEOL = strstr(pszLastModified, "\r\n");
862 0 : if (pszEOL)
863 : {
864 0 : std::string osLastModified;
865 : osLastModified.assign(pszLastModified,
866 0 : pszEOL - pszLastModified);
867 :
868 : const GIntBig nMTime =
869 0 : GetUnixTimeFromRFC822(osLastModified.c_str());
870 0 : if (nMTime != GINTBIG_MIN)
871 : {
872 0 : pStatBuf->st_mtime = static_cast<time_t>(nMTime);
873 : }
874 : }
875 : }
876 :
877 3 : curl_easy_cleanup(hCurlHandle);
878 :
879 3 : return 0;
880 : }
881 :
882 22 : return VSICurlFilesystemHandlerBase::Stat(osFilenameWithoutSlash.c_str(),
883 22 : pStatBuf, nFlags);
884 : }
885 :
886 : /************************************************************************/
887 : /* GetFileMetadata() */
888 : /************************************************************************/
889 :
890 4 : char **VSIADLSFSHandler::GetFileMetadata(const char *pszFilename,
891 : const char *pszDomain,
892 : CSLConstList papszOptions)
893 : {
894 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
895 0 : return nullptr;
896 :
897 4 : if (pszDomain == nullptr ||
898 4 : (!EQUAL(pszDomain, "STATUS") && !EQUAL(pszDomain, "ACL")))
899 : {
900 1 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
901 1 : pszFilename, pszDomain, papszOptions);
902 : }
903 :
904 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
905 6 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
906 3 : if (poHandleHelper == nullptr)
907 : {
908 0 : return nullptr;
909 : }
910 :
911 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
912 6 : NetworkStatisticsAction oContextAction("GetFileMetadata");
913 :
914 : bool bRetry;
915 3 : bool bError = true;
916 :
917 6 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
918 6 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
919 6 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
920 :
921 3 : CPLStringList aosMetadata;
922 3 : do
923 : {
924 3 : bRetry = false;
925 3 : CURL *hCurlHandle = curl_easy_init();
926 3 : poHandleHelper->AddQueryParameter("action", EQUAL(pszDomain, "STATUS")
927 : ? "getStatus"
928 : : "getAccessControl");
929 :
930 : struct curl_slist *headers =
931 3 : VSICurlSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
932 : aosHTTPOptions.List());
933 :
934 3 : headers = poHandleHelper->GetCurlHeaders("HEAD", headers);
935 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
936 :
937 3 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_NOBODY, 1);
938 :
939 6 : CurlRequestHelper requestHelper;
940 3 : const long response_code = requestHelper.perform(
941 : hCurlHandle, headers, this, poHandleHelper.get());
942 :
943 3 : NetworkStatisticsLogger::LogHEAD();
944 :
945 3 : if (response_code != 200 ||
946 2 : requestHelper.sWriteFuncHeaderData.pBuffer == nullptr)
947 : {
948 : // Look if we should attempt a retry
949 1 : if (oRetryContext.CanRetry(
950 : static_cast<int>(response_code),
951 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
952 : requestHelper.szCurlErrBuf))
953 : {
954 0 : CPLError(CE_Warning, CPLE_AppDefined,
955 : "HTTP error code: %d - %s. "
956 : "Retrying again in %.1f secs",
957 : static_cast<int>(response_code),
958 0 : poHandleHelper->GetURL().c_str(),
959 : oRetryContext.GetCurrentDelay());
960 0 : CPLSleep(oRetryContext.GetCurrentDelay());
961 0 : bRetry = true;
962 : }
963 : else
964 : {
965 1 : CPLDebug(GetDebugKey(), "GetFileMetadata failed on %s: %s",
966 : pszFilename,
967 1 : requestHelper.sWriteFuncData.pBuffer
968 : ? requestHelper.sWriteFuncData.pBuffer
969 : : "(null)");
970 : }
971 : }
972 : else
973 : {
974 4 : char **papszHeaders = CSLTokenizeString2(
975 2 : requestHelper.sWriteFuncHeaderData.pBuffer, "\r\n", 0);
976 12 : for (int i = 0; papszHeaders[i]; ++i)
977 : {
978 10 : char *pszKey = nullptr;
979 : const char *pszValue =
980 10 : CPLParseNameValue(papszHeaders[i], &pszKey);
981 10 : if (pszKey && pszValue && !EQUAL(pszKey, "Server") &&
982 6 : !EQUAL(pszKey, "Date"))
983 : {
984 4 : aosMetadata.SetNameValue(pszKey, pszValue);
985 : }
986 10 : CPLFree(pszKey);
987 : }
988 2 : CSLDestroy(papszHeaders);
989 2 : bError = false;
990 : }
991 :
992 3 : curl_easy_cleanup(hCurlHandle);
993 : } while (bRetry);
994 3 : return bError ? nullptr : CSLDuplicate(aosMetadata.List());
995 : }
996 :
997 : /************************************************************************/
998 : /* SetFileMetadata() */
999 : /************************************************************************/
1000 :
1001 4 : bool VSIADLSFSHandler::SetFileMetadata(const char *pszFilename,
1002 : CSLConstList papszMetadata,
1003 : const char *pszDomain,
1004 : CSLConstList papszOptions)
1005 : {
1006 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
1007 0 : return false;
1008 :
1009 4 : if (pszDomain == nullptr ||
1010 4 : !(EQUAL(pszDomain, "PROPERTIES") || EQUAL(pszDomain, "ACL")))
1011 : {
1012 0 : CPLError(CE_Failure, CPLE_NotSupported,
1013 : "Only PROPERTIES and ACL domain are supported");
1014 0 : return false;
1015 : }
1016 :
1017 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1018 8 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false));
1019 4 : if (poHandleHelper == nullptr)
1020 : {
1021 0 : return false;
1022 : }
1023 :
1024 : const bool bRecursive =
1025 4 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "RECURSIVE", "FALSE"));
1026 4 : const char *pszMode = CSLFetchNameValue(papszOptions, "MODE");
1027 4 : if (!EQUAL(pszDomain, "PROPERTIES") && bRecursive && pszMode == nullptr)
1028 : {
1029 0 : CPLError(CE_Failure, CPLE_AppDefined,
1030 : "For setAccessControlRecursive, the MODE option should be set "
1031 : "to: 'set', 'modify' or 'remove'");
1032 0 : return false;
1033 : }
1034 :
1035 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1036 8 : NetworkStatisticsAction oContextAction("SetFileMetadata");
1037 :
1038 : bool bRetry;
1039 4 : bool bRet = false;
1040 :
1041 8 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
1042 8 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1043 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1044 :
1045 4 : do
1046 : {
1047 4 : bRetry = false;
1048 4 : CURL *hCurlHandle = curl_easy_init();
1049 8 : poHandleHelper->AddQueryParameter(
1050 4 : "action", EQUAL(pszDomain, "PROPERTIES") ? "setProperties"
1051 2 : : bRecursive ? "setAccessControlRecursive"
1052 : : "setAccessControl");
1053 4 : if (pszMode)
1054 : {
1055 2 : poHandleHelper->AddQueryParameter("mode",
1056 2 : CPLString(pszMode).tolower());
1057 : }
1058 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
1059 :
1060 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1061 4 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1062 : aosHTTPOptions.List()));
1063 :
1064 8 : CPLStringList aosList;
1065 8 : for (CSLConstList papszIter = papszMetadata; papszIter && *papszIter;
1066 : ++papszIter)
1067 : {
1068 4 : char *pszKey = nullptr;
1069 4 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
1070 4 : if (pszKey && pszValue)
1071 : {
1072 4 : if ((EQUAL(pszDomain, "PROPERTIES") &&
1073 2 : (EQUAL(pszKey, "x-ms-lease-id") ||
1074 2 : EQUAL(pszKey, "x-ms-cache-control") ||
1075 2 : EQUAL(pszKey, "x-ms-content-type") ||
1076 2 : EQUAL(pszKey, "x-ms-content-disposition") ||
1077 2 : EQUAL(pszKey, "x-ms-content-encoding") ||
1078 2 : EQUAL(pszKey, "x-ms-content-language") ||
1079 2 : EQUAL(pszKey, "x-ms-content-md5") ||
1080 2 : EQUAL(pszKey, "x-ms-properties") ||
1081 0 : EQUAL(pszKey, "x-ms-client-request-id") ||
1082 0 : STARTS_WITH_CI(pszKey, "If-"))) ||
1083 2 : (!EQUAL(pszDomain, "PROPERTIES") && !bRecursive &&
1084 1 : (EQUAL(pszKey, "x-ms-lease-id") ||
1085 1 : EQUAL(pszKey, "x-ms-owner") ||
1086 1 : EQUAL(pszKey, "x-ms-group") ||
1087 1 : EQUAL(pszKey, "x-ms-permissions") ||
1088 1 : EQUAL(pszKey, "x-ms-acl") ||
1089 0 : EQUAL(pszKey, "x-ms-client-request-id") ||
1090 0 : STARTS_WITH_CI(pszKey, "If-"))) ||
1091 1 : (!EQUAL(pszDomain, "PROPERTIES") && bRecursive &&
1092 1 : (EQUAL(pszKey, "x-ms-lease-id") ||
1093 1 : EQUAL(pszKey, "x-ms-acl") ||
1094 0 : EQUAL(pszKey, "x-ms-client-request-id") ||
1095 0 : STARTS_WITH_CI(pszKey, "If-"))))
1096 : {
1097 : const char *pszHeader =
1098 4 : CPLSPrintf("%s: %s", pszKey, pszValue);
1099 4 : aosList.AddString(pszHeader);
1100 4 : headers = curl_slist_append(headers, pszHeader);
1101 : }
1102 : else
1103 : {
1104 0 : CPLDebug(GetDebugKey(), "Ignorizing metadata item %s",
1105 : *papszIter);
1106 : }
1107 : }
1108 4 : CPLFree(pszKey);
1109 : }
1110 :
1111 4 : headers = poHandleHelper->GetCurlHeaders("PATCH", headers);
1112 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1113 :
1114 4 : NetworkStatisticsLogger::LogPUT(0);
1115 :
1116 8 : CurlRequestHelper requestHelper;
1117 4 : const long response_code = requestHelper.perform(
1118 : hCurlHandle, headers, this, poHandleHelper.get());
1119 :
1120 4 : if (response_code != 200 && response_code != 202)
1121 : {
1122 : // Look if we should attempt a retry
1123 1 : if (oRetryContext.CanRetry(
1124 : static_cast<int>(response_code),
1125 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1126 : requestHelper.szCurlErrBuf))
1127 : {
1128 0 : CPLError(CE_Warning, CPLE_AppDefined,
1129 : "HTTP error code: %d - %s. "
1130 : "Retrying again in %.1f secs",
1131 : static_cast<int>(response_code),
1132 0 : poHandleHelper->GetURL().c_str(),
1133 : oRetryContext.GetCurrentDelay());
1134 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1135 0 : bRetry = true;
1136 : }
1137 : else
1138 : {
1139 1 : CPLDebug(GetDebugKey(), "SetFileMetadata on %s failed: %s",
1140 : pszFilename,
1141 1 : requestHelper.sWriteFuncData.pBuffer
1142 : ? requestHelper.sWriteFuncData.pBuffer
1143 : : "(null)");
1144 : }
1145 : }
1146 : else
1147 : {
1148 3 : bRet = true;
1149 : }
1150 :
1151 4 : curl_easy_cleanup(hCurlHandle);
1152 : } while (bRetry);
1153 4 : return bRet;
1154 : }
1155 :
1156 : /************************************************************************/
1157 : /* VSIADLSWriteHandle() */
1158 : /************************************************************************/
1159 :
1160 7 : VSIADLSWriteHandle::VSIADLSWriteHandle(VSIADLSFSHandler *poFS,
1161 : const char *pszFilename,
1162 7 : VSIAzureBlobHandleHelper *poHandleHelper)
1163 7 : : VSIAppendWriteHandle(poFS, poFS->GetFSPrefix().c_str(), pszFilename,
1164 : GetAzureAppendBufferSize()),
1165 14 : m_poHandleHelper(poHandleHelper)
1166 : {
1167 7 : }
1168 :
1169 : /************************************************************************/
1170 : /* ~VSIADLSWriteHandle() */
1171 : /************************************************************************/
1172 :
1173 14 : VSIADLSWriteHandle::~VSIADLSWriteHandle()
1174 : {
1175 7 : Close();
1176 14 : }
1177 :
1178 : /************************************************************************/
1179 : /* InvalidateParentDirectory() */
1180 : /************************************************************************/
1181 :
1182 6 : void VSIADLSWriteHandle::InvalidateParentDirectory()
1183 : {
1184 6 : m_poFS->InvalidateCachedData(m_poHandleHelper->GetURLNoKVP().c_str());
1185 :
1186 6 : const std::string osFilenameWithoutSlash(RemoveTrailingSlash(m_osFilename));
1187 6 : m_poFS->InvalidateDirContent(
1188 12 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
1189 6 : }
1190 :
1191 : /************************************************************************/
1192 : /* CreateFile() */
1193 : /************************************************************************/
1194 :
1195 7 : bool VSIADLSWriteHandle::CreateFile(CSLConstList papszOptions)
1196 : {
1197 7 : m_bCreated =
1198 7 : SendInternal(VSIADLSFSHandler::Event::CREATE_FILE, papszOptions);
1199 7 : return m_bCreated;
1200 : }
1201 :
1202 : /************************************************************************/
1203 : /* Send() */
1204 : /************************************************************************/
1205 :
1206 9 : bool VSIADLSWriteHandle::Send(bool bIsLastBlock)
1207 : {
1208 9 : if (!m_bCreated)
1209 1 : return false;
1210 : // If we have a non-empty buffer, append it
1211 15 : if (m_nBufferOff != 0 &&
1212 7 : !SendInternal(VSIADLSFSHandler::Event::APPEND_DATA, nullptr))
1213 1 : return false;
1214 : // If we are the last block, send the flush event
1215 7 : if (bIsLastBlock && !SendInternal(VSIADLSFSHandler::Event::FLUSH, nullptr))
1216 1 : return false;
1217 :
1218 6 : InvalidateParentDirectory();
1219 :
1220 6 : return true;
1221 : }
1222 :
1223 : /************************************************************************/
1224 : /* SendInternal() */
1225 : /************************************************************************/
1226 :
1227 19 : bool VSIADLSWriteHandle::SendInternal(VSIADLSFSHandler::Event event,
1228 : CSLConstList papszOptions)
1229 : {
1230 38 : return cpl::down_cast<VSIADLSFSHandler *>(m_poFS)->UploadFile(
1231 19 : m_osFilename, event,
1232 : event == VSIADLSFSHandler::Event::CREATE_FILE ? 0
1233 : : event == VSIADLSFSHandler::Event::APPEND_DATA
1234 12 : ? m_nCurOffset - m_nBufferOff
1235 : : m_nCurOffset,
1236 19 : m_pabyBuffer, m_nBufferOff, m_poHandleHelper.get(), m_oRetryParameters,
1237 19 : papszOptions);
1238 : }
1239 :
1240 : /************************************************************************/
1241 : /* ClearCache() */
1242 : /************************************************************************/
1243 :
1244 331 : void VSIADLSFSHandler::ClearCache()
1245 : {
1246 331 : IVSIS3LikeFSHandler::ClearCache();
1247 :
1248 331 : VSIAzureBlobHandleHelper::ClearCache();
1249 331 : }
1250 :
1251 : /************************************************************************/
1252 : /* GetURLFromFilename() */
1253 : /************************************************************************/
1254 :
1255 : std::string
1256 17 : VSIADLSFSHandler::GetURLFromFilename(const std::string &osFilename) const
1257 : {
1258 : const std::string osFilenameWithoutPrefix =
1259 34 : osFilename.substr(GetFSPrefix().size());
1260 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
1261 : VSIAzureBlobHandleHelper::BuildFromURI(osFilenameWithoutPrefix.c_str(),
1262 34 : GetFSPrefix().c_str()));
1263 17 : if (!poHandleHelper)
1264 0 : return std::string();
1265 17 : return poHandleHelper->GetURLNoKVP();
1266 : }
1267 :
1268 : /************************************************************************/
1269 : /* CreateHandleHelper() */
1270 : /************************************************************************/
1271 :
1272 39 : IVSIS3LikeHandleHelper *VSIADLSFSHandler::CreateHandleHelper(const char *pszURI,
1273 : bool)
1274 : {
1275 39 : return VSIAzureBlobHandleHelper::BuildFromURI(pszURI,
1276 78 : GetFSPrefix().c_str());
1277 : }
1278 :
1279 : /************************************************************************/
1280 : /* Rename() */
1281 : /************************************************************************/
1282 :
1283 1 : int VSIADLSFSHandler::Rename(const char *oldpath, const char *newpath,
1284 : GDALProgressFunc, void *)
1285 : {
1286 1 : if (!STARTS_WITH_CI(oldpath, GetFSPrefix().c_str()))
1287 0 : return -1;
1288 1 : if (!STARTS_WITH_CI(newpath, GetFSPrefix().c_str()))
1289 0 : return -1;
1290 :
1291 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1292 2 : NetworkStatisticsAction oContextAction("Rename");
1293 :
1294 : VSIStatBufL sStat;
1295 1 : if (VSIStatL(oldpath, &sStat) != 0)
1296 : {
1297 0 : CPLDebug(GetDebugKey(), "%s is not a object", oldpath);
1298 0 : errno = ENOENT;
1299 0 : return -1;
1300 : }
1301 :
1302 : // POSIX says renaming on the same file is OK
1303 1 : if (strcmp(oldpath, newpath) == 0)
1304 0 : return 0;
1305 :
1306 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1307 2 : CreateHandleHelper(newpath + GetFSPrefix().size(), false));
1308 1 : if (poHandleHelper == nullptr)
1309 : {
1310 0 : return -1;
1311 : }
1312 :
1313 2 : std::string osContinuation;
1314 1 : int nRet = 0;
1315 : bool bRetry;
1316 :
1317 1 : InvalidateCachedData(GetURLFromFilename(oldpath).c_str());
1318 1 : InvalidateCachedData(GetURLFromFilename(newpath).c_str());
1319 1 : InvalidateDirContent(CPLGetDirnameSafe(oldpath));
1320 :
1321 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
1322 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1323 1 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1324 :
1325 1 : do
1326 : {
1327 1 : bRetry = false;
1328 :
1329 1 : CURL *hCurlHandle = curl_easy_init();
1330 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1331 :
1332 1 : poHandleHelper->ResetQueryParameters();
1333 1 : if (!osContinuation.empty())
1334 0 : poHandleHelper->AddQueryParameter("continuation", osContinuation);
1335 :
1336 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1337 1 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1338 : aosHTTPOptions.List()));
1339 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1340 2 : std::string osRenameSource("x-ms-rename-source: /");
1341 : osRenameSource +=
1342 1 : CPLAWSURLEncode(oldpath + GetFSPrefix().size(), false);
1343 1 : headers = curl_slist_append(headers, osRenameSource.c_str());
1344 1 : headers = poHandleHelper->GetCurlHeaders("PUT", headers);
1345 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1346 :
1347 2 : CurlRequestHelper requestHelper;
1348 1 : const long response_code = requestHelper.perform(
1349 : hCurlHandle, headers, this, poHandleHelper.get());
1350 :
1351 1 : NetworkStatisticsLogger::LogPUT(0);
1352 :
1353 1 : if (response_code != 201)
1354 : {
1355 : // Look if we should attempt a retry
1356 0 : if (oRetryContext.CanRetry(
1357 : static_cast<int>(response_code),
1358 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1359 : requestHelper.szCurlErrBuf))
1360 : {
1361 0 : CPLError(CE_Warning, CPLE_AppDefined,
1362 : "HTTP error code: %d - %s. "
1363 : "Retrying again in %.1f secs",
1364 : static_cast<int>(response_code),
1365 0 : poHandleHelper->GetURL().c_str(),
1366 : oRetryContext.GetCurrentDelay());
1367 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1368 0 : bRetry = true;
1369 : }
1370 : else
1371 : {
1372 0 : CPLDebug(GetDebugKey(), "Renaming of %s failed: %s", oldpath,
1373 0 : requestHelper.sWriteFuncData.pBuffer
1374 : ? requestHelper.sWriteFuncData.pBuffer
1375 : : "(null)");
1376 0 : nRet = -1;
1377 : }
1378 : }
1379 : else
1380 : {
1381 : // Get continuation token for response headers
1382 1 : osContinuation = GetContinuationToken(
1383 1 : requestHelper.sWriteFuncHeaderData.pBuffer);
1384 1 : if (!osContinuation.empty())
1385 : {
1386 0 : oRetryContext.ResetCounter();
1387 0 : bRetry = true;
1388 : }
1389 : }
1390 :
1391 1 : curl_easy_cleanup(hCurlHandle);
1392 : } while (bRetry);
1393 :
1394 1 : return nRet;
1395 : }
1396 :
1397 : /************************************************************************/
1398 : /* Unlink() */
1399 : /************************************************************************/
1400 :
1401 2 : int VSIADLSFSHandler::Unlink(const char *pszFilename)
1402 : {
1403 2 : return IVSIS3LikeFSHandler::Unlink(pszFilename);
1404 : }
1405 :
1406 : /************************************************************************/
1407 : /* Mkdir() */
1408 : /************************************************************************/
1409 :
1410 3 : int VSIADLSFSHandler::MkdirInternal(const char *pszDirname, long nMode,
1411 : bool bDoStatCheck)
1412 : {
1413 3 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1414 1 : return -1;
1415 :
1416 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1417 4 : NetworkStatisticsAction oContextAction("Mkdir");
1418 :
1419 4 : const std::string osDirname(pszDirname);
1420 :
1421 2 : if (bDoStatCheck)
1422 : {
1423 : VSIStatBufL sStat;
1424 2 : if (VSIStatL(osDirname.c_str(), &sStat) == 0)
1425 : {
1426 1 : CPLDebug(GetDebugKey(), "Directory or file %s already exists",
1427 : osDirname.c_str());
1428 1 : errno = EEXIST;
1429 1 : return -1;
1430 : }
1431 : }
1432 :
1433 2 : const std::string osDirnameWithoutEndSlash(RemoveTrailingSlash(osDirname));
1434 : auto poHandleHelper =
1435 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
1436 2 : osDirnameWithoutEndSlash.c_str() + GetFSPrefix().size(), false));
1437 1 : if (poHandleHelper == nullptr)
1438 : {
1439 0 : return -1;
1440 : }
1441 :
1442 1 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1443 1 : InvalidateCachedData(
1444 2 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1445 1 : InvalidateDirContent(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
1446 :
1447 1 : int nRet = 0;
1448 :
1449 : bool bRetry;
1450 :
1451 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszDirname));
1452 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1453 1 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1454 :
1455 1 : do
1456 : {
1457 1 : bRetry = false;
1458 1 : CURL *hCurlHandle = curl_easy_init();
1459 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1460 :
1461 1 : poHandleHelper->ResetQueryParameters();
1462 2 : poHandleHelper->AddQueryParameter(
1463 1 : "resource", osDirnameWithoutEndSlash.find(
1464 2 : '/', GetFSPrefix().size()) == std::string::npos
1465 : ? "filesystem"
1466 : : "directory");
1467 :
1468 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1469 1 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1470 : aosHTTPOptions.List()));
1471 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1472 2 : CPLString osPermissions; // keep in this scope
1473 1 : if ((nMode & 0777) != 0)
1474 : {
1475 : osPermissions.Printf("x-ms-permissions: 0%03o",
1476 0 : static_cast<int>(nMode));
1477 0 : headers = curl_slist_append(headers, osPermissions.c_str());
1478 : }
1479 1 : if (bDoStatCheck)
1480 : {
1481 1 : headers = curl_slist_append(headers, "If-None-Match: \"*\"");
1482 : }
1483 :
1484 1 : headers = poHandleHelper->GetCurlHeaders("PUT", headers);
1485 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1486 :
1487 2 : CurlRequestHelper requestHelper;
1488 1 : const long response_code = requestHelper.perform(
1489 : hCurlHandle, headers, this, poHandleHelper.get());
1490 :
1491 1 : NetworkStatisticsLogger::LogPUT(0);
1492 :
1493 1 : if (response_code != 201)
1494 : {
1495 : // Look if we should attempt a retry
1496 0 : if (oRetryContext.CanRetry(
1497 : static_cast<int>(response_code),
1498 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1499 : requestHelper.szCurlErrBuf))
1500 : {
1501 0 : CPLError(CE_Warning, CPLE_AppDefined,
1502 : "HTTP error code: %d - %s. "
1503 : "Retrying again in %.1f secs",
1504 : static_cast<int>(response_code),
1505 0 : poHandleHelper->GetURL().c_str(),
1506 : oRetryContext.GetCurrentDelay());
1507 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1508 0 : bRetry = true;
1509 : }
1510 : else
1511 : {
1512 0 : CPLDebug(GetDebugKey(), "Creation of %s failed: %s",
1513 : osDirname.c_str(),
1514 0 : requestHelper.sWriteFuncData.pBuffer
1515 : ? requestHelper.sWriteFuncData.pBuffer
1516 : : "(null)");
1517 0 : nRet = -1;
1518 : }
1519 : }
1520 :
1521 1 : curl_easy_cleanup(hCurlHandle);
1522 : } while (bRetry);
1523 :
1524 1 : return nRet;
1525 : }
1526 :
1527 3 : int VSIADLSFSHandler::Mkdir(const char *pszDirname, long nMode)
1528 : {
1529 3 : return MkdirInternal(pszDirname, nMode, true);
1530 : }
1531 :
1532 : /************************************************************************/
1533 : /* RmdirInternal() */
1534 : /************************************************************************/
1535 :
1536 4 : int VSIADLSFSHandler::RmdirInternal(const char *pszDirname, bool bRecursive)
1537 : {
1538 8 : const std::string osDirname(pszDirname);
1539 : const std::string osDirnameWithoutEndSlash(
1540 12 : RemoveTrailingSlash(osDirname.c_str()));
1541 :
1542 : const bool bIsFileSystem =
1543 4 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1544 4 : std::string::npos;
1545 :
1546 4 : if (!bRecursive && bIsFileSystem)
1547 : {
1548 : // List content, to confirm it is empty first, as filesystem deletion
1549 : // is recursive by default.
1550 0 : bool bGotFileList = false;
1551 0 : CSLDestroy(GetFileList(osDirnameWithoutEndSlash.c_str(), 1, false,
1552 : &bGotFileList));
1553 0 : if (bGotFileList)
1554 : {
1555 0 : CPLDebug(GetDebugKey(), "Cannot delete filesystem with "
1556 : "non-recursive method as it is not empty");
1557 0 : errno = ENOTEMPTY;
1558 0 : return -1;
1559 : }
1560 : }
1561 :
1562 4 : if (!bIsFileSystem)
1563 : {
1564 : VSIStatBufL sStat;
1565 4 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1566 : {
1567 1 : CPLDebug(GetDebugKey(), "Object %s does not exist",
1568 : osDirname.c_str());
1569 1 : errno = ENOENT;
1570 2 : return -1;
1571 : }
1572 3 : if (!VSI_ISDIR(sStat.st_mode))
1573 : {
1574 1 : CPLDebug(GetDebugKey(), "Object %s is not a directory",
1575 : osDirname.c_str());
1576 1 : errno = ENOTDIR;
1577 1 : return -1;
1578 : }
1579 : }
1580 :
1581 : auto poHandleHelper =
1582 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
1583 4 : osDirnameWithoutEndSlash.c_str() + GetFSPrefix().size(), false));
1584 2 : if (poHandleHelper == nullptr)
1585 : {
1586 0 : return -1;
1587 : }
1588 :
1589 2 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1590 2 : InvalidateCachedData(
1591 4 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1592 2 : InvalidateDirContent(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
1593 2 : if (bRecursive)
1594 : {
1595 1 : PartialClearCache(osDirnameWithoutEndSlash.c_str());
1596 : }
1597 :
1598 4 : std::string osContinuation;
1599 2 : int nRet = 0;
1600 : bool bRetry;
1601 :
1602 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszDirname));
1603 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1604 2 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1605 :
1606 2 : do
1607 : {
1608 2 : bRetry = false;
1609 2 : CURL *hCurlHandle = curl_easy_init();
1610 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1611 : "DELETE");
1612 :
1613 2 : poHandleHelper->ResetQueryParameters();
1614 2 : if (bIsFileSystem)
1615 : {
1616 0 : poHandleHelper->AddQueryParameter("resource", "filesystem");
1617 : }
1618 : else
1619 : {
1620 2 : poHandleHelper->AddQueryParameter("recursive",
1621 : bRecursive ? "true" : "false");
1622 2 : if (!osContinuation.empty())
1623 0 : poHandleHelper->AddQueryParameter("continuation",
1624 : osContinuation);
1625 : }
1626 :
1627 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1628 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1629 : aosHTTPOptions.List()));
1630 2 : headers = poHandleHelper->GetCurlHeaders("DELETE", headers);
1631 :
1632 4 : CurlRequestHelper requestHelper;
1633 2 : const long response_code = requestHelper.perform(
1634 : hCurlHandle, headers, this, poHandleHelper.get());
1635 :
1636 2 : NetworkStatisticsLogger::LogDELETE();
1637 :
1638 : // 200 for path deletion
1639 : // 202 for filesystem deletion
1640 2 : if (response_code != 200 && response_code != 202)
1641 : {
1642 : // Look if we should attempt a retry
1643 0 : if (oRetryContext.CanRetry(
1644 : static_cast<int>(response_code),
1645 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1646 : requestHelper.szCurlErrBuf))
1647 : {
1648 0 : CPLError(CE_Warning, CPLE_AppDefined,
1649 : "HTTP error code: %d - %s. "
1650 : "Retrying again in %.1f secs",
1651 : static_cast<int>(response_code),
1652 0 : poHandleHelper->GetURL().c_str(),
1653 : oRetryContext.GetCurrentDelay());
1654 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1655 0 : bRetry = true;
1656 : }
1657 : else
1658 : {
1659 0 : CPLDebug(GetDebugKey(), "Delete of %s failed: %s",
1660 : osDirname.c_str(),
1661 0 : requestHelper.sWriteFuncData.pBuffer
1662 : ? requestHelper.sWriteFuncData.pBuffer
1663 : : "(null)");
1664 0 : if (requestHelper.sWriteFuncData.pBuffer != nullptr)
1665 : {
1666 0 : VSIError(VSIE_ObjectStorageGenericError, "%s",
1667 : requestHelper.sWriteFuncData.pBuffer);
1668 0 : if (strstr(requestHelper.sWriteFuncData.pBuffer,
1669 : "PathNotFound"))
1670 : {
1671 0 : errno = ENOENT;
1672 : }
1673 0 : else if (strstr(requestHelper.sWriteFuncData.pBuffer,
1674 : "DirectoryNotEmpty"))
1675 : {
1676 0 : errno = ENOTEMPTY;
1677 : }
1678 : }
1679 0 : nRet = -1;
1680 : }
1681 : }
1682 : else
1683 : {
1684 : // Get continuation token for response headers
1685 2 : osContinuation = GetContinuationToken(
1686 2 : requestHelper.sWriteFuncHeaderData.pBuffer);
1687 2 : if (!osContinuation.empty())
1688 : {
1689 0 : oRetryContext.ResetCounter();
1690 0 : bRetry = true;
1691 : }
1692 : }
1693 :
1694 2 : curl_easy_cleanup(hCurlHandle);
1695 : } while (bRetry);
1696 :
1697 2 : return nRet;
1698 : }
1699 :
1700 : /************************************************************************/
1701 : /* Rmdir() */
1702 : /************************************************************************/
1703 :
1704 4 : int VSIADLSFSHandler::Rmdir(const char *pszDirname)
1705 : {
1706 4 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1707 1 : return -1;
1708 :
1709 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1710 6 : NetworkStatisticsAction oContextAction("Rmdir");
1711 :
1712 3 : return RmdirInternal(pszDirname, false);
1713 : }
1714 :
1715 : /************************************************************************/
1716 : /* RmdirRecursive() */
1717 : /************************************************************************/
1718 :
1719 1 : int VSIADLSFSHandler::RmdirRecursive(const char *pszDirname)
1720 : {
1721 1 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1722 0 : return -1;
1723 :
1724 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1725 2 : NetworkStatisticsAction oContextAction("RmdirRecursive");
1726 :
1727 1 : return RmdirInternal(pszDirname, true);
1728 : }
1729 :
1730 : /************************************************************************/
1731 : /* CopyObject() */
1732 : /************************************************************************/
1733 :
1734 4 : int VSIADLSFSHandler::CopyObject(const char *oldpath, const char *newpath,
1735 : CSLConstList /* papszMetadata */)
1736 : {
1737 : // There is no CopyObject in ADLS... So use the base Azure blob one...
1738 :
1739 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1740 8 : NetworkStatisticsAction oContextAction("CopyObject");
1741 :
1742 12 : std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
1743 : auto poAzHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1744 4 : VSIAzureBlobHandleHelper::BuildFromURI(
1745 8 : osTargetNameWithoutPrefix.c_str(), "/vsiaz/"));
1746 4 : if (poAzHandleHelper == nullptr)
1747 : {
1748 0 : return -1;
1749 : }
1750 :
1751 12 : std::string osSourceNameWithoutPrefix = oldpath + GetFSPrefix().size();
1752 : auto poAzHandleHelperSource = std::unique_ptr<IVSIS3LikeHandleHelper>(
1753 4 : VSIAzureBlobHandleHelper::BuildFromURI(
1754 8 : osSourceNameWithoutPrefix.c_str(), "/vsiaz/"));
1755 4 : if (poAzHandleHelperSource == nullptr)
1756 : {
1757 0 : return -1;
1758 : }
1759 :
1760 8 : std::string osSourceHeader("x-ms-copy-source: ");
1761 4 : osSourceHeader += poAzHandleHelperSource->GetURLNoKVP();
1762 :
1763 4 : int nRet = 0;
1764 :
1765 : bool bRetry;
1766 :
1767 8 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
1768 8 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1769 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1770 :
1771 4 : do
1772 : {
1773 4 : bRetry = false;
1774 4 : CURL *hCurlHandle = curl_easy_init();
1775 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1776 :
1777 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1778 4 : CPLHTTPSetOptions(hCurlHandle, poAzHandleHelper->GetURL().c_str(),
1779 : aosHTTPOptions.List()));
1780 4 : headers = curl_slist_append(headers, osSourceHeader.c_str());
1781 4 : headers = curl_slist_append(headers, "Content-Length: 0");
1782 4 : headers = VSICurlSetContentTypeFromExt(headers, newpath);
1783 4 : headers = poAzHandleHelper->GetCurlHeaders("PUT", headers);
1784 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1785 :
1786 8 : CurlRequestHelper requestHelper;
1787 4 : const long response_code = requestHelper.perform(
1788 : hCurlHandle, headers, this, poAzHandleHelper.get());
1789 :
1790 4 : NetworkStatisticsLogger::LogPUT(0);
1791 :
1792 4 : if (response_code != 202)
1793 : {
1794 : // Look if we should attempt a retry
1795 1 : if (oRetryContext.CanRetry(
1796 : static_cast<int>(response_code),
1797 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1798 : requestHelper.szCurlErrBuf))
1799 : {
1800 0 : CPLError(CE_Warning, CPLE_AppDefined,
1801 : "HTTP error code: %d - %s. "
1802 : "Retrying again in %.1f secs",
1803 : static_cast<int>(response_code),
1804 0 : poAzHandleHelper->GetURL().c_str(),
1805 : oRetryContext.GetCurrentDelay());
1806 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1807 0 : bRetry = true;
1808 : }
1809 : else
1810 : {
1811 1 : CPLDebug(GetDebugKey(), "%s",
1812 1 : requestHelper.sWriteFuncData.pBuffer
1813 : ? requestHelper.sWriteFuncData.pBuffer
1814 : : "(null)");
1815 1 : CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
1816 : oldpath, newpath);
1817 1 : nRet = -1;
1818 : }
1819 : }
1820 : else
1821 : {
1822 : auto poADLSHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1823 3 : VSIAzureBlobHandleHelper::BuildFromURI(
1824 9 : osTargetNameWithoutPrefix.c_str(), GetFSPrefix().c_str()));
1825 3 : if (poADLSHandleHelper != nullptr)
1826 3 : InvalidateCachedData(poADLSHandleHelper->GetURLNoKVP().c_str());
1827 :
1828 : const std::string osFilenameWithoutSlash(
1829 6 : RemoveTrailingSlash(newpath));
1830 3 : InvalidateDirContent(
1831 6 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
1832 : }
1833 :
1834 4 : curl_easy_cleanup(hCurlHandle);
1835 : } while (bRetry);
1836 :
1837 4 : return nRet;
1838 : }
1839 :
1840 : /************************************************************************/
1841 : /* UploadFile() */
1842 : /************************************************************************/
1843 :
1844 25 : bool VSIADLSFSHandler::UploadFile(
1845 : const std::string &osFilename, Event event, vsi_l_offset nPosition,
1846 : const void *pabyBuffer, size_t nBufferSize,
1847 : IVSIS3LikeHandleHelper *poHandleHelper,
1848 : const CPLHTTPRetryParameters &oRetryParameters, CSLConstList papszOptions)
1849 : {
1850 50 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1851 50 : NetworkStatisticsFile oContextFile(osFilename.c_str());
1852 50 : NetworkStatisticsAction oContextAction("UploadFile");
1853 :
1854 25 : if (event == Event::CREATE_FILE)
1855 : {
1856 8 : InvalidateCachedData(poHandleHelper->GetURLNoKVP().c_str());
1857 8 : InvalidateDirContent(CPLGetDirnameSafe(osFilename.c_str()));
1858 : }
1859 :
1860 : const CPLStringList aosHTTPOptions(
1861 50 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
1862 :
1863 25 : bool bSuccess = true;
1864 25 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1865 : bool bRetry;
1866 25 : do
1867 : {
1868 25 : bRetry = false;
1869 :
1870 25 : CURL *hCurlHandle = curl_easy_init();
1871 :
1872 25 : poHandleHelper->ResetQueryParameters();
1873 25 : if (event == Event::CREATE_FILE)
1874 : {
1875 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/create?view=rest-storageservices-datalakestoragegen2-2019-12-12
1876 8 : poHandleHelper->AddQueryParameter("resource", "file");
1877 : }
1878 17 : else if (event == Event::APPEND_DATA)
1879 : {
1880 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update?view=rest-storageservices-datalakestoragegen2-2019-12-12
1881 10 : poHandleHelper->AddQueryParameter("action", "append");
1882 10 : poHandleHelper->AddQueryParameter(
1883 : "position",
1884 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nPosition)));
1885 : }
1886 : else
1887 : {
1888 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update?view=rest-storageservices-datalakestoragegen2-2019-12-12
1889 7 : poHandleHelper->AddQueryParameter("action", "flush");
1890 7 : poHandleHelper->AddQueryParameter("close", "true");
1891 7 : poHandleHelper->AddQueryParameter(
1892 : "position",
1893 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nPosition)));
1894 : }
1895 :
1896 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
1897 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
1898 : PutData::ReadCallBackBuffer);
1899 25 : PutData putData;
1900 25 : putData.pabyData = static_cast<const GByte *>(pabyBuffer);
1901 25 : putData.nOff = 0;
1902 25 : putData.nTotalSize = nBufferSize;
1903 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
1904 :
1905 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1906 25 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1907 : aosHTTPOptions.List()));
1908 25 : headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
1909 : osFilename.c_str());
1910 :
1911 50 : CPLString osContentLength; // leave it in this scope
1912 :
1913 25 : if (event == Event::APPEND_DATA)
1914 : {
1915 10 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1916 : static_cast<int>(nBufferSize));
1917 : // Disable "Expect: 100-continue" which doesn't hurt, but is not
1918 : // needed
1919 10 : headers = curl_slist_append(headers, "Expect:");
1920 : osContentLength.Printf("Content-Length: %d",
1921 10 : static_cast<int>(nBufferSize));
1922 10 : headers = curl_slist_append(headers, osContentLength.c_str());
1923 : }
1924 : else
1925 : {
1926 15 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
1927 15 : headers = curl_slist_append(headers, "Content-Length: 0");
1928 : }
1929 :
1930 25 : const char *pszVerb = (event == Event::CREATE_FILE) ? "PUT" : "PATCH";
1931 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, pszVerb);
1932 25 : headers = poHandleHelper->GetCurlHeaders(pszVerb, headers);
1933 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1934 :
1935 50 : CurlRequestHelper requestHelper;
1936 : const long response_code =
1937 25 : requestHelper.perform(hCurlHandle, headers, this, poHandleHelper);
1938 :
1939 25 : NetworkStatisticsLogger::LogPUT(
1940 : event == Event::APPEND_DATA ? nBufferSize : 0);
1941 :
1942 : // 200 for PATCH flush
1943 : // 201 for PUT create
1944 : // 202 for PATCH append
1945 25 : if (response_code != 200 && response_code != 201 &&
1946 : response_code != 202)
1947 : {
1948 : // Look if we should attempt a retry
1949 3 : if (oRetryContext.CanRetry(
1950 : static_cast<int>(response_code),
1951 3 : requestHelper.sWriteFuncHeaderData.pBuffer,
1952 : requestHelper.szCurlErrBuf))
1953 : {
1954 0 : CPLError(CE_Warning, CPLE_AppDefined,
1955 : "HTTP error code: %d - %s. "
1956 : "Retrying again in %.1f secs",
1957 : static_cast<int>(response_code),
1958 0 : poHandleHelper->GetURL().c_str(),
1959 : oRetryContext.GetCurrentDelay());
1960 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1961 0 : bRetry = true;
1962 : }
1963 : else
1964 : {
1965 3 : CPLDebug(GetDebugKey(), "%s of %s failed: %s", pszVerb,
1966 : osFilename.c_str(),
1967 3 : requestHelper.sWriteFuncData.pBuffer
1968 : ? requestHelper.sWriteFuncData.pBuffer
1969 : : "(null)");
1970 3 : bSuccess = false;
1971 : }
1972 : }
1973 :
1974 25 : curl_easy_cleanup(hCurlHandle);
1975 : } while (bRetry);
1976 :
1977 50 : return bSuccess;
1978 : }
1979 :
1980 : /************************************************************************/
1981 : /* GetFileList() */
1982 : /************************************************************************/
1983 :
1984 4 : char **VSIADLSFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
1985 : bool *pbGotFileList)
1986 : {
1987 4 : return GetFileList(pszDirname, nMaxFiles, true, pbGotFileList);
1988 : }
1989 :
1990 4 : char **VSIADLSFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
1991 : bool bCacheEntries, bool *pbGotFileList)
1992 : {
1993 : if (ENABLE_DEBUG)
1994 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
1995 :
1996 4 : *pbGotFileList = false;
1997 :
1998 : char **papszOptions =
1999 4 : CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
2000 4 : papszOptions = CSLSetNameValue(papszOptions, "CACHE_ENTRIES",
2001 : bCacheEntries ? "YES" : "NO");
2002 4 : auto dir = OpenDir(pszDirname, 0, papszOptions);
2003 4 : CSLDestroy(papszOptions);
2004 4 : if (!dir)
2005 : {
2006 1 : return nullptr;
2007 : }
2008 6 : CPLStringList aosFileList;
2009 : while (true)
2010 : {
2011 8 : auto entry = dir->NextDirEntry();
2012 8 : if (!entry)
2013 : {
2014 3 : break;
2015 : }
2016 5 : aosFileList.AddString(entry->pszName);
2017 :
2018 5 : if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
2019 0 : break;
2020 5 : }
2021 3 : delete dir;
2022 3 : *pbGotFileList = true;
2023 3 : return aosFileList.StealList();
2024 : }
2025 :
2026 : /************************************************************************/
2027 : /* GetOptions() */
2028 : /************************************************************************/
2029 :
2030 1 : const char *VSIADLSFSHandler::GetOptions()
2031 : {
2032 : static std::string osOptions(
2033 2 : std::string("<Options>") +
2034 : " <Option name='AZURE_STORAGE_CONNECTION_STRING' type='string' "
2035 : "description='Connection string that contains account name and "
2036 : "secret key'/>"
2037 : " <Option name='AZURE_STORAGE_ACCOUNT' type='string' "
2038 : "description='Storage account. To use with AZURE_STORAGE_ACCESS_KEY'/>"
2039 : " <Option name='AZURE_STORAGE_ACCESS_KEY' type='string' "
2040 : "description='Secret key'/>"
2041 : " <Option name='VSIAZ_CHUNK_SIZE' type='int' "
2042 : "description='Size in MB for chunks of files that are uploaded' "
2043 3 : "default='4' min='1' max='4'/>" +
2044 2 : VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
2045 1 : return osOptions.c_str();
2046 : }
2047 :
2048 : /************************************************************************/
2049 : /* GetSignedURL() */
2050 : /************************************************************************/
2051 :
2052 2 : char *VSIADLSFSHandler::GetSignedURL(const char *pszFilename,
2053 : CSLConstList papszOptions)
2054 : {
2055 2 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2056 0 : return nullptr;
2057 :
2058 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
2059 : VSIAzureBlobHandleHelper::BuildFromURI(pszFilename +
2060 2 : GetFSPrefix().size(),
2061 : "/vsiaz/", // use Azure blob
2062 4 : nullptr, papszOptions));
2063 2 : if (poHandleHelper == nullptr)
2064 : {
2065 1 : return nullptr;
2066 : }
2067 :
2068 2 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
2069 :
2070 1 : return CPLStrdup(osRet.c_str());
2071 : }
2072 :
2073 : /************************************************************************/
2074 : /* OpenDir() */
2075 : /************************************************************************/
2076 :
2077 10 : VSIDIR *VSIADLSFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
2078 : const char *const *papszOptions)
2079 : {
2080 10 : if (nRecurseDepth > 0)
2081 : {
2082 0 : return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
2083 0 : papszOptions);
2084 : }
2085 :
2086 10 : if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
2087 0 : return nullptr;
2088 :
2089 20 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2090 20 : NetworkStatisticsAction oContextAction("OpenDir");
2091 :
2092 : const std::string osDirnameWithoutPrefix =
2093 30 : RemoveTrailingSlash(pszPath + GetFSPrefix().size());
2094 20 : std::string osFilesystem(osDirnameWithoutPrefix);
2095 20 : std::string osObjectKey;
2096 10 : size_t nSlashPos = osDirnameWithoutPrefix.find('/');
2097 10 : if (nSlashPos != std::string::npos)
2098 : {
2099 3 : osFilesystem = osDirnameWithoutPrefix.substr(0, nSlashPos);
2100 3 : osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
2101 : }
2102 :
2103 10 : VSIDIRADLS *dir = new VSIDIRADLS(this);
2104 10 : dir->m_nRecurseDepth = nRecurseDepth;
2105 10 : dir->m_poFS = this;
2106 10 : dir->m_bRecursiveRequestFromAccountRoot =
2107 10 : osFilesystem.empty() && nRecurseDepth < 0;
2108 10 : dir->m_osFilesystem = std::move(osFilesystem);
2109 10 : dir->m_osObjectKey = std::move(osObjectKey);
2110 10 : dir->m_nMaxFiles =
2111 10 : atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
2112 10 : dir->m_bCacheEntries =
2113 10 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "YES"));
2114 10 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
2115 10 : if (!dir->IssueListDir())
2116 : {
2117 1 : delete dir;
2118 1 : return nullptr;
2119 : }
2120 :
2121 9 : return dir;
2122 : }
2123 :
2124 : /************************************************************************/
2125 : /* GetStreamingFilename() */
2126 : /************************************************************************/
2127 :
2128 : std::string
2129 0 : VSIADLSFSHandler::GetStreamingFilename(const std::string &osFilename) const
2130 : {
2131 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
2132 0 : return "/vsiaz_streaming/" + osFilename.substr(GetFSPrefix().size());
2133 0 : return osFilename;
2134 : }
2135 :
2136 : /************************************************************************/
2137 : /* VSIADLSHandle() */
2138 : /************************************************************************/
2139 :
2140 25 : VSIADLSHandle::VSIADLSHandle(VSIADLSFSHandler *poFSIn, const char *pszFilename,
2141 25 : VSIAzureBlobHandleHelper *poHandleHelper)
2142 25 : : VSICurlHandle(poFSIn, pszFilename, poHandleHelper->GetURLNoKVP().c_str()),
2143 50 : m_poHandleHelper(poHandleHelper)
2144 : {
2145 25 : m_osQueryString = poHandleHelper->GetSASQueryString();
2146 25 : }
2147 :
2148 : /************************************************************************/
2149 : /* GetCurlHeaders() */
2150 : /************************************************************************/
2151 :
2152 21 : struct curl_slist *VSIADLSHandle::GetCurlHeaders(const std::string &osVerb,
2153 : struct curl_slist *psHeaders)
2154 : {
2155 21 : return m_poHandleHelper->GetCurlHeaders(osVerb, psHeaders);
2156 : }
2157 :
2158 : /************************************************************************/
2159 : /* CanRestartOnError() */
2160 : /************************************************************************/
2161 :
2162 6 : bool VSIADLSHandle::CanRestartOnError(const char *pszErrorMsg,
2163 : const char *pszHeaders, bool bSetError)
2164 : {
2165 6 : return m_poHandleHelper->CanRestartOnError(pszErrorMsg, pszHeaders,
2166 6 : bSetError);
2167 : }
2168 :
2169 : } /* end of namespace cpl */
2170 :
2171 : #endif // DOXYGEN_SKIP
2172 : //! @endcond
2173 :
2174 : /************************************************************************/
2175 : /* VSIInstallADLSFileHandler() */
2176 : /************************************************************************/
2177 :
2178 : /*!
2179 : \brief Install /vsiaz/ Microsoft Azure Data Lake Storage Gen2 file system
2180 : handler (requires libcurl)
2181 :
2182 : \verbatim embed:rst
2183 : See :ref:`/vsiadls/ documentation <vsiadls>`
2184 : \endverbatim
2185 :
2186 : @since GDAL 3.3
2187 : */
2188 :
2189 1691 : void VSIInstallADLSFileHandler(void)
2190 : {
2191 1691 : VSIFileManager::InstallHandler("/vsiadls/", new cpl::VSIADLSFSHandler);
2192 1691 : }
2193 :
2194 : #endif /* HAVE_CURL */
|