Coverage Report

Created: 2026-06-30 08:33

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