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