1 /******************************************************************************
2 *
3 * Project: CPL - Common Portability Library
4 * Purpose: Implement VSI large file api for WebHDFS REST API
5 * Author: Even Rouault, even.rouault at spatialys.com
6 *
7 ******************************************************************************
8 * Copyright (c) 2018, Even Rouault <even.rouault at spatialys.com>
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a
11 * copy of this software and associated documentation files (the "Software"),
12 * to deal in the Software without restriction, including without limitation
13 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 * and/or sell copies of the Software, and to permit persons to whom the
15 * Software is furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included
18 * in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 * DEALINGS IN THE SOFTWARE.
27 ****************************************************************************/
28
29 #include "cpl_port.h"
30 #include "cpl_http.h"
31 #include "cpl_json.h"
32 #include "cpl_vsil_curl_priv.h"
33 #include "cpl_vsil_curl_class.h"
34
35 #include <errno.h>
36
37 #include <algorithm>
38 #include <set>
39 #include <map>
40 #include <memory>
41
42 #include "cpl_alibaba_oss.h"
43
44 CPL_CVSID("$Id: cpl_vsil_webhdfs.cpp 818288a58e026803402172eaa5a53bcc31ebf7aa 2021-03-14 17:20:00 +0300 drons $")
45
46 #ifndef HAVE_CURL
47
VSIInstallWebHdfsHandler(void)48 void VSIInstallWebHdfsHandler( void )
49 {
50 // Not supported
51 }
52
53
54 #else
55
56 #if !CURL_AT_LEAST_VERSION(7,18,2)
57 // Needed for CURLINFO_REDIRECT_URL
58 #error Need libcurl version 7.18.2 or newer
59 #endif
60
61 //! @cond Doxygen_Suppress
62 #ifndef DOXYGEN_SKIP
63
64 #define ENABLE_DEBUG 0
65
66 namespace cpl {
67
68
69
70 /************************************************************************/
71 /* VSIWebHDFSFSHandler */
72 /************************************************************************/
73
74 class VSIWebHDFSFSHandler final : public VSICurlFilesystemHandler
75 {
76 CPL_DISALLOW_COPY_ASSIGN(VSIWebHDFSFSHandler)
77
78 protected:
79 VSICurlHandle* CreateFileHandle( const char* pszFilename ) override;
80
81 int HasOptimizedReadMultiRange( const char* /* pszPath */ )
82 override { return false; }
83
84 char** GetFileList(const char *pszFilename,
85 int nMaxFiles,
86 bool* pbGotFileList) override;
87
88 CPLString GetURLFromFilename( const CPLString& osFilename ) override;
89
90 public:
91 VSIWebHDFSFSHandler() = default;
92 ~VSIWebHDFSFSHandler() override = default;
93
94 VSIVirtualHandle *Open( const char *pszFilename,
95 const char *pszAccess,
96 bool bSetError,
97 CSLConstList papszOptions ) override;
98 int Unlink( const char *pszFilename ) override;
99 int Rmdir( const char *pszFilename ) override;
100 int Mkdir( const char *pszDirname, long nMode ) override;
101
102 CPLString GetFSPrefix() const override { return "/vsiwebhdfs/"; }
103
104 const char* GetOptions() override;
105 };
106
107 /************************************************************************/
108 /* VSIWebHDFSHandle */
109 /************************************************************************/
110
111 class VSIWebHDFSHandle final : public VSICurlHandle
112 {
113 CPL_DISALLOW_COPY_ASSIGN(VSIWebHDFSHandle)
114
115 CPLString m_osDataNodeHost{};
116 CPLString m_osUsernameParam{};
117 CPLString m_osDelegationParam{};
118
119 std::string DownloadRegion(vsi_l_offset startOffset, int nBlocks) override;
120
121 public:
122 VSIWebHDFSHandle( VSIWebHDFSFSHandler* poFS,
123 const char* pszFilename, const char* pszURL );
124 ~VSIWebHDFSHandle() override = default;
125
126 int ReadMultiRange( int nRanges, void ** ppData,
127 const vsi_l_offset* panOffsets,
128 const size_t* panSizes ) override {
129 return VSIVirtualHandle::ReadMultiRange( nRanges, ppData,
130 panOffsets, panSizes );
131 }
132
133 vsi_l_offset GetFileSize( bool bSetError ) override;
134 };
135
136 /************************************************************************/
137 /* PatchWebHDFSUrl() */
138 /************************************************************************/
139
140 static CPLString PatchWebHDFSUrl(const CPLString& osURLIn,
141 const CPLString& osNewHost)
142 {
143 CPLString osURL(osURLIn);
144 size_t nStart = 0;
145 if( osURL.find("http://") == 0 )
146 nStart = strlen("http://");
147 else if( osURL.find("https://") == 0 )
148 nStart = strlen("https://");
149 if( nStart )
150 {
151 size_t nHostEnd = osURL.find(':', nStart);
152 if( nHostEnd != std::string::npos )
153 {
154 osURL = osURL.substr(0, nStart) + osNewHost +
155 osURL.substr(nHostEnd);
156 }
157 }
158 return osURL;
159 }
160
161 /************************************************************************/
162 /* GetWebHDFSDataNodeHost() */
163 /************************************************************************/
164
165 static CPLString GetWebHDFSDataNodeHost()
166 {
167 CPLString osDataNodeHost = CPLGetConfigOption("WEBHDFS_DATANODE_HOST", "");
168 return osDataNodeHost;
169 }
170
171 /************************************************************************/
172 /* VSIWebHDFSWriteHandle */
173 /************************************************************************/
174
175 class VSIWebHDFSWriteHandle final : public VSIAppendWriteHandle
176 {
177 CPL_DISALLOW_COPY_ASSIGN(VSIWebHDFSWriteHandle)
178
179 CPLString m_osURL{};
180 CPLString m_osDataNodeHost{};
181 CPLString m_osUsernameParam{};
182 CPLString m_osDelegationParam{};
183
184 bool Send(bool bIsLastBlock) override;
185 bool CreateFile();
186 bool Append();
187
188 void InvalidateParentDirectory();
189
190 public:
191 VSIWebHDFSWriteHandle( VSIWebHDFSFSHandler* poFS,
192 const char* pszFilename );
193 virtual ~VSIWebHDFSWriteHandle();
194 };
195
196 /************************************************************************/
197 /* GetWebHDFSBufferSize() */
198 /************************************************************************/
199
200 static int GetWebHDFSBufferSize()
201 {
202 int nBufferSize;
203 int nChunkSizeMB = atoi(CPLGetConfigOption("VSIWEBHDFS_SIZE", "4"));
204 if( nChunkSizeMB <= 0 || nChunkSizeMB > 1000 )
205 nBufferSize = 4 * 1024 * 1024;
206 else
207 nBufferSize = nChunkSizeMB * 1024 * 1024;
208
209 // For testing only !
210 const char* pszChunkSizeBytes =
211 CPLGetConfigOption("VSIWEBHDFS_SIZE_BYTES", nullptr);
212 if( pszChunkSizeBytes )
213 nBufferSize = atoi(pszChunkSizeBytes);
214 if( nBufferSize <= 0 || nBufferSize > 1000 * 1024 * 1024 )
215 nBufferSize = 4 * 1024 * 1024;
216 return nBufferSize;
217 }
218
219 /************************************************************************/
220 /* VSIWebHDFSWriteHandle() */
221 /************************************************************************/
222
223 VSIWebHDFSWriteHandle::VSIWebHDFSWriteHandle( VSIWebHDFSFSHandler* poFS,
224 const char* pszFilename) :
225 VSIAppendWriteHandle(poFS, poFS->GetFSPrefix(), pszFilename,
226 GetWebHDFSBufferSize()),
227 m_osURL( pszFilename + poFS->GetFSPrefix().size() ),
228 m_osDataNodeHost( GetWebHDFSDataNodeHost() )
229 {
230 // cppcheck-suppress useInitializationList
231 m_osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
232 if( !m_osUsernameParam.empty() )
233 m_osUsernameParam = "&user.name=" + m_osUsernameParam;
234 m_osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
235 if( !m_osDelegationParam.empty() )
236 m_osDelegationParam = "&delegation=" + m_osDelegationParam;
237
238 if( m_pabyBuffer != nullptr && !CreateFile() )
239 {
240 CPLFree(m_pabyBuffer);
241 m_pabyBuffer = nullptr;
242 }
243 }
244
245 /************************************************************************/
246 /* ~VSIWebHDFSWriteHandle() */
247 /************************************************************************/
248
249 VSIWebHDFSWriteHandle::~VSIWebHDFSWriteHandle()
250 {
251 Close();
252 }
253
254 /************************************************************************/
255 /* InvalidateParentDirectory() */
256 /************************************************************************/
257
258 void VSIWebHDFSWriteHandle::InvalidateParentDirectory()
259 {
260 m_poFS->InvalidateCachedData(m_osURL);
261
262 CPLString osFilenameWithoutSlash(m_osFilename);
263 if( !osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/' )
264 osFilenameWithoutSlash.resize( osFilenameWithoutSlash.size() - 1 );
265 m_poFS->InvalidateDirContent( CPLGetDirname(osFilenameWithoutSlash) );
266 }
267
268 /************************************************************************/
269 /* Send() */
270 /************************************************************************/
271
272 bool VSIWebHDFSWriteHandle::Send(bool /* bIsLastBlock */)
273 {
274 if( m_nCurOffset > 0 )
275 return Append();
276 return true;
277 }
278
279 /************************************************************************/
280 /* CreateFile() */
281 /************************************************************************/
282
283 bool VSIWebHDFSWriteHandle::CreateFile()
284 {
285 if( m_osUsernameParam.empty() && m_osDelegationParam.empty() )
286 {
287 CPLError(CE_Failure, CPLE_AppDefined,
288 "Configuration option WEBHDFS_USERNAME or WEBHDFS_DELEGATION "
289 "should be defined");
290 return false;
291 }
292
293 NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix());
294 NetworkStatisticsFile oContextFile(m_osFilename);
295 NetworkStatisticsAction oContextAction("Write");
296
297 CPLString osURL = m_osURL + "?op=CREATE&overwrite=true" +
298 m_osUsernameParam + m_osDelegationParam;
299
300 CPLString osPermission = CPLGetConfigOption("WEBHDFS_PERMISSION", "");
301 if( !osPermission.empty() )
302 osURL += "&permission=" + osPermission;
303
304 CPLString osReplication = CPLGetConfigOption("WEBHDFS_REPLICATION", "");
305 if( !osReplication.empty() )
306 osURL += "&replication=" + osReplication;
307
308 bool bInRedirect = false;
309
310 retry:
311 CURL* hCurlHandle = curl_easy_init();
312
313 struct curl_slist* headers = static_cast<struct curl_slist*>(
314 CPLHTTPSetOptions(hCurlHandle, osURL.c_str(), nullptr));
315
316 curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
317 curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0);
318
319 if( !m_osDataNodeHost.empty() )
320 {
321 curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
322 }
323
324 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
325
326 WriteFuncStruct sWriteFuncData;
327 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
328 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
329 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
330 VSICurlHandleWriteFunc);
331
332 MultiPerform(m_poFS->GetCurlMultiHandleFor(m_osURL), hCurlHandle);
333
334 curl_slist_free_all(headers);
335
336 NetworkStatisticsLogger::LogPUT(0);
337
338 long response_code = 0;
339 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
340
341 if( !bInRedirect )
342 {
343 char *pszRedirectURL = nullptr;
344 curl_easy_getinfo(hCurlHandle, CURLINFO_REDIRECT_URL, &pszRedirectURL);
345 if( pszRedirectURL &&
346 strstr(pszRedirectURL, osURL.c_str()) == nullptr )
347 {
348 CPLDebug("WEBHDFS", "Redirect URL: %s", pszRedirectURL);
349
350 bInRedirect = true;
351 osURL = pszRedirectURL;
352 if( !m_osDataNodeHost.empty() )
353 {
354 osURL = PatchWebHDFSUrl(osURL, m_osDataNodeHost);
355 }
356
357 curl_easy_cleanup(hCurlHandle);
358 CPLFree(sWriteFuncData.pBuffer);
359
360 goto retry;
361 }
362 }
363
364 curl_easy_cleanup(hCurlHandle);
365
366 if( response_code == 201 )
367 {
368 InvalidateParentDirectory();
369 }
370 else
371 {
372 CPLDebug("WEBHDFS",
373 "%s",
374 sWriteFuncData.pBuffer
375 ? sWriteFuncData.pBuffer
376 : "(null)");
377 CPLError(CE_Failure, CPLE_AppDefined,
378 "PUT of %s failed",
379 m_osURL.c_str());
380 }
381 CPLFree(sWriteFuncData.pBuffer);
382
383 return response_code == 201;
384 }
385
386 /************************************************************************/
387 /* Append() */
388 /************************************************************************/
389
390 bool VSIWebHDFSWriteHandle::Append()
391 {
392 NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix());
393 NetworkStatisticsFile oContextFile(m_osFilename);
394 NetworkStatisticsAction oContextAction("Write");
395
396 CPLString osURL = m_osURL + "?op=APPEND" +
397 m_osUsernameParam + m_osDelegationParam;
398
399 CURL* hCurlHandle = curl_easy_init();
400
401 struct curl_slist* headers = static_cast<struct curl_slist*>(
402 CPLHTTPSetOptions(hCurlHandle, osURL.c_str(), nullptr));
403
404 curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "POST");
405 curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
406 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
407
408 WriteFuncStruct sWriteFuncData;
409 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
410 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
411 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
412 VSICurlHandleWriteFunc);
413
414 MultiPerform(m_poFS->GetCurlMultiHandleFor(m_osURL), hCurlHandle);
415
416 curl_slist_free_all(headers);
417
418 NetworkStatisticsLogger::LogPOST(0, 0);
419
420 long response_code = 0;
421 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
422
423 if( response_code != 307 )
424 {
425 CPLDebug("WEBHDFS",
426 "%s",
427 sWriteFuncData.pBuffer
428 ? sWriteFuncData.pBuffer
429 : "(null)");
430 CPLError(CE_Failure, CPLE_AppDefined,
431 "POST of %s failed",
432 m_osURL.c_str());
433 curl_easy_cleanup(hCurlHandle);
434 CPLFree(sWriteFuncData.pBuffer);
435 return false;
436 }
437
438 char *pszRedirectURL = nullptr;
439 curl_easy_getinfo(hCurlHandle, CURLINFO_REDIRECT_URL, &pszRedirectURL);
440 if( pszRedirectURL == nullptr )
441 {
442 curl_easy_cleanup(hCurlHandle);
443 CPLFree(sWriteFuncData.pBuffer);
444 return false;
445 }
446 CPLDebug("WEBHDFS", "Redirect URL: %s", pszRedirectURL);
447
448 osURL = pszRedirectURL;
449 if( !m_osDataNodeHost.empty() )
450 {
451 osURL = PatchWebHDFSUrl(osURL, m_osDataNodeHost);
452 }
453
454 curl_easy_cleanup(hCurlHandle);
455 CPLFree(sWriteFuncData.pBuffer);
456
457
458 // After redirection
459
460 hCurlHandle = curl_easy_init();
461
462 headers = static_cast<struct curl_slist*>(
463 CPLHTTPSetOptions(hCurlHandle, osURL.c_str(), nullptr));
464 headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
465
466 curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, m_pabyBuffer);
467 curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDSIZE , m_nBufferOff);
468 curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
469 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
470
471 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
472 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
473 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
474 VSICurlHandleWriteFunc);
475
476 MultiPerform(m_poFS->GetCurlMultiHandleFor(m_osURL), hCurlHandle);
477
478 curl_slist_free_all(headers);
479
480 NetworkStatisticsLogger::LogPOST(m_nBufferOff, 0);
481
482 response_code = 0;
483 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
484
485 curl_easy_cleanup(hCurlHandle);
486
487 if( response_code != 200 )
488 {
489 CPLDebug("WEBHDFS",
490 "%s",
491 sWriteFuncData.pBuffer
492 ? sWriteFuncData.pBuffer
493 : "(null)");
494 CPLError(CE_Failure, CPLE_AppDefined,
495 "POST of %s failed",
496 m_osURL.c_str());
497 }
498 CPLFree(sWriteFuncData.pBuffer);
499
500 return response_code == 200;
501 }
502
503 /************************************************************************/
504 /* Open() */
505 /************************************************************************/
506
507 VSIVirtualHandle* VSIWebHDFSFSHandler::Open( const char *pszFilename,
508 const char *pszAccess,
509 bool bSetError,
510 CSLConstList papszOptions )
511 {
512 if( !STARTS_WITH_CI(pszFilename, GetFSPrefix()) )
513 return nullptr;
514
515 if( strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, 'a') != nullptr )
516 {
517 if( strchr(pszAccess, '+') != nullptr &&
518 !CPLTestBool(CPLGetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) )
519 {
520 CPLError(CE_Failure, CPLE_AppDefined,
521 "w+ not supported for /vsiwebhdfs, unless "
522 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE is set to YES");
523 errno = EACCES;
524 return nullptr;
525 }
526
527 VSIWebHDFSWriteHandle* poHandle =
528 new VSIWebHDFSWriteHandle(this, pszFilename);
529 if( !poHandle->IsOK() )
530 {
531 delete poHandle;
532 return nullptr;
533 }
534 if( strchr(pszAccess, '+') != nullptr)
535 {
536 return VSICreateUploadOnCloseFile(poHandle);
537 }
538 return poHandle;
539 }
540
541 return
542 VSICurlFilesystemHandler::Open(pszFilename, pszAccess, bSetError, papszOptions);
543 }
544
545 /************************************************************************/
546 /* GetOptions() */
547 /************************************************************************/
548
549 const char* VSIWebHDFSFSHandler::GetOptions()
550 {
551 static CPLString osOptions(
552 CPLString("<Options>") +
553 " <Option name='WEBHDFS_USERNAME' type='string' "
554 "description='username (when security is off)'/>"
555 " <Option name='WEBHDFS_DELEGATION' type='string' "
556 "description='Hadoop delegation token (when security is on)'/>"
557 " <Option name='WEBHDFS_DATANODE_HOST' type='string' "
558 "description='For APIs using redirect, substitute the redirection "
559 "hostname with the one provided by this option (normally resolvable "
560 "hostname should be rewritten by a proxy)'/>"
561 " <Option name='WEBHDFS_REPLICATION' type='integer' "
562 "description='Replication value used when creating a file'/>"
563 " <Option name='WEBHDFS_PERMISSION' type='integer' "
564 "description='Permission mask (to provide as decimal number) when "
565 "creating a file or directory'/>" +
566 VSICurlFilesystemHandler::GetOptionsStatic() +
567 "</Options>");
568 return osOptions.c_str();
569 }
570
571 /************************************************************************/
572 /* CreateFileHandle() */
573 /************************************************************************/
574
575 VSICurlHandle* VSIWebHDFSFSHandler::CreateFileHandle(const char* pszFilename)
576 {
577 return new VSIWebHDFSHandle(this, pszFilename,
578 pszFilename + GetFSPrefix().size());
579 }
580
581 /************************************************************************/
582 /* GetURLFromFilename() */
583 /************************************************************************/
584
585 CPLString VSIWebHDFSFSHandler::GetURLFromFilename( const CPLString& osFilename )
586 {
587 return osFilename.substr(GetFSPrefix().size());
588 }
589 /************************************************************************/
590 /* GetFileList() */
591 /************************************************************************/
592
593 char** VSIWebHDFSFSHandler::GetFileList( const char *pszDirname,
594 int /*nMaxFiles*/,
595 bool* pbGotFileList )
596 {
597 if( ENABLE_DEBUG )
598 CPLDebug("WEBHDFS", "GetFileList(%s)" , pszDirname);
599 *pbGotFileList = false;
600
601 NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
602 NetworkStatisticsAction oContextAction("ListBucket");
603
604 CPLAssert( strlen(pszDirname) >= GetFSPrefix().size() );
605 CPLString osDirnameWithoutPrefix = pszDirname + GetFSPrefix().size();
606
607 CPLString osBaseURL = osDirnameWithoutPrefix;
608 if( !osBaseURL.empty() && osBaseURL.back() != '/' )
609 osBaseURL += '/';
610
611 CURLM* hCurlMultiHandle = GetCurlMultiHandleFor(osBaseURL);
612
613 CPLString osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
614 if( !osUsernameParam.empty() )
615 osUsernameParam = "&user.name=" + osUsernameParam;
616 CPLString osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
617 if( !osDelegationParam.empty() )
618 osDelegationParam = "&delegation=" + osDelegationParam;
619 CPLString osURL = osBaseURL + "?op=LISTSTATUS" + osUsernameParam + osDelegationParam;
620
621 CURL* hCurlHandle = curl_easy_init();
622
623 struct curl_slist* headers =
624 VSICurlSetOptions(hCurlHandle, osURL, nullptr);
625
626 WriteFuncStruct sWriteFuncData;
627 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
628 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
629 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
630 VSICurlHandleWriteFunc);
631
632 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
633
634 MultiPerform(hCurlMultiHandle, hCurlHandle);
635
636 VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
637
638 curl_slist_free_all(headers);
639
640 NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
641
642 long response_code = 0;
643 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
644
645 CPLStringList aosList;
646 bool bOK = false;
647 if( response_code == 200 && sWriteFuncData.pBuffer )
648 {
649 CPLJSONDocument oDoc;
650 if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
651 {
652 CPLJSONArray oFileStatus = oDoc.GetRoot().GetArray("FileStatuses/FileStatus");
653 bOK = oFileStatus.IsValid();
654 for( int i = 0; i < oFileStatus.Size(); i++ )
655 {
656 CPLJSONObject oItem = oFileStatus[i];
657 vsi_l_offset fileSize = oItem.GetLong("length");
658 size_t mTime = static_cast<size_t>(
659 oItem.GetLong("modificationTime") / 1000);
660 bool bIsDirectory = oItem.GetString("type") == "DIRECTORY";
661 CPLString osName = oItem.GetString("pathSuffix");
662 // can be empty if we for example ask to list a file: in that
663 // case the file entry is reported but with an empty pathSuffix
664 if( !osName.empty() )
665 {
666 aosList.AddString(osName);
667
668 FileProp prop;
669 prop.eExists = EXIST_YES;
670 prop.bIsDirectory = bIsDirectory;
671 prop.bHasComputedFileSize = true;
672 prop.fileSize = fileSize;
673 prop.mTime = mTime;
674 CPLString osCachedFilename(osBaseURL + osName);
675 #if DEBUG_VERBOSE
676 CPLDebug("WEBHDFS", "Cache %s", osCachedFilename.c_str());
677 #endif
678 SetCachedFileProp(osCachedFilename, prop);
679 }
680 }
681 }
682 }
683
684 *pbGotFileList = bOK;
685
686 CPLFree(sWriteFuncData.pBuffer);
687 curl_easy_cleanup(hCurlHandle);
688
689 if( bOK )
690 return aosList.StealList();
691 else
692 return nullptr;
693 }
694
695 /************************************************************************/
696 /* Unlink() */
697 /************************************************************************/
698
699 int VSIWebHDFSFSHandler::Unlink( const char *pszFilename )
700 {
701 if( !STARTS_WITH_CI(pszFilename, GetFSPrefix()) )
702 return -1;
703
704 NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
705 NetworkStatisticsAction oContextAction("Unlink");
706
707 CPLString osBaseURL = GetURLFromFilename(pszFilename);
708
709 CURLM* hCurlMultiHandle = GetCurlMultiHandleFor(osBaseURL);
710
711 CPLString osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
712 if( !osUsernameParam.empty() )
713 osUsernameParam = "&user.name=" + osUsernameParam;
714 CPLString osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
715 if( !osDelegationParam.empty() )
716 osDelegationParam = "&delegation=" + osDelegationParam;
717 CPLString osURL = osBaseURL + "?op=DELETE" + osUsernameParam + osDelegationParam;
718
719 CURL* hCurlHandle = curl_easy_init();
720
721 curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "DELETE");
722
723 struct curl_slist* headers =
724 VSICurlSetOptions(hCurlHandle, osURL, nullptr);
725
726 WriteFuncStruct sWriteFuncData;
727 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
728 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
729 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
730 VSICurlHandleWriteFunc);
731
732 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
733
734 MultiPerform(hCurlMultiHandle, hCurlHandle);
735
736 VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
737
738 curl_slist_free_all(headers);
739
740 NetworkStatisticsLogger::LogDELETE();
741
742 long response_code = 0;
743 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
744
745 CPLStringList aosList;
746 bool bOK = false;
747 if( response_code == 200 && sWriteFuncData.pBuffer )
748 {
749 CPLJSONDocument oDoc;
750 if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
751 {
752 bOK = oDoc.GetRoot().GetBool("boolean");
753 }
754 }
755 if( bOK )
756 {
757 InvalidateCachedData(osBaseURL);
758
759 CPLString osFilenameWithoutSlash(pszFilename);
760 if( !osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/' )
761 osFilenameWithoutSlash.resize( osFilenameWithoutSlash.size() - 1 );
762
763 InvalidateDirContent( CPLGetDirname(osFilenameWithoutSlash) );
764 }
765 else
766 {
767 CPLDebug("WEBHDFS",
768 "%s",
769 sWriteFuncData.pBuffer
770 ? sWriteFuncData.pBuffer
771 : "(null)");
772 }
773
774 CPLFree(sWriteFuncData.pBuffer);
775 curl_easy_cleanup(hCurlHandle);
776
777 return bOK ? 0 : -1;
778 }
779
780 /************************************************************************/
781 /* Rmdir() */
782 /************************************************************************/
783
784 int VSIWebHDFSFSHandler::Rmdir( const char *pszFilename )
785 {
786 NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
787 NetworkStatisticsAction oContextAction("Rmdir");
788
789 return Unlink(pszFilename);
790 }
791
792 /************************************************************************/
793 /* Mkdir() */
794 /************************************************************************/
795
796 int VSIWebHDFSFSHandler::Mkdir( const char *pszDirname, long nMode )
797 {
798 if( !STARTS_WITH_CI(pszDirname, GetFSPrefix()) )
799 return -1;
800
801 CPLString osDirnameWithoutEndSlash(pszDirname);
802 if( !osDirnameWithoutEndSlash.empty() &&
803 osDirnameWithoutEndSlash.back() == '/' )
804 {
805 osDirnameWithoutEndSlash.resize( osDirnameWithoutEndSlash.size() - 1 );
806 }
807
808 if( osDirnameWithoutEndSlash.find("/webhdfs/v1") ==
809 osDirnameWithoutEndSlash.size() - strlen("/webhdfs/v1") &&
810 std::count(osDirnameWithoutEndSlash.begin(),
811 osDirnameWithoutEndSlash.end(),'/') == 6 )
812 {
813 // The server does weird things (creating a webhdfs/v1 subfolder)
814 // if we provide the root directory like
815 // /vsiwebhdfs/http://localhost:50070/webhdfs/v1
816 return -1;
817 }
818
819 NetworkStatisticsFileSystem oContextFS(GetFSPrefix());
820 NetworkStatisticsAction oContextAction("Mkdir");
821
822 CPLString osBaseURL = GetURLFromFilename(osDirnameWithoutEndSlash);
823
824 CURLM* hCurlMultiHandle = GetCurlMultiHandleFor(osBaseURL);
825
826 CPLString osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
827 if( !osUsernameParam.empty() )
828 osUsernameParam = "&user.name=" + osUsernameParam;
829 CPLString osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
830 if( !osDelegationParam.empty() )
831 osDelegationParam = "&delegation=" + osDelegationParam;
832 CPLString osURL = osBaseURL + "?op=MKDIRS" + osUsernameParam + osDelegationParam;
833 if( nMode )
834 {
835 osURL += "&permission=";
836 osURL += CPLSPrintf("%o", static_cast<int>(nMode));
837 }
838
839 CURL* hCurlHandle = curl_easy_init();
840
841 curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
842
843 struct curl_slist* headers =
844 VSICurlSetOptions(hCurlHandle, osURL, nullptr);
845
846 WriteFuncStruct sWriteFuncData;
847 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
848 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
849 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
850 VSICurlHandleWriteFunc);
851
852 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
853
854 MultiPerform(hCurlMultiHandle, hCurlHandle);
855
856 VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
857
858 curl_slist_free_all(headers);
859
860 NetworkStatisticsLogger::LogPUT(0);
861
862 long response_code = 0;
863 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
864
865 CPLStringList aosList;
866 bool bOK = false;
867 if( response_code == 200 && sWriteFuncData.pBuffer )
868 {
869 CPLJSONDocument oDoc;
870 if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
871 {
872 bOK = oDoc.GetRoot().GetBool("boolean");
873 }
874 }
875 if( bOK )
876 {
877 InvalidateDirContent( CPLGetDirname(osDirnameWithoutEndSlash) );
878
879 FileProp cachedFileProp;
880 cachedFileProp.eExists = EXIST_YES;
881 cachedFileProp.bIsDirectory = true;
882 cachedFileProp.bHasComputedFileSize = true;
883 SetCachedFileProp(GetURLFromFilename(osDirnameWithoutEndSlash), cachedFileProp);
884
885 RegisterEmptyDir(osDirnameWithoutEndSlash);
886 }
887 else
888 {
889 CPLDebug("WEBHDFS",
890 "%s",
891 sWriteFuncData.pBuffer
892 ? sWriteFuncData.pBuffer
893 : "(null)");
894 }
895
896 CPLFree(sWriteFuncData.pBuffer);
897 curl_easy_cleanup(hCurlHandle);
898
899 return bOK ? 0 : -1;
900 }
901
902 /************************************************************************/
903 /* VSIWebHDFSHandle() */
904 /************************************************************************/
905
906 VSIWebHDFSHandle::VSIWebHDFSHandle( VSIWebHDFSFSHandler* poFSIn,
907 const char* pszFilename, const char* pszURL ) :
908 VSICurlHandle(poFSIn, pszFilename, pszURL),
909 m_osDataNodeHost(GetWebHDFSDataNodeHost())
910 {
911 // cppcheck-suppress useInitializationList
912 m_osUsernameParam = CPLGetConfigOption("WEBHDFS_USERNAME", "");
913 if( !m_osUsernameParam.empty() )
914 m_osUsernameParam = "&user.name=" + m_osUsernameParam;
915 m_osDelegationParam = CPLGetConfigOption("WEBHDFS_DELEGATION", "");
916 if( !m_osDelegationParam.empty() )
917 m_osDelegationParam = "&delegation=" + m_osDelegationParam;
918
919 }
920
921 /************************************************************************/
922 /* GetFileSize() */
923 /************************************************************************/
924
925 vsi_l_offset VSIWebHDFSHandle::GetFileSize( bool bSetError )
926 {
927 if( oFileProp.bHasComputedFileSize )
928 return oFileProp.fileSize;
929
930 NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix());
931 NetworkStatisticsFile oContextFile(m_osFilename);
932 NetworkStatisticsAction oContextAction("GetFileSize");
933
934 oFileProp.bHasComputedFileSize = true;
935
936 CURLM* hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
937
938 CPLString osURL(m_pszURL);
939
940 if( osURL.size() > strlen("/webhdfs/v1") &&
941 osURL.find("/webhdfs/v1") == osURL.size() - strlen("/webhdfs/v1") &&
942 std::count(osURL.begin(),
943 osURL.end(),'/') == 4 )
944 {
945 // If this is the root directory, add a trailing slash
946 osURL += "/";
947 }
948
949 osURL += "?op=GETFILESTATUS" + m_osUsernameParam + m_osDelegationParam;
950
951 CURL* hCurlHandle = curl_easy_init();
952
953 struct curl_slist* headers =
954 VSICurlSetOptions(hCurlHandle, osURL, m_papszHTTPOptions);
955
956 WriteFuncStruct sWriteFuncData;
957 VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, nullptr);
958 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
959 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
960 VSICurlHandleWriteFunc);
961
962 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
963
964 char szCurlErrBuf[CURL_ERROR_SIZE+1] = {};
965 szCurlErrBuf[0] = '\0';
966 curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
967
968 MultiPerform(hCurlMultiHandle, hCurlHandle);
969
970 VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
971
972 curl_slist_free_all(headers);
973
974 NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
975
976 long response_code = 0;
977 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
978
979 oFileProp.eExists = EXIST_NO;
980 if( response_code == 200 && sWriteFuncData.pBuffer )
981 {
982 CPLJSONDocument oDoc;
983 if( oDoc.LoadMemory(reinterpret_cast<const GByte*>(sWriteFuncData.pBuffer )) )
984 {
985 CPLJSONObject oFileStatus = oDoc.GetRoot().GetObj("FileStatus");
986 oFileProp.fileSize = oFileStatus.GetLong("length");
987 oFileProp.mTime = static_cast<size_t>(
988 oFileStatus.GetLong("modificationTime") / 1000);
989 oFileProp.bIsDirectory = oFileStatus.GetString("type") == "DIRECTORY";
990 oFileProp.eExists = EXIST_YES;
991 }
992 }
993
994 // If there was no VSI error thrown in the process,
995 // fail by reporting the HTTP response code.
996 if( response_code != 200 && bSetError && VSIGetLastErrorNo() == 0 )
997 {
998 if( strlen(szCurlErrBuf) > 0 )
999 {
1000 if( response_code == 0 )
1001 {
1002 VSIError(VSIE_HttpError,
1003 "CURL error: %s", szCurlErrBuf);
1004 }
1005 else
1006 {
1007 VSIError(VSIE_HttpError,
1008 "HTTP response code: %d - %s",
1009 static_cast<int>(response_code), szCurlErrBuf);
1010 }
1011 }
1012 else
1013 {
1014 VSIError(VSIE_HttpError, "HTTP response code: %d",
1015 static_cast<int>(response_code));
1016 }
1017 }
1018
1019 if( ENABLE_DEBUG )
1020 CPLDebug("WEBHDFS", "GetFileSize(%s)=" CPL_FRMT_GUIB
1021 " response_code=%d",
1022 osURL.c_str(), oFileProp.fileSize,
1023 static_cast<int>(response_code));
1024
1025 CPLFree(sWriteFuncData.pBuffer);
1026 curl_easy_cleanup(hCurlHandle);
1027
1028 oFileProp.bHasComputedFileSize = true;
1029 poFS->SetCachedFileProp(m_pszURL, oFileProp);
1030
1031 return oFileProp.fileSize;
1032 }
1033
1034 /************************************************************************/
1035 /* DownloadRegion() */
1036 /************************************************************************/
1037
1038 std::string VSIWebHDFSHandle::DownloadRegion( const vsi_l_offset startOffset,
1039 const int nBlocks )
1040 {
1041 if( bInterrupted && bStopOnInterruptUntilUninstall )
1042 return std::string();
1043
1044 poFS->GetCachedFileProp(m_pszURL, oFileProp);
1045 if( oFileProp.eExists == EXIST_NO )
1046 return std::string();
1047
1048 NetworkStatisticsFileSystem oContextFS(poFS->GetFSPrefix());
1049 NetworkStatisticsFile oContextFile(m_osFilename);
1050 NetworkStatisticsAction oContextAction("Read");
1051
1052 CURLM* hCurlMultiHandle = poFS->GetCurlMultiHandleFor(m_pszURL);
1053
1054 CPLString osURL(m_pszURL);
1055
1056 WriteFuncStruct sWriteFuncData;
1057 int nRetryCount = 0;
1058 double dfRetryDelay = m_dfRetryDelay;
1059 bool bInRedirect = false;
1060 const vsi_l_offset nEndOffset =
1061 startOffset + nBlocks * VSICURLGetDownloadChunkSize() - 1;
1062
1063 retry:
1064 CURL* hCurlHandle = curl_easy_init();
1065
1066 VSICURLInitWriteFuncStruct(&sWriteFuncData,
1067 reinterpret_cast<VSILFILE *>(this),
1068 pfnReadCbk, pReadCbkUserData);
1069 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, &sWriteFuncData);
1070 curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION,
1071 VSICurlHandleWriteFunc);
1072
1073 if( !bInRedirect )
1074 {
1075 osURL += "?op=OPEN&offset=";
1076 osURL += CPLSPrintf(CPL_FRMT_GUIB, startOffset);
1077 osURL += "&length=";
1078 osURL += CPLSPrintf(CPL_FRMT_GUIB, nEndOffset - startOffset + 1);
1079 osURL += m_osUsernameParam + m_osDelegationParam;
1080 }
1081
1082 struct curl_slist* headers =
1083 VSICurlSetOptions(hCurlHandle, osURL, m_papszHTTPOptions);
1084
1085 if( !m_osDataNodeHost.empty() )
1086 {
1087 curl_easy_setopt(hCurlHandle, CURLOPT_FOLLOWLOCATION, 0);
1088 }
1089
1090 if( ENABLE_DEBUG )
1091 CPLDebug("WEBHDFS", "Downloading %s...", osURL.c_str());
1092
1093 char szCurlErrBuf[CURL_ERROR_SIZE+1] = {};
1094 szCurlErrBuf[0] = '\0';
1095 curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
1096
1097 curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, headers);
1098
1099 MultiPerform(hCurlMultiHandle, hCurlHandle);
1100
1101 VSICURLResetHeaderAndWriterFunctions(hCurlHandle);
1102
1103 curl_slist_free_all(headers);
1104
1105 NetworkStatisticsLogger::LogGET(sWriteFuncData.nSize);
1106
1107 if( sWriteFuncData.bInterrupted )
1108 {
1109 bInterrupted = true;
1110
1111 CPLFree(sWriteFuncData.pBuffer);
1112 curl_easy_cleanup(hCurlHandle);
1113
1114 return std::string();
1115 }
1116
1117 long response_code = 0;
1118 curl_easy_getinfo(hCurlHandle, CURLINFO_HTTP_CODE, &response_code);
1119
1120 if( ENABLE_DEBUG )
1121 CPLDebug("WEBHDFS", "Got response_code=%ld", response_code);
1122
1123 if( !bInRedirect )
1124 {
1125 char *pszRedirectURL = nullptr;
1126 curl_easy_getinfo(hCurlHandle, CURLINFO_REDIRECT_URL, &pszRedirectURL);
1127 if( pszRedirectURL &&
1128 strstr(pszRedirectURL, m_pszURL) == nullptr )
1129 {
1130 CPLDebug("WEBHDFS", "Redirect URL: %s", pszRedirectURL);
1131
1132 bInRedirect = true;
1133 osURL = pszRedirectURL;
1134 if( !m_osDataNodeHost.empty() )
1135 {
1136 osURL = PatchWebHDFSUrl(osURL, m_osDataNodeHost);
1137 }
1138
1139 CPLFree(sWriteFuncData.pBuffer);
1140 curl_easy_cleanup(hCurlHandle);
1141
1142 goto retry;
1143 }
1144 }
1145
1146 if( response_code != 200 )
1147 {
1148 // If HTTP 429, 500, 502, 503, 504 error retry after a
1149 // pause.
1150 const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
1151 static_cast<int>(response_code), dfRetryDelay,
1152 nullptr, szCurlErrBuf);
1153 if( dfNewRetryDelay > 0 &&
1154 nRetryCount < m_nMaxRetry )
1155 {
1156 CPLError(CE_Warning, CPLE_AppDefined,
1157 "HTTP error code: %d - %s. "
1158 "Retrying again in %.1f secs",
1159 static_cast<int>(response_code), m_pszURL,
1160 dfRetryDelay);
1161 CPLSleep(dfRetryDelay);
1162 dfRetryDelay = dfNewRetryDelay;
1163 nRetryCount++;
1164 CPLFree(sWriteFuncData.pBuffer);
1165 curl_easy_cleanup(hCurlHandle);
1166 goto retry;
1167 }
1168
1169 if( response_code >= 400 && szCurlErrBuf[0] != '\0' )
1170 {
1171 CPLError(CE_Failure, CPLE_AppDefined, "%d: %s",
1172 static_cast<int>(response_code), szCurlErrBuf);
1173 }
1174 if( !oFileProp.bHasComputedFileSize && startOffset == 0 )
1175 {
1176 oFileProp.bHasComputedFileSize = true;
1177 oFileProp.fileSize = 0;
1178 oFileProp.eExists = EXIST_NO;
1179 poFS->SetCachedFileProp(m_pszURL, oFileProp);
1180 }
1181 CPLFree(sWriteFuncData.pBuffer);
1182 curl_easy_cleanup(hCurlHandle);
1183 return std::string();
1184 }
1185
1186 oFileProp.eExists = EXIST_YES;
1187 poFS->SetCachedFileProp(m_pszURL, oFileProp);
1188
1189 DownloadRegionPostProcess(startOffset, nBlocks,
1190 sWriteFuncData.pBuffer,
1191 sWriteFuncData.nSize);
1192
1193 std::string osRet;
1194 osRet.assign(sWriteFuncData.pBuffer, sWriteFuncData.nSize);
1195
1196 CPLFree(sWriteFuncData.pBuffer);
1197 curl_easy_cleanup(hCurlHandle);
1198
1199 return osRet;
1200 }
1201
1202
1203 } /* end of namespace cpl */
1204
1205
1206 #endif // DOXYGEN_SKIP
1207 //! @endcond
1208
1209 /************************************************************************/
1210 /* VSIInstallWebHdfsHandler() */
1211 /************************************************************************/
1212
1213 /**
1214 * \brief Install /vsiwebhdfs/ WebHDFS (Hadoop File System) REST API file
1215 * system handler (requires libcurl)
1216 *
1217 * @see <a href="gdal_virtual_file_systems.html#gdal_virtual_file_systems_vsiwebhdfs">/vsiwebhdfs/ documentation</a>
1218 *
1219 * @since GDAL 2.4
1220 */
1221 void VSIInstallWebHdfsHandler( void )
1222 {
1223 VSIFileManager::InstallHandler( "/vsiwebhdfs/", new cpl::VSIWebHDFSFSHandler );
1224 }
1225
1226 #endif /* HAVE_CURL */
1227