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