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 1392 : VSIADLSFSHandler() = default;
177 1882 : ~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 6 : const std::string osFilenameWithoutSlash(RemoveTrailingSlash(m_osFilename));
1189 6 : m_poFS->InvalidateDirContent(
1190 12 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
1191 6 : }
1192 :
1193 : /************************************************************************/
1194 : /* CreateFile() */
1195 : /************************************************************************/
1196 :
1197 7 : bool VSIADLSWriteHandle::CreateFile(CSLConstList papszOptions)
1198 : {
1199 7 : m_bCreated =
1200 7 : SendInternal(VSIADLSFSHandler::Event::CREATE_FILE, papszOptions);
1201 7 : return m_bCreated;
1202 : }
1203 :
1204 : /************************************************************************/
1205 : /* Send() */
1206 : /************************************************************************/
1207 :
1208 9 : bool VSIADLSWriteHandle::Send(bool bIsLastBlock)
1209 : {
1210 9 : if (!m_bCreated)
1211 1 : return false;
1212 : // If we have a non-empty buffer, append it
1213 15 : if (m_nBufferOff != 0 &&
1214 7 : !SendInternal(VSIADLSFSHandler::Event::APPEND_DATA, nullptr))
1215 1 : return false;
1216 : // If we are the last block, send the flush event
1217 7 : if (bIsLastBlock && !SendInternal(VSIADLSFSHandler::Event::FLUSH, nullptr))
1218 1 : return false;
1219 :
1220 6 : InvalidateParentDirectory();
1221 :
1222 6 : return true;
1223 : }
1224 :
1225 : /************************************************************************/
1226 : /* SendInternal() */
1227 : /************************************************************************/
1228 :
1229 19 : bool VSIADLSWriteHandle::SendInternal(VSIADLSFSHandler::Event event,
1230 : CSLConstList papszOptions)
1231 : {
1232 38 : return cpl::down_cast<VSIADLSFSHandler *>(m_poFS)->UploadFile(
1233 19 : m_osFilename, event,
1234 : event == VSIADLSFSHandler::Event::CREATE_FILE ? 0
1235 : : event == VSIADLSFSHandler::Event::APPEND_DATA
1236 12 : ? m_nCurOffset - m_nBufferOff
1237 : : m_nCurOffset,
1238 19 : m_pabyBuffer, m_nBufferOff, m_poHandleHelper.get(), m_oRetryParameters,
1239 19 : papszOptions);
1240 : }
1241 :
1242 : /************************************************************************/
1243 : /* ClearCache() */
1244 : /************************************************************************/
1245 :
1246 313 : void VSIADLSFSHandler::ClearCache()
1247 : {
1248 313 : IVSIS3LikeFSHandler::ClearCache();
1249 :
1250 313 : VSIAzureBlobHandleHelper::ClearCache();
1251 313 : }
1252 :
1253 : /************************************************************************/
1254 : /* GetURLFromFilename() */
1255 : /************************************************************************/
1256 :
1257 : std::string
1258 17 : VSIADLSFSHandler::GetURLFromFilename(const std::string &osFilename) const
1259 : {
1260 : const std::string osFilenameWithoutPrefix =
1261 34 : osFilename.substr(GetFSPrefix().size());
1262 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
1263 : VSIAzureBlobHandleHelper::BuildFromURI(osFilenameWithoutPrefix.c_str(),
1264 34 : GetFSPrefix().c_str()));
1265 17 : if (!poHandleHelper)
1266 0 : return std::string();
1267 17 : return poHandleHelper->GetURLNoKVP();
1268 : }
1269 :
1270 : /************************************************************************/
1271 : /* CreateHandleHelper() */
1272 : /************************************************************************/
1273 :
1274 39 : IVSIS3LikeHandleHelper *VSIADLSFSHandler::CreateHandleHelper(const char *pszURI,
1275 : bool)
1276 : {
1277 39 : return VSIAzureBlobHandleHelper::BuildFromURI(pszURI,
1278 78 : GetFSPrefix().c_str());
1279 : }
1280 :
1281 : /************************************************************************/
1282 : /* Rename() */
1283 : /************************************************************************/
1284 :
1285 1 : int VSIADLSFSHandler::Rename(const char *oldpath, const char *newpath)
1286 : {
1287 1 : if (!STARTS_WITH_CI(oldpath, GetFSPrefix().c_str()))
1288 0 : return -1;
1289 1 : if (!STARTS_WITH_CI(newpath, GetFSPrefix().c_str()))
1290 0 : return -1;
1291 :
1292 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1293 2 : NetworkStatisticsAction oContextAction("Rename");
1294 :
1295 : VSIStatBufL sStat;
1296 1 : if (VSIStatL(oldpath, &sStat) != 0)
1297 : {
1298 0 : CPLDebug(GetDebugKey(), "%s is not a object", oldpath);
1299 0 : errno = ENOENT;
1300 0 : return -1;
1301 : }
1302 :
1303 : // POSIX says renaming on the same file is OK
1304 1 : if (strcmp(oldpath, newpath) == 0)
1305 0 : return 0;
1306 :
1307 : auto poHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1308 2 : CreateHandleHelper(newpath + GetFSPrefix().size(), false));
1309 1 : if (poHandleHelper == nullptr)
1310 : {
1311 0 : return -1;
1312 : }
1313 :
1314 2 : std::string osContinuation;
1315 1 : int nRet = 0;
1316 : bool bRetry;
1317 :
1318 1 : InvalidateCachedData(GetURLFromFilename(oldpath).c_str());
1319 1 : InvalidateCachedData(GetURLFromFilename(newpath).c_str());
1320 1 : InvalidateDirContent(CPLGetDirnameSafe(oldpath));
1321 :
1322 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
1323 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1324 1 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1325 :
1326 1 : do
1327 : {
1328 1 : bRetry = false;
1329 :
1330 1 : CURL *hCurlHandle = curl_easy_init();
1331 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1332 :
1333 1 : poHandleHelper->ResetQueryParameters();
1334 1 : if (!osContinuation.empty())
1335 0 : poHandleHelper->AddQueryParameter("continuation", osContinuation);
1336 :
1337 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1338 1 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1339 : aosHTTPOptions.List()));
1340 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1341 2 : std::string osRenameSource("x-ms-rename-source: /");
1342 : osRenameSource +=
1343 1 : CPLAWSURLEncode(oldpath + GetFSPrefix().size(), false);
1344 1 : headers = curl_slist_append(headers, osRenameSource.c_str());
1345 1 : headers = VSICurlMergeHeaders(
1346 1 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
1347 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1348 :
1349 2 : CurlRequestHelper requestHelper;
1350 1 : const long response_code = requestHelper.perform(
1351 : hCurlHandle, headers, this, poHandleHelper.get());
1352 :
1353 1 : NetworkStatisticsLogger::LogPUT(0);
1354 :
1355 1 : if (response_code != 201)
1356 : {
1357 : // Look if we should attempt a retry
1358 0 : if (oRetryContext.CanRetry(
1359 : static_cast<int>(response_code),
1360 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1361 : requestHelper.szCurlErrBuf))
1362 : {
1363 0 : CPLError(CE_Warning, CPLE_AppDefined,
1364 : "HTTP error code: %d - %s. "
1365 : "Retrying again in %.1f secs",
1366 : static_cast<int>(response_code),
1367 0 : poHandleHelper->GetURL().c_str(),
1368 : oRetryContext.GetCurrentDelay());
1369 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1370 0 : bRetry = true;
1371 : }
1372 : else
1373 : {
1374 0 : CPLDebug(GetDebugKey(), "Renaming of %s failed: %s", oldpath,
1375 0 : requestHelper.sWriteFuncData.pBuffer
1376 : ? requestHelper.sWriteFuncData.pBuffer
1377 : : "(null)");
1378 0 : nRet = -1;
1379 : }
1380 : }
1381 : else
1382 : {
1383 : // Get continuation token for response headers
1384 1 : osContinuation = GetContinuationToken(
1385 1 : requestHelper.sWriteFuncHeaderData.pBuffer);
1386 1 : if (!osContinuation.empty())
1387 : {
1388 0 : oRetryContext.ResetCounter();
1389 0 : bRetry = true;
1390 : }
1391 : }
1392 :
1393 1 : curl_easy_cleanup(hCurlHandle);
1394 : } while (bRetry);
1395 :
1396 1 : return nRet;
1397 : }
1398 :
1399 : /************************************************************************/
1400 : /* Unlink() */
1401 : /************************************************************************/
1402 :
1403 2 : int VSIADLSFSHandler::Unlink(const char *pszFilename)
1404 : {
1405 2 : return IVSIS3LikeFSHandler::Unlink(pszFilename);
1406 : }
1407 :
1408 : /************************************************************************/
1409 : /* Mkdir() */
1410 : /************************************************************************/
1411 :
1412 3 : int VSIADLSFSHandler::MkdirInternal(const char *pszDirname, long nMode,
1413 : bool bDoStatCheck)
1414 : {
1415 3 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1416 1 : return -1;
1417 :
1418 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1419 4 : NetworkStatisticsAction oContextAction("Mkdir");
1420 :
1421 4 : const std::string osDirname(pszDirname);
1422 :
1423 2 : if (bDoStatCheck)
1424 : {
1425 : VSIStatBufL sStat;
1426 2 : if (VSIStatL(osDirname.c_str(), &sStat) == 0)
1427 : {
1428 1 : CPLDebug(GetDebugKey(), "Directory or file %s already exists",
1429 : osDirname.c_str());
1430 1 : errno = EEXIST;
1431 1 : return -1;
1432 : }
1433 : }
1434 :
1435 2 : const std::string osDirnameWithoutEndSlash(RemoveTrailingSlash(osDirname));
1436 : auto poHandleHelper =
1437 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
1438 2 : osDirnameWithoutEndSlash.c_str() + GetFSPrefix().size(), false));
1439 1 : if (poHandleHelper == nullptr)
1440 : {
1441 0 : return -1;
1442 : }
1443 :
1444 1 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1445 1 : InvalidateCachedData(
1446 2 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1447 1 : InvalidateDirContent(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
1448 :
1449 1 : int nRet = 0;
1450 :
1451 : bool bRetry;
1452 :
1453 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszDirname));
1454 2 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1455 1 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1456 :
1457 1 : do
1458 : {
1459 1 : bRetry = false;
1460 1 : CURL *hCurlHandle = curl_easy_init();
1461 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1462 :
1463 1 : poHandleHelper->ResetQueryParameters();
1464 2 : poHandleHelper->AddQueryParameter(
1465 1 : "resource", osDirnameWithoutEndSlash.find(
1466 2 : '/', GetFSPrefix().size()) == std::string::npos
1467 : ? "filesystem"
1468 : : "directory");
1469 :
1470 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1471 1 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1472 : aosHTTPOptions.List()));
1473 1 : headers = curl_slist_append(headers, "Content-Length: 0");
1474 2 : CPLString osPermissions; // keep in this scope
1475 1 : if ((nMode & 0777) != 0)
1476 : {
1477 : osPermissions.Printf("x-ms-permissions: 0%03o",
1478 0 : static_cast<int>(nMode));
1479 0 : headers = curl_slist_append(headers, osPermissions.c_str());
1480 : }
1481 1 : if (bDoStatCheck)
1482 : {
1483 1 : headers = curl_slist_append(headers, "If-None-Match: \"*\"");
1484 : }
1485 :
1486 1 : headers = VSICurlMergeHeaders(
1487 1 : headers, poHandleHelper->GetCurlHeaders("PUT", headers));
1488 1 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1489 :
1490 2 : CurlRequestHelper requestHelper;
1491 1 : const long response_code = requestHelper.perform(
1492 : hCurlHandle, headers, this, poHandleHelper.get());
1493 :
1494 1 : NetworkStatisticsLogger::LogPUT(0);
1495 :
1496 1 : if (response_code != 201)
1497 : {
1498 : // Look if we should attempt a retry
1499 0 : if (oRetryContext.CanRetry(
1500 : static_cast<int>(response_code),
1501 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1502 : requestHelper.szCurlErrBuf))
1503 : {
1504 0 : CPLError(CE_Warning, CPLE_AppDefined,
1505 : "HTTP error code: %d - %s. "
1506 : "Retrying again in %.1f secs",
1507 : static_cast<int>(response_code),
1508 0 : poHandleHelper->GetURL().c_str(),
1509 : oRetryContext.GetCurrentDelay());
1510 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1511 0 : bRetry = true;
1512 : }
1513 : else
1514 : {
1515 0 : CPLDebug(GetDebugKey(), "Creation of %s failed: %s",
1516 : osDirname.c_str(),
1517 0 : requestHelper.sWriteFuncData.pBuffer
1518 : ? requestHelper.sWriteFuncData.pBuffer
1519 : : "(null)");
1520 0 : nRet = -1;
1521 : }
1522 : }
1523 :
1524 1 : curl_easy_cleanup(hCurlHandle);
1525 : } while (bRetry);
1526 :
1527 1 : return nRet;
1528 : }
1529 :
1530 3 : int VSIADLSFSHandler::Mkdir(const char *pszDirname, long nMode)
1531 : {
1532 3 : return MkdirInternal(pszDirname, nMode, true);
1533 : }
1534 :
1535 : /************************************************************************/
1536 : /* RmdirInternal() */
1537 : /************************************************************************/
1538 :
1539 4 : int VSIADLSFSHandler::RmdirInternal(const char *pszDirname, bool bRecursive)
1540 : {
1541 8 : const std::string osDirname(pszDirname);
1542 : const std::string osDirnameWithoutEndSlash(
1543 12 : RemoveTrailingSlash(osDirname.c_str()));
1544 :
1545 : const bool bIsFileSystem =
1546 4 : osDirnameWithoutEndSlash.find('/', GetFSPrefix().size()) ==
1547 4 : std::string::npos;
1548 :
1549 4 : if (!bRecursive && bIsFileSystem)
1550 : {
1551 : // List content, to confirm it is empty first, as filesystem deletion
1552 : // is recursive by default.
1553 0 : bool bGotFileList = false;
1554 0 : CSLDestroy(GetFileList(osDirnameWithoutEndSlash.c_str(), 1, false,
1555 : &bGotFileList));
1556 0 : if (bGotFileList)
1557 : {
1558 0 : CPLDebug(GetDebugKey(), "Cannot delete filesystem with "
1559 : "non-recursive method as it is not empty");
1560 0 : errno = ENOTEMPTY;
1561 0 : return -1;
1562 : }
1563 : }
1564 :
1565 4 : if (!bIsFileSystem)
1566 : {
1567 : VSIStatBufL sStat;
1568 4 : if (VSIStatL(osDirname.c_str(), &sStat) != 0)
1569 : {
1570 1 : CPLDebug(GetDebugKey(), "Object %s does not exist",
1571 : osDirname.c_str());
1572 1 : errno = ENOENT;
1573 2 : return -1;
1574 : }
1575 3 : if (!VSI_ISDIR(sStat.st_mode))
1576 : {
1577 1 : CPLDebug(GetDebugKey(), "Object %s is not a directory",
1578 : osDirname.c_str());
1579 1 : errno = ENOTDIR;
1580 1 : return -1;
1581 : }
1582 : }
1583 :
1584 : auto poHandleHelper =
1585 : std::unique_ptr<IVSIS3LikeHandleHelper>(CreateHandleHelper(
1586 4 : osDirnameWithoutEndSlash.c_str() + GetFSPrefix().size(), false));
1587 2 : if (poHandleHelper == nullptr)
1588 : {
1589 0 : return -1;
1590 : }
1591 :
1592 2 : InvalidateCachedData(GetURLFromFilename(osDirname.c_str()).c_str());
1593 2 : InvalidateCachedData(
1594 4 : GetURLFromFilename(osDirnameWithoutEndSlash.c_str()).c_str());
1595 2 : InvalidateDirContent(CPLGetDirnameSafe(osDirnameWithoutEndSlash.c_str()));
1596 2 : if (bRecursive)
1597 : {
1598 1 : PartialClearCache(osDirnameWithoutEndSlash.c_str());
1599 : }
1600 :
1601 4 : std::string osContinuation;
1602 2 : int nRet = 0;
1603 : bool bRetry;
1604 :
1605 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszDirname));
1606 4 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1607 2 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1608 :
1609 2 : do
1610 : {
1611 2 : bRetry = false;
1612 2 : CURL *hCurlHandle = curl_easy_init();
1613 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
1614 : "DELETE");
1615 :
1616 2 : poHandleHelper->ResetQueryParameters();
1617 2 : if (bIsFileSystem)
1618 : {
1619 0 : poHandleHelper->AddQueryParameter("resource", "filesystem");
1620 : }
1621 : else
1622 : {
1623 2 : poHandleHelper->AddQueryParameter("recursive",
1624 : bRecursive ? "true" : "false");
1625 2 : if (!osContinuation.empty())
1626 0 : poHandleHelper->AddQueryParameter("continuation",
1627 : osContinuation);
1628 : }
1629 :
1630 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1631 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1632 : aosHTTPOptions.List()));
1633 2 : headers = VSICurlMergeHeaders(
1634 2 : headers, poHandleHelper->GetCurlHeaders("DELETE", headers));
1635 :
1636 4 : CurlRequestHelper requestHelper;
1637 2 : const long response_code = requestHelper.perform(
1638 : hCurlHandle, headers, this, poHandleHelper.get());
1639 :
1640 2 : NetworkStatisticsLogger::LogDELETE();
1641 :
1642 : // 200 for path deletion
1643 : // 202 for filesystem deletion
1644 2 : if (response_code != 200 && response_code != 202)
1645 : {
1646 : // Look if we should attempt a retry
1647 0 : if (oRetryContext.CanRetry(
1648 : static_cast<int>(response_code),
1649 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
1650 : requestHelper.szCurlErrBuf))
1651 : {
1652 0 : CPLError(CE_Warning, CPLE_AppDefined,
1653 : "HTTP error code: %d - %s. "
1654 : "Retrying again in %.1f secs",
1655 : static_cast<int>(response_code),
1656 0 : poHandleHelper->GetURL().c_str(),
1657 : oRetryContext.GetCurrentDelay());
1658 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1659 0 : bRetry = true;
1660 : }
1661 : else
1662 : {
1663 0 : CPLDebug(GetDebugKey(), "Delete of %s failed: %s",
1664 : osDirname.c_str(),
1665 0 : requestHelper.sWriteFuncData.pBuffer
1666 : ? requestHelper.sWriteFuncData.pBuffer
1667 : : "(null)");
1668 0 : if (requestHelper.sWriteFuncData.pBuffer != nullptr)
1669 : {
1670 0 : VSIError(VSIE_AWSError, "%s",
1671 : requestHelper.sWriteFuncData.pBuffer);
1672 0 : if (strstr(requestHelper.sWriteFuncData.pBuffer,
1673 : "PathNotFound"))
1674 : {
1675 0 : errno = ENOENT;
1676 : }
1677 0 : else if (strstr(requestHelper.sWriteFuncData.pBuffer,
1678 : "DirectoryNotEmpty"))
1679 : {
1680 0 : errno = ENOTEMPTY;
1681 : }
1682 : }
1683 0 : nRet = -1;
1684 : }
1685 : }
1686 : else
1687 : {
1688 : // Get continuation token for response headers
1689 2 : osContinuation = GetContinuationToken(
1690 2 : requestHelper.sWriteFuncHeaderData.pBuffer);
1691 2 : if (!osContinuation.empty())
1692 : {
1693 0 : oRetryContext.ResetCounter();
1694 0 : bRetry = true;
1695 : }
1696 : }
1697 :
1698 2 : curl_easy_cleanup(hCurlHandle);
1699 : } while (bRetry);
1700 :
1701 2 : return nRet;
1702 : }
1703 :
1704 : /************************************************************************/
1705 : /* Rmdir() */
1706 : /************************************************************************/
1707 :
1708 4 : int VSIADLSFSHandler::Rmdir(const char *pszDirname)
1709 : {
1710 4 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1711 1 : return -1;
1712 :
1713 6 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1714 6 : NetworkStatisticsAction oContextAction("Rmdir");
1715 :
1716 3 : return RmdirInternal(pszDirname, false);
1717 : }
1718 :
1719 : /************************************************************************/
1720 : /* RmdirRecursive() */
1721 : /************************************************************************/
1722 :
1723 1 : int VSIADLSFSHandler::RmdirRecursive(const char *pszDirname)
1724 : {
1725 1 : if (!STARTS_WITH_CI(pszDirname, GetFSPrefix().c_str()))
1726 0 : return -1;
1727 :
1728 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1729 2 : NetworkStatisticsAction oContextAction("RmdirRecursive");
1730 :
1731 1 : return RmdirInternal(pszDirname, true);
1732 : }
1733 :
1734 : /************************************************************************/
1735 : /* CopyObject() */
1736 : /************************************************************************/
1737 :
1738 4 : int VSIADLSFSHandler::CopyObject(const char *oldpath, const char *newpath,
1739 : CSLConstList /* papszMetadata */)
1740 : {
1741 : // There is no CopyObject in ADLS... So use the base Azure blob one...
1742 :
1743 8 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1744 8 : NetworkStatisticsAction oContextAction("CopyObject");
1745 :
1746 12 : std::string osTargetNameWithoutPrefix = newpath + GetFSPrefix().size();
1747 : auto poAzHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1748 4 : VSIAzureBlobHandleHelper::BuildFromURI(
1749 8 : osTargetNameWithoutPrefix.c_str(), "/vsiaz/"));
1750 4 : if (poAzHandleHelper == nullptr)
1751 : {
1752 0 : return -1;
1753 : }
1754 :
1755 12 : std::string osSourceNameWithoutPrefix = oldpath + GetFSPrefix().size();
1756 : auto poAzHandleHelperSource = std::unique_ptr<IVSIS3LikeHandleHelper>(
1757 4 : VSIAzureBlobHandleHelper::BuildFromURI(
1758 8 : osSourceNameWithoutPrefix.c_str(), "/vsiaz/"));
1759 4 : if (poAzHandleHelperSource == nullptr)
1760 : {
1761 0 : return -1;
1762 : }
1763 :
1764 8 : std::string osSourceHeader("x-ms-copy-source: ");
1765 4 : osSourceHeader += poAzHandleHelperSource->GetURLNoKVP();
1766 :
1767 4 : int nRet = 0;
1768 :
1769 : bool bRetry;
1770 :
1771 8 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(oldpath));
1772 8 : const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions);
1773 4 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1774 :
1775 4 : do
1776 : {
1777 4 : bRetry = false;
1778 4 : CURL *hCurlHandle = curl_easy_init();
1779 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
1780 :
1781 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1782 4 : CPLHTTPSetOptions(hCurlHandle, poAzHandleHelper->GetURL().c_str(),
1783 : aosHTTPOptions.List()));
1784 4 : headers = curl_slist_append(headers, osSourceHeader.c_str());
1785 4 : headers = curl_slist_append(headers, "Content-Length: 0");
1786 4 : headers = VSICurlSetContentTypeFromExt(headers, newpath);
1787 4 : headers = VSICurlMergeHeaders(
1788 4 : headers, poAzHandleHelper->GetCurlHeaders("PUT", headers));
1789 4 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1790 :
1791 8 : CurlRequestHelper requestHelper;
1792 4 : const long response_code = requestHelper.perform(
1793 : hCurlHandle, headers, this, poAzHandleHelper.get());
1794 :
1795 4 : NetworkStatisticsLogger::LogPUT(0);
1796 :
1797 4 : if (response_code != 202)
1798 : {
1799 : // Look if we should attempt a retry
1800 1 : if (oRetryContext.CanRetry(
1801 : static_cast<int>(response_code),
1802 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
1803 : requestHelper.szCurlErrBuf))
1804 : {
1805 0 : CPLError(CE_Warning, CPLE_AppDefined,
1806 : "HTTP error code: %d - %s. "
1807 : "Retrying again in %.1f secs",
1808 : static_cast<int>(response_code),
1809 0 : poAzHandleHelper->GetURL().c_str(),
1810 : oRetryContext.GetCurrentDelay());
1811 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1812 0 : bRetry = true;
1813 : }
1814 : else
1815 : {
1816 1 : CPLDebug(GetDebugKey(), "%s",
1817 1 : requestHelper.sWriteFuncData.pBuffer
1818 : ? requestHelper.sWriteFuncData.pBuffer
1819 : : "(null)");
1820 1 : CPLError(CE_Failure, CPLE_AppDefined, "Copy of %s to %s failed",
1821 : oldpath, newpath);
1822 1 : nRet = -1;
1823 : }
1824 : }
1825 : else
1826 : {
1827 : auto poADLSHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
1828 3 : VSIAzureBlobHandleHelper::BuildFromURI(
1829 9 : osTargetNameWithoutPrefix.c_str(), GetFSPrefix().c_str()));
1830 3 : if (poADLSHandleHelper != nullptr)
1831 3 : InvalidateCachedData(poADLSHandleHelper->GetURLNoKVP().c_str());
1832 :
1833 : const std::string osFilenameWithoutSlash(
1834 6 : RemoveTrailingSlash(newpath));
1835 3 : InvalidateDirContent(
1836 6 : CPLGetDirnameSafe(osFilenameWithoutSlash.c_str()));
1837 : }
1838 :
1839 4 : curl_easy_cleanup(hCurlHandle);
1840 : } while (bRetry);
1841 :
1842 4 : return nRet;
1843 : }
1844 :
1845 : /************************************************************************/
1846 : /* UploadFile() */
1847 : /************************************************************************/
1848 :
1849 25 : bool VSIADLSFSHandler::UploadFile(
1850 : const std::string &osFilename, Event event, vsi_l_offset nPosition,
1851 : const void *pabyBuffer, size_t nBufferSize,
1852 : IVSIS3LikeHandleHelper *poHandleHelper,
1853 : const CPLHTTPRetryParameters &oRetryParameters, CSLConstList papszOptions)
1854 : {
1855 50 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
1856 50 : NetworkStatisticsFile oContextFile(osFilename.c_str());
1857 50 : NetworkStatisticsAction oContextAction("UploadFile");
1858 :
1859 25 : if (event == Event::CREATE_FILE)
1860 : {
1861 8 : InvalidateCachedData(poHandleHelper->GetURLNoKVP().c_str());
1862 8 : InvalidateDirContent(CPLGetDirnameSafe(osFilename.c_str()));
1863 : }
1864 :
1865 : const CPLStringList aosHTTPOptions(
1866 50 : CPLHTTPGetOptionsFromEnv(osFilename.c_str()));
1867 :
1868 25 : bool bSuccess = true;
1869 25 : CPLHTTPRetryContext oRetryContext(oRetryParameters);
1870 : bool bRetry;
1871 25 : do
1872 : {
1873 25 : bRetry = false;
1874 :
1875 25 : CURL *hCurlHandle = curl_easy_init();
1876 :
1877 25 : poHandleHelper->ResetQueryParameters();
1878 25 : if (event == Event::CREATE_FILE)
1879 : {
1880 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/create?view=rest-storageservices-datalakestoragegen2-2019-12-12
1881 8 : poHandleHelper->AddQueryParameter("resource", "file");
1882 : }
1883 17 : else if (event == Event::APPEND_DATA)
1884 : {
1885 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update?view=rest-storageservices-datalakestoragegen2-2019-12-12
1886 10 : poHandleHelper->AddQueryParameter("action", "append");
1887 10 : poHandleHelper->AddQueryParameter(
1888 : "position",
1889 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nPosition)));
1890 : }
1891 : else
1892 : {
1893 : // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update?view=rest-storageservices-datalakestoragegen2-2019-12-12
1894 7 : poHandleHelper->AddQueryParameter("action", "flush");
1895 7 : poHandleHelper->AddQueryParameter("close", "true");
1896 7 : poHandleHelper->AddQueryParameter(
1897 : "position",
1898 : CPLSPrintf(CPL_FRMT_GUIB, static_cast<GUIntBig>(nPosition)));
1899 : }
1900 :
1901 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L);
1902 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION,
1903 : PutData::ReadCallBackBuffer);
1904 25 : PutData putData;
1905 25 : putData.pabyData = static_cast<const GByte *>(pabyBuffer);
1906 25 : putData.nOff = 0;
1907 25 : putData.nTotalSize = nBufferSize;
1908 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData);
1909 :
1910 : struct curl_slist *headers = static_cast<struct curl_slist *>(
1911 25 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
1912 : aosHTTPOptions.List()));
1913 25 : headers = VSICurlSetCreationHeadersFromOptions(headers, papszOptions,
1914 : osFilename.c_str());
1915 :
1916 50 : CPLString osContentLength; // leave it in this scope
1917 :
1918 25 : if (event == Event::APPEND_DATA)
1919 : {
1920 10 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE,
1921 : static_cast<int>(nBufferSize));
1922 : // Disable "Expect: 100-continue" which doesn't hurt, but is not
1923 : // needed
1924 10 : headers = curl_slist_append(headers, "Expect:");
1925 : osContentLength.Printf("Content-Length: %d",
1926 10 : static_cast<int>(nBufferSize));
1927 10 : headers = curl_slist_append(headers, osContentLength.c_str());
1928 : }
1929 : else
1930 : {
1931 15 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
1932 15 : headers = curl_slist_append(headers, "Content-Length: 0");
1933 : }
1934 :
1935 25 : const char *pszVerb = (event == Event::CREATE_FILE) ? "PUT" : "PATCH";
1936 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, pszVerb);
1937 25 : headers = VSICurlMergeHeaders(
1938 25 : headers, poHandleHelper->GetCurlHeaders(pszVerb, headers));
1939 25 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1940 :
1941 50 : CurlRequestHelper requestHelper;
1942 : const long response_code =
1943 25 : requestHelper.perform(hCurlHandle, headers, this, poHandleHelper);
1944 :
1945 25 : NetworkStatisticsLogger::LogPUT(
1946 : event == Event::APPEND_DATA ? nBufferSize : 0);
1947 :
1948 : // 200 for PATCH flush
1949 : // 201 for PUT create
1950 : // 202 for PATCH append
1951 25 : if (response_code != 200 && response_code != 201 &&
1952 : response_code != 202)
1953 : {
1954 : // Look if we should attempt a retry
1955 3 : if (oRetryContext.CanRetry(
1956 : static_cast<int>(response_code),
1957 3 : requestHelper.sWriteFuncHeaderData.pBuffer,
1958 : requestHelper.szCurlErrBuf))
1959 : {
1960 0 : CPLError(CE_Warning, CPLE_AppDefined,
1961 : "HTTP error code: %d - %s. "
1962 : "Retrying again in %.1f secs",
1963 : static_cast<int>(response_code),
1964 0 : poHandleHelper->GetURL().c_str(),
1965 : oRetryContext.GetCurrentDelay());
1966 0 : CPLSleep(oRetryContext.GetCurrentDelay());
1967 0 : bRetry = true;
1968 : }
1969 : else
1970 : {
1971 3 : CPLDebug(GetDebugKey(), "%s of %s failed: %s", pszVerb,
1972 : osFilename.c_str(),
1973 3 : requestHelper.sWriteFuncData.pBuffer
1974 : ? requestHelper.sWriteFuncData.pBuffer
1975 : : "(null)");
1976 3 : bSuccess = false;
1977 : }
1978 : }
1979 :
1980 25 : curl_easy_cleanup(hCurlHandle);
1981 : } while (bRetry);
1982 :
1983 50 : return bSuccess;
1984 : }
1985 :
1986 : /************************************************************************/
1987 : /* GetFileList() */
1988 : /************************************************************************/
1989 :
1990 4 : char **VSIADLSFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
1991 : bool *pbGotFileList)
1992 : {
1993 4 : return GetFileList(pszDirname, nMaxFiles, true, pbGotFileList);
1994 : }
1995 :
1996 4 : char **VSIADLSFSHandler::GetFileList(const char *pszDirname, int nMaxFiles,
1997 : bool bCacheEntries, bool *pbGotFileList)
1998 : {
1999 : if (ENABLE_DEBUG)
2000 : CPLDebug(GetDebugKey(), "GetFileList(%s)", pszDirname);
2001 :
2002 4 : *pbGotFileList = false;
2003 :
2004 : char **papszOptions =
2005 4 : CSLSetNameValue(nullptr, "MAXFILES", CPLSPrintf("%d", nMaxFiles));
2006 4 : papszOptions = CSLSetNameValue(papszOptions, "CACHE_ENTRIES",
2007 : bCacheEntries ? "YES" : "NO");
2008 4 : auto dir = OpenDir(pszDirname, 0, papszOptions);
2009 4 : CSLDestroy(papszOptions);
2010 4 : if (!dir)
2011 : {
2012 1 : return nullptr;
2013 : }
2014 6 : CPLStringList aosFileList;
2015 : while (true)
2016 : {
2017 8 : auto entry = dir->NextDirEntry();
2018 8 : if (!entry)
2019 : {
2020 3 : break;
2021 : }
2022 5 : aosFileList.AddString(entry->pszName);
2023 :
2024 5 : if (nMaxFiles > 0 && aosFileList.size() >= nMaxFiles)
2025 0 : break;
2026 5 : }
2027 3 : delete dir;
2028 3 : *pbGotFileList = true;
2029 3 : return aosFileList.StealList();
2030 : }
2031 :
2032 : /************************************************************************/
2033 : /* GetOptions() */
2034 : /************************************************************************/
2035 :
2036 1 : const char *VSIADLSFSHandler::GetOptions()
2037 : {
2038 : static std::string osOptions(
2039 2 : std::string("<Options>") +
2040 : " <Option name='AZURE_STORAGE_CONNECTION_STRING' type='string' "
2041 : "description='Connection string that contains account name and "
2042 : "secret key'/>"
2043 : " <Option name='AZURE_STORAGE_ACCOUNT' type='string' "
2044 : "description='Storage account. To use with AZURE_STORAGE_ACCESS_KEY'/>"
2045 : " <Option name='AZURE_STORAGE_ACCESS_KEY' type='string' "
2046 : "description='Secret key'/>"
2047 : " <Option name='VSIAZ_CHUNK_SIZE' type='int' "
2048 : "description='Size in MB for chunks of files that are uploaded' "
2049 3 : "default='4' min='1' max='4'/>" +
2050 2 : VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
2051 1 : return osOptions.c_str();
2052 : }
2053 :
2054 : /************************************************************************/
2055 : /* GetSignedURL() */
2056 : /************************************************************************/
2057 :
2058 2 : char *VSIADLSFSHandler::GetSignedURL(const char *pszFilename,
2059 : CSLConstList papszOptions)
2060 : {
2061 2 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
2062 0 : return nullptr;
2063 :
2064 : auto poHandleHelper = std::unique_ptr<VSIAzureBlobHandleHelper>(
2065 : VSIAzureBlobHandleHelper::BuildFromURI(pszFilename +
2066 2 : GetFSPrefix().size(),
2067 : "/vsiaz/", // use Azure blob
2068 4 : nullptr, papszOptions));
2069 2 : if (poHandleHelper == nullptr)
2070 : {
2071 1 : return nullptr;
2072 : }
2073 :
2074 2 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
2075 :
2076 1 : return CPLStrdup(osRet.c_str());
2077 : }
2078 :
2079 : /************************************************************************/
2080 : /* OpenDir() */
2081 : /************************************************************************/
2082 :
2083 10 : VSIDIR *VSIADLSFSHandler::OpenDir(const char *pszPath, int nRecurseDepth,
2084 : const char *const *papszOptions)
2085 : {
2086 10 : if (nRecurseDepth > 0)
2087 : {
2088 0 : return VSIFilesystemHandler::OpenDir(pszPath, nRecurseDepth,
2089 0 : papszOptions);
2090 : }
2091 :
2092 10 : if (!STARTS_WITH_CI(pszPath, GetFSPrefix().c_str()))
2093 0 : return nullptr;
2094 :
2095 20 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
2096 20 : NetworkStatisticsAction oContextAction("OpenDir");
2097 :
2098 : const std::string osDirnameWithoutPrefix =
2099 30 : RemoveTrailingSlash(pszPath + GetFSPrefix().size());
2100 20 : std::string osFilesystem(osDirnameWithoutPrefix);
2101 20 : std::string osObjectKey;
2102 10 : size_t nSlashPos = osDirnameWithoutPrefix.find('/');
2103 10 : if (nSlashPos != std::string::npos)
2104 : {
2105 3 : osFilesystem = osDirnameWithoutPrefix.substr(0, nSlashPos);
2106 3 : osObjectKey = osDirnameWithoutPrefix.substr(nSlashPos + 1);
2107 : }
2108 :
2109 10 : VSIDIRADLS *dir = new VSIDIRADLS(this);
2110 10 : dir->m_nRecurseDepth = nRecurseDepth;
2111 10 : dir->m_poFS = this;
2112 10 : dir->m_bRecursiveRequestFromAccountRoot =
2113 10 : osFilesystem.empty() && nRecurseDepth < 0;
2114 10 : dir->m_osFilesystem = std::move(osFilesystem);
2115 10 : dir->m_osObjectKey = std::move(osObjectKey);
2116 10 : dir->m_nMaxFiles =
2117 10 : atoi(CSLFetchNameValueDef(papszOptions, "MAXFILES", "0"));
2118 10 : dir->m_bCacheEntries =
2119 10 : CPLTestBool(CSLFetchNameValueDef(papszOptions, "CACHE_ENTRIES", "YES"));
2120 10 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
2121 10 : if (!dir->IssueListDir())
2122 : {
2123 1 : delete dir;
2124 1 : return nullptr;
2125 : }
2126 :
2127 9 : return dir;
2128 : }
2129 :
2130 : /************************************************************************/
2131 : /* GetStreamingFilename() */
2132 : /************************************************************************/
2133 :
2134 : std::string
2135 0 : VSIADLSFSHandler::GetStreamingFilename(const std::string &osFilename) const
2136 : {
2137 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
2138 0 : return "/vsiaz_streaming/" + osFilename.substr(GetFSPrefix().size());
2139 0 : return osFilename;
2140 : }
2141 :
2142 : /************************************************************************/
2143 : /* VSIADLSHandle() */
2144 : /************************************************************************/
2145 :
2146 25 : VSIADLSHandle::VSIADLSHandle(VSIADLSFSHandler *poFSIn, const char *pszFilename,
2147 25 : VSIAzureBlobHandleHelper *poHandleHelper)
2148 25 : : VSICurlHandle(poFSIn, pszFilename, poHandleHelper->GetURLNoKVP().c_str()),
2149 50 : m_poHandleHelper(poHandleHelper)
2150 : {
2151 25 : m_osQueryString = poHandleHelper->GetSASQueryString();
2152 25 : }
2153 :
2154 : /************************************************************************/
2155 : /* GetCurlHeaders() */
2156 : /************************************************************************/
2157 :
2158 : struct curl_slist *
2159 21 : VSIADLSHandle::GetCurlHeaders(const std::string &osVerb,
2160 : const struct curl_slist *psExistingHeaders)
2161 : {
2162 21 : return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
2163 : }
2164 :
2165 : } /* end of namespace cpl */
2166 :
2167 : #endif // DOXYGEN_SKIP
2168 : //! @endcond
2169 :
2170 : /************************************************************************/
2171 : /* VSIInstallADLSFileHandler() */
2172 : /************************************************************************/
2173 :
2174 : /*!
2175 : \brief Install /vsiaz/ Microsoft Azure Data Lake Storage Gen2 file system
2176 : handler (requires libcurl)
2177 :
2178 : \verbatim embed:rst
2179 : See :ref:`/vsiadls/ documentation <vsiadls>`
2180 : \endverbatim
2181 :
2182 : @since GDAL 3.3
2183 : */
2184 :
2185 1392 : void VSIInstallADLSFileHandler(void)
2186 : {
2187 1392 : VSIFileManager::InstallHandler("/vsiadls/", new cpl::VSIADLSFSHandler);
2188 1392 : }
2189 :
2190 : #endif /* HAVE_CURL */
|