Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for Google Cloud Storage
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-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_minixml.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_google_cloud.h"
43 :
44 : #ifndef HAVE_CURL
45 :
46 : void VSIInstallGSFileHandler(void)
47 : {
48 : // Not supported.
49 : }
50 :
51 : #else
52 :
53 : //! @cond Doxygen_Suppress
54 : #ifndef DOXYGEN_SKIP
55 :
56 : #define ENABLE_DEBUG 0
57 :
58 : #define unchecked_curl_easy_setopt(handle, opt, param) \
59 : CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param))
60 :
61 : namespace cpl
62 : {
63 :
64 : /************************************************************************/
65 : /* VSIGSFSHandler */
66 : /************************************************************************/
67 :
68 : class VSIGSFSHandler final : public IVSIS3LikeFSHandler
69 : {
70 : CPL_DISALLOW_COPY_ASSIGN(VSIGSFSHandler)
71 : const std::string m_osPrefix;
72 :
73 : protected:
74 : VSICurlHandle *CreateFileHandle(const char *pszFilename) override;
75 :
76 44 : const char *GetDebugKey() const override
77 : {
78 44 : return "GS";
79 : }
80 :
81 459 : std::string GetFSPrefix() const override
82 : {
83 459 : return m_osPrefix;
84 : }
85 :
86 : std::string GetURLFromFilename(const std::string &osFilename) override;
87 :
88 : IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI,
89 : bool bAllowNoObject) override;
90 :
91 : void ClearCache() override;
92 :
93 0 : bool IsAllowedHeaderForObjectCreation(const char *pszHeaderName) override
94 : {
95 0 : return STARTS_WITH(pszHeaderName, "x-goog-");
96 : }
97 :
98 : VSIVirtualHandleUniquePtr
99 : CreateWriteHandle(const char *pszFilename,
100 : CSLConstList papszOptions) override;
101 :
102 : public:
103 1225 : explicit VSIGSFSHandler(const char *pszPrefix) : m_osPrefix(pszPrefix)
104 : {
105 1225 : }
106 :
107 : ~VSIGSFSHandler() override;
108 :
109 : const char *GetOptions() override;
110 :
111 : char *GetSignedURL(const char *pszFilename,
112 : CSLConstList papszOptions) override;
113 :
114 : // Multipart upload
115 0 : bool SupportsParallelMultipartUpload() const override
116 : {
117 0 : return true;
118 : }
119 :
120 : char **GetFileMetadata(const char *pszFilename, const char *pszDomain,
121 : CSLConstList papszOptions) override;
122 :
123 : bool SetFileMetadata(const char *pszFilename, CSLConstList papszMetadata,
124 : const char *pszDomain,
125 : CSLConstList papszOptions) override;
126 :
127 : int *UnlinkBatch(CSLConstList papszFiles) override;
128 : int RmdirRecursive(const char *pszDirname) override;
129 :
130 : std::string
131 : GetStreamingFilename(const std::string &osFilename) const override;
132 :
133 0 : VSIFilesystemHandler *Duplicate(const char *pszPrefix) override
134 : {
135 0 : return new VSIGSFSHandler(pszPrefix);
136 : }
137 : };
138 :
139 : /************************************************************************/
140 : /* VSIGSHandle */
141 : /************************************************************************/
142 :
143 : class VSIGSHandle final : public IVSIS3LikeHandle
144 : {
145 : CPL_DISALLOW_COPY_ASSIGN(VSIGSHandle)
146 :
147 : VSIGSHandleHelper *m_poHandleHelper = nullptr;
148 :
149 : protected:
150 : struct curl_slist *
151 : GetCurlHeaders(const std::string &osVerb,
152 : const struct curl_slist *psExistingHeaders) override;
153 :
154 : public:
155 : VSIGSHandle(VSIGSFSHandler *poFS, const char *pszFilename,
156 : VSIGSHandleHelper *poHandleHelper);
157 : ~VSIGSHandle() override;
158 : };
159 :
160 : /************************************************************************/
161 : /* ~VSIGSFSHandler() */
162 : /************************************************************************/
163 :
164 1698 : VSIGSFSHandler::~VSIGSFSHandler()
165 : {
166 849 : VSICurlFilesystemHandlerBase::ClearCache();
167 849 : VSIGSHandleHelper::CleanMutex();
168 1698 : }
169 :
170 : /************************************************************************/
171 : /* ClearCache() */
172 : /************************************************************************/
173 :
174 246 : void VSIGSFSHandler::ClearCache()
175 : {
176 246 : VSICurlFilesystemHandlerBase::ClearCache();
177 :
178 246 : VSIGSHandleHelper::ClearCache();
179 246 : }
180 :
181 : /************************************************************************/
182 : /* CreateFileHandle() */
183 : /************************************************************************/
184 :
185 31 : VSICurlHandle *VSIGSFSHandler::CreateFileHandle(const char *pszFilename)
186 : {
187 62 : VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
188 93 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str());
189 31 : if (poHandleHelper == nullptr)
190 4 : return nullptr;
191 27 : return new VSIGSHandle(this, pszFilename, poHandleHelper);
192 : }
193 :
194 : /************************************************************************/
195 : /* GetOptions() */
196 : /************************************************************************/
197 :
198 2 : const char *VSIGSFSHandler::GetOptions()
199 : {
200 : static std::string osOptions(
201 2 : std::string("<Options>") +
202 : " <Option name='GS_SECRET_ACCESS_KEY' type='string' "
203 : "description='Secret access key. To use with GS_ACCESS_KEY_ID'/>"
204 : " <Option name='GS_ACCESS_KEY_ID' type='string' "
205 : "description='Access key id'/>"
206 : " <Option name='GS_NO_SIGN_REQUEST' type='boolean' "
207 : "description='Whether to disable signing of requests' default='NO'/>"
208 : " <Option name='GS_OAUTH2_REFRESH_TOKEN' type='string' "
209 : "description='OAuth2 refresh token. For OAuth2 client authentication. "
210 : "To use with GS_OAUTH2_CLIENT_ID and GS_OAUTH2_CLIENT_SECRET'/>"
211 : " <Option name='GS_OAUTH2_CLIENT_ID' type='string' "
212 : "description='OAuth2 client id for OAuth2 client authentication'/>"
213 : " <Option name='GS_OAUTH2_CLIENT_SECRET' type='string' "
214 : "description='OAuth2 client secret for OAuth2 client authentication'/>"
215 : " <Option name='GS_OAUTH2_PRIVATE_KEY' type='string' "
216 : "description='Private key for OAuth2 service account authentication. "
217 : "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
218 : " <Option name='GS_OAUTH2_PRIVATE_KEY_FILE' type='string' "
219 : "description='Filename that contains private key for OAuth2 service "
220 : "account authentication. "
221 : "To use with GS_OAUTH2_CLIENT_EMAIL'/>"
222 : " <Option name='GS_OAUTH2_CLIENT_EMAIL' type='string' "
223 : "description='Client email to use with OAuth2 service account "
224 : "authentication'/>"
225 : " <Option name='GS_OAUTH2_SCOPE' type='string' "
226 : "description='OAuth2 authorization scope' "
227 : "default='https://www.googleapis.com/auth/devstorage.read_write'/>"
228 : " <Option name='CPL_MACHINE_IS_GCE' type='boolean' "
229 : "description='Whether the current machine is a Google Compute Engine "
230 : "instance' default='NO'/>"
231 : " <Option name='CPL_GCE_CHECK_LOCAL_FILES' type='boolean' "
232 : "description='Whether to check system logs to determine "
233 : "if current machine is a GCE instance' default='YES'/>"
234 : "description='Filename that contains AWS configuration' "
235 : "default='~/.aws/config'/>"
236 : " <Option name='CPL_GS_CREDENTIALS_FILE' type='string' "
237 : "description='Filename that contains Google Storage credentials' "
238 3 : "default='~/.boto'/>" +
239 3 : VSICurlFilesystemHandlerBase::GetOptionsStatic() + "</Options>");
240 2 : return osOptions.c_str();
241 : }
242 :
243 : /************************************************************************/
244 : /* GetSignedURL() */
245 : /************************************************************************/
246 :
247 6 : char *VSIGSFSHandler::GetSignedURL(const char *pszFilename,
248 : CSLConstList papszOptions)
249 : {
250 6 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
251 0 : return nullptr;
252 :
253 : VSIGSHandleHelper *poHandleHelper =
254 6 : VSIGSHandleHelper::BuildFromURI(pszFilename + GetFSPrefix().size(),
255 12 : GetFSPrefix().c_str(), papszOptions);
256 6 : if (poHandleHelper == nullptr)
257 : {
258 1 : return nullptr;
259 : }
260 :
261 5 : std::string osRet(poHandleHelper->GetSignedURL(papszOptions));
262 :
263 5 : delete poHandleHelper;
264 5 : return osRet.empty() ? nullptr : CPLStrdup(osRet.c_str());
265 : }
266 :
267 : /************************************************************************/
268 : /* GetURLFromFilename() */
269 : /************************************************************************/
270 :
271 7 : std::string VSIGSFSHandler::GetURLFromFilename(const std::string &osFilename)
272 : {
273 : std::string osFilenameWithoutPrefix =
274 14 : osFilename.substr(GetFSPrefix().size());
275 7 : VSIGSHandleHelper *poHandleHelper = VSIGSHandleHelper::BuildFromURI(
276 14 : osFilenameWithoutPrefix.c_str(), GetFSPrefix().c_str());
277 7 : if (poHandleHelper == nullptr)
278 0 : return std::string();
279 14 : std::string osURL(poHandleHelper->GetURL());
280 7 : delete poHandleHelper;
281 7 : return osURL;
282 : }
283 :
284 : /************************************************************************/
285 : /* CreateHandleHelper() */
286 : /************************************************************************/
287 :
288 9 : IVSIS3LikeHandleHelper *VSIGSFSHandler::CreateHandleHelper(const char *pszURI,
289 : bool)
290 : {
291 9 : return VSIGSHandleHelper::BuildFromURI(pszURI, GetFSPrefix().c_str());
292 : }
293 :
294 : /************************************************************************/
295 : /* CreateWriteHandle() */
296 : /************************************************************************/
297 :
298 : VSIVirtualHandleUniquePtr
299 1 : VSIGSFSHandler::CreateWriteHandle(const char *pszFilename,
300 : CSLConstList papszOptions)
301 : {
302 : auto poHandleHelper =
303 1 : CreateHandleHelper(pszFilename + GetFSPrefix().size(), false);
304 1 : if (poHandleHelper == nullptr)
305 0 : return nullptr;
306 : auto poHandle = std::make_unique<VSIS3WriteHandle>(
307 2 : this, pszFilename, poHandleHelper, false, papszOptions);
308 1 : if (!poHandle->IsOK())
309 : {
310 0 : return nullptr;
311 : }
312 1 : return VSIVirtualHandleUniquePtr(poHandle.release());
313 : }
314 :
315 : /************************************************************************/
316 : /* GetFileMetadata() */
317 : /************************************************************************/
318 :
319 4 : char **VSIGSFSHandler::GetFileMetadata(const char *pszFilename,
320 : const char *pszDomain,
321 : CSLConstList papszOptions)
322 : {
323 4 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
324 0 : return nullptr;
325 :
326 4 : if (pszDomain == nullptr || !EQUAL(pszDomain, "ACL"))
327 : {
328 2 : return VSICurlFilesystemHandlerBase::GetFileMetadata(
329 2 : pszFilename, pszDomain, papszOptions);
330 : }
331 :
332 : auto poHandleHelper =
333 4 : std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
334 10 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
335 2 : if (!poHandleHelper)
336 0 : return nullptr;
337 :
338 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
339 4 : NetworkStatisticsAction oContextAction("GetFileMetadata");
340 :
341 4 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
342 :
343 : bool bRetry;
344 : // coverity[tainted_data]
345 2 : double dfRetryDelay = CPLAtof(
346 : VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
347 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
348 : const int nMaxRetry =
349 2 : atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
350 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
351 2 : int nRetryCount = 0;
352 :
353 4 : CPLStringList aosResult;
354 2 : do
355 : {
356 2 : bRetry = false;
357 2 : CURL *hCurlHandle = curl_easy_init();
358 2 : poHandleHelper->AddQueryParameter("acl", "");
359 :
360 : struct curl_slist *headers = static_cast<struct curl_slist *>(
361 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
362 : aosHTTPOptions.List()));
363 2 : headers = VSICurlMergeHeaders(
364 2 : headers, poHandleHelper->GetCurlHeaders("GET", headers));
365 :
366 4 : CurlRequestHelper requestHelper;
367 2 : const long response_code = requestHelper.perform(
368 : hCurlHandle, headers, this, poHandleHelper.get());
369 :
370 2 : NetworkStatisticsLogger::LogGET(requestHelper.sWriteFuncData.nSize);
371 :
372 2 : if (response_code != 200 ||
373 1 : requestHelper.sWriteFuncData.pBuffer == nullptr)
374 : {
375 : // Look if we should attempt a retry
376 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
377 : static_cast<int>(response_code), dfRetryDelay,
378 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
379 : requestHelper.szCurlErrBuf);
380 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
381 : {
382 0 : CPLError(CE_Warning, CPLE_AppDefined,
383 : "HTTP error code: %d - %s. "
384 : "Retrying again in %.1f secs",
385 : static_cast<int>(response_code),
386 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
387 0 : CPLSleep(dfRetryDelay);
388 0 : dfRetryDelay = dfNewRetryDelay;
389 0 : nRetryCount++;
390 0 : bRetry = true;
391 : }
392 : else
393 : {
394 1 : CPLDebug(GetDebugKey(), "%s",
395 1 : requestHelper.sWriteFuncData.pBuffer
396 : ? requestHelper.sWriteFuncData.pBuffer
397 : : "(null)");
398 1 : CPLError(CE_Failure, CPLE_AppDefined, "GetFileMetadata failed");
399 1 : }
400 : }
401 : else
402 : {
403 1 : aosResult.SetNameValue("XML", requestHelper.sWriteFuncData.pBuffer);
404 : }
405 :
406 2 : curl_easy_cleanup(hCurlHandle);
407 : } while (bRetry);
408 2 : return CSLDuplicate(aosResult.List());
409 : }
410 :
411 : /************************************************************************/
412 : /* SetFileMetadata() */
413 : /************************************************************************/
414 :
415 5 : bool VSIGSFSHandler::SetFileMetadata(const char *pszFilename,
416 : CSLConstList papszMetadata,
417 : const char *pszDomain,
418 : CSLConstList /* papszOptions */)
419 : {
420 5 : if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str()))
421 0 : return false;
422 :
423 5 : if (pszDomain == nullptr ||
424 5 : !(EQUAL(pszDomain, "HEADERS") || EQUAL(pszDomain, "ACL")))
425 : {
426 1 : CPLError(CE_Failure, CPLE_NotSupported,
427 : "Only HEADERS and ACL domain are supported");
428 1 : return false;
429 : }
430 :
431 4 : if (EQUAL(pszDomain, "HEADERS"))
432 : {
433 1 : return CopyObject(pszFilename, pszFilename, papszMetadata) == 0;
434 : }
435 :
436 3 : const char *pszXML = CSLFetchNameValue(papszMetadata, "XML");
437 3 : if (pszXML == nullptr)
438 : {
439 1 : CPLError(CE_Failure, CPLE_AppDefined, "XML key is missing in metadata");
440 1 : return false;
441 : }
442 :
443 : auto poHandleHelper =
444 4 : std::unique_ptr<IVSIS3LikeHandleHelper>(VSIGSHandleHelper::BuildFromURI(
445 10 : pszFilename + GetFSPrefix().size(), GetFSPrefix().c_str()));
446 2 : if (!poHandleHelper)
447 0 : return false;
448 :
449 4 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
450 4 : NetworkStatisticsAction oContextAction("SetFileMetadata");
451 :
452 : bool bRetry;
453 : // coverity[tainted_data]
454 2 : double dfRetryDelay = CPLAtof(
455 : VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_RETRY_DELAY",
456 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
457 : const int nMaxRetry =
458 2 : atoi(VSIGetPathSpecificOption(pszFilename, "GDAL_HTTP_MAX_RETRY",
459 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
460 2 : int nRetryCount = 0;
461 :
462 2 : bool bRet = false;
463 :
464 2 : const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename));
465 :
466 2 : do
467 : {
468 2 : bRetry = false;
469 2 : CURL *hCurlHandle = curl_easy_init();
470 2 : poHandleHelper->AddQueryParameter("acl", "");
471 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST, "PUT");
472 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS, pszXML);
473 :
474 : struct curl_slist *headers = static_cast<struct curl_slist *>(
475 2 : CPLHTTPSetOptions(hCurlHandle, poHandleHelper->GetURL().c_str(),
476 : aosHTTPOptions.List()));
477 2 : headers = curl_slist_append(headers, "Content-Type: application/xml");
478 2 : headers = VSICurlMergeHeaders(
479 2 : headers, poHandleHelper->GetCurlHeaders("PUT", headers, pszXML,
480 2 : strlen(pszXML)));
481 2 : NetworkStatisticsLogger::LogPUT(strlen(pszXML));
482 :
483 4 : CurlRequestHelper requestHelper;
484 2 : const long response_code = requestHelper.perform(
485 : hCurlHandle, headers, this, poHandleHelper.get());
486 :
487 2 : if (response_code != 200)
488 : {
489 : // Look if we should attempt a retry
490 2 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
491 : static_cast<int>(response_code), dfRetryDelay,
492 1 : requestHelper.sWriteFuncHeaderData.pBuffer,
493 : requestHelper.szCurlErrBuf);
494 1 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
495 : {
496 0 : CPLError(CE_Warning, CPLE_AppDefined,
497 : "HTTP error code: %d - %s. "
498 : "Retrying again in %.1f secs",
499 : static_cast<int>(response_code),
500 0 : poHandleHelper->GetURL().c_str(), dfRetryDelay);
501 0 : CPLSleep(dfRetryDelay);
502 0 : dfRetryDelay = dfNewRetryDelay;
503 0 : nRetryCount++;
504 0 : bRetry = true;
505 : }
506 : else
507 : {
508 1 : CPLDebug(GetDebugKey(), "%s",
509 1 : requestHelper.sWriteFuncData.pBuffer
510 : ? requestHelper.sWriteFuncData.pBuffer
511 : : "(null)");
512 1 : CPLError(CE_Failure, CPLE_AppDefined, "SetFileMetadata failed");
513 : }
514 : }
515 : else
516 : {
517 1 : bRet = true;
518 : }
519 :
520 2 : curl_easy_cleanup(hCurlHandle);
521 : } while (bRetry);
522 2 : return bRet;
523 : }
524 :
525 : /************************************************************************/
526 : /* UnlinkBatch() */
527 : /************************************************************************/
528 :
529 1 : int *VSIGSFSHandler::UnlinkBatch(CSLConstList papszFiles)
530 : {
531 : // Implemented using
532 : // https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
533 :
534 : auto poHandleHelper =
535 : std::unique_ptr<VSIGSHandleHelper>(VSIGSHandleHelper::BuildFromURI(
536 2 : "batch/storage/v1", GetFSPrefix().c_str()));
537 :
538 : // The JSON API cannot be used with HMAC keys
539 1 : if (poHandleHelper && poHandleHelper->UsesHMACKey())
540 : {
541 0 : CPLDebug(GetDebugKey(), "UnlinkBatch() has an efficient implementation "
542 : "only for OAuth2 authentication");
543 0 : return VSICurlFilesystemHandlerBase::UnlinkBatch(papszFiles);
544 : }
545 :
546 : int *panRet =
547 1 : static_cast<int *>(CPLCalloc(sizeof(int), CSLCount(papszFiles)));
548 :
549 1 : const char *pszFirstFilename =
550 1 : papszFiles && papszFiles[0] ? papszFiles[0] : nullptr;
551 1 : if (!poHandleHelper || pszFirstFilename == nullptr)
552 0 : return panRet;
553 :
554 2 : NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str());
555 2 : NetworkStatisticsAction oContextAction("UnlinkBatch");
556 :
557 : // coverity[tainted_data]
558 1 : double dfRetryDelay = CPLAtof(
559 : VSIGetPathSpecificOption(pszFirstFilename, "GDAL_HTTP_RETRY_DELAY",
560 : CPLSPrintf("%f", CPL_HTTP_RETRY_DELAY)));
561 : const int nMaxRetry =
562 1 : atoi(VSIGetPathSpecificOption(pszFirstFilename, "GDAL_HTTP_MAX_RETRY",
563 : CPLSPrintf("%d", CPL_HTTP_MAX_RETRY)));
564 :
565 : // For debug / testing only
566 : const int nBatchSize =
567 1 : std::max(1, std::min(100, atoi(CPLGetConfigOption(
568 1 : "CPL_VSIGS_UNLINK_BATCH_SIZE", "100"))));
569 2 : std::string osPOSTContent;
570 :
571 : const CPLStringList aosHTTPOptions(
572 2 : CPLHTTPGetOptionsFromEnv(pszFirstFilename));
573 :
574 4 : for (int i = 0; papszFiles && papszFiles[i]; i++)
575 : {
576 3 : CPLAssert(STARTS_WITH_CI(papszFiles[i], GetFSPrefix().c_str()));
577 : const char *pszFilenameWithoutPrefix =
578 3 : papszFiles[i] + GetFSPrefix().size();
579 3 : const char *pszSlash = strchr(pszFilenameWithoutPrefix, '/');
580 3 : if (!pszSlash)
581 0 : return panRet;
582 6 : std::string osBucket;
583 : osBucket.assign(pszFilenameWithoutPrefix,
584 3 : pszSlash - pszFilenameWithoutPrefix);
585 :
586 6 : std::string osResource = "storage/v1/b/";
587 3 : osResource += osBucket;
588 3 : osResource += "/o/";
589 3 : osResource += CPLAWSURLEncode(pszSlash + 1, true);
590 :
591 : #ifdef ADD_AUTH_TO_NESTED_REQUEST
592 : std::string osAuthorization;
593 : std::string osDate;
594 : {
595 : auto poTmpHandleHelper = std::unique_ptr<IVSIS3LikeHandleHelper>(
596 : VSIGSHandleHelper::BuildFromURI(osResource.c_str(),
597 : GetFSPrefix().c_str()));
598 : CURL *hCurlHandle = curl_easy_init();
599 : struct curl_slist *subrequest_headers =
600 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
601 : hCurlHandle, poTmpHandleHelper->GetURL().c_str(),
602 : aosHTTPOptions.List()));
603 : subrequest_headers = poTmpHandleHelper->GetCurlHeaders(
604 : "DELETE", subrequest_headers, nullptr, 0);
605 : for (struct curl_slist *iter = subrequest_headers; iter;
606 : iter = iter->next)
607 : {
608 : if (STARTS_WITH_CI(iter->data, "Authorization: "))
609 : {
610 : osAuthorization = iter->data;
611 : }
612 : else if (STARTS_WITH_CI(iter->data, "Date: "))
613 : {
614 : osDate = iter->data;
615 : }
616 : }
617 : curl_slist_free_all(subrequest_headers);
618 : curl_easy_cleanup(hCurlHandle);
619 : }
620 : #endif
621 :
622 3 : osPOSTContent += "--===============7330845974216740156==\r\n";
623 3 : osPOSTContent += "Content-Type: application/http\r\n";
624 3 : osPOSTContent += CPLSPrintf("Content-ID: <%d>\r\n", i + 1);
625 3 : osPOSTContent += "\r\n\r\n";
626 3 : osPOSTContent += "DELETE /";
627 3 : osPOSTContent += osResource;
628 3 : osPOSTContent += " HTTP/1.1\r\n";
629 : #ifdef ADD_AUTH_TO_NESTED_REQUEST
630 : if (!osAuthorization.empty())
631 : {
632 : osPOSTContent += osAuthorization;
633 : osPOSTContent += "\r\n";
634 : }
635 : if (!osDate.empty())
636 : {
637 : osPOSTContent += osDate;
638 : osPOSTContent += "\r\n";
639 : }
640 : #endif
641 3 : osPOSTContent += "\r\n\r\n";
642 :
643 3 : if (((i + 1) % nBatchSize) == 0 || papszFiles[i + 1] == nullptr)
644 : {
645 2 : osPOSTContent += "--===============7330845974216740156==--\r\n";
646 :
647 : #ifdef DEBUG_VERBOSE
648 : CPLDebug(GetDebugKey(), "%s", osPOSTContent.c_str());
649 : #endif
650 :
651 : // Run request
652 2 : int nRetryCount = 0;
653 : bool bRetry;
654 4 : std::string osResponse;
655 2 : do
656 : {
657 2 : bRetry = false;
658 2 : CURL *hCurlHandle = curl_easy_init();
659 :
660 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_CUSTOMREQUEST,
661 : "POST");
662 2 : unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_POSTFIELDS,
663 : osPOSTContent.c_str());
664 :
665 : struct curl_slist *headers =
666 4 : static_cast<struct curl_slist *>(CPLHTTPSetOptions(
667 2 : hCurlHandle, poHandleHelper->GetURL().c_str(),
668 : aosHTTPOptions.List()));
669 2 : headers = curl_slist_append(
670 : headers,
671 : "Content-Type: multipart/mixed; "
672 : "boundary=\"===============7330845974216740156==\"");
673 4 : headers = VSICurlMergeHeaders(
674 : headers, poHandleHelper->GetCurlHeaders(
675 2 : "POST", headers, osPOSTContent.c_str(),
676 : osPOSTContent.size()));
677 :
678 4 : CurlRequestHelper requestHelper;
679 2 : const long response_code = requestHelper.perform(
680 2 : hCurlHandle, headers, this, poHandleHelper.get());
681 :
682 2 : NetworkStatisticsLogger::LogPOST(
683 : osPOSTContent.size(), requestHelper.sWriteFuncData.nSize);
684 :
685 2 : if (response_code != 200 ||
686 2 : requestHelper.sWriteFuncData.pBuffer == nullptr)
687 : {
688 : // Look if we should attempt a retry
689 0 : const double dfNewRetryDelay = CPLHTTPGetNewRetryDelay(
690 : static_cast<int>(response_code), dfRetryDelay,
691 0 : requestHelper.sWriteFuncHeaderData.pBuffer,
692 : requestHelper.szCurlErrBuf);
693 0 : if (dfNewRetryDelay > 0 && nRetryCount < nMaxRetry)
694 : {
695 0 : CPLError(CE_Warning, CPLE_AppDefined,
696 : "HTTP error code: %d - %s. "
697 : "Retrying again in %.1f secs",
698 : static_cast<int>(response_code),
699 0 : poHandleHelper->GetURL().c_str(),
700 : dfRetryDelay);
701 0 : CPLSleep(dfRetryDelay);
702 0 : dfRetryDelay = dfNewRetryDelay;
703 0 : nRetryCount++;
704 0 : bRetry = true;
705 : }
706 : else
707 : {
708 0 : CPLDebug(GetDebugKey(), "%s",
709 0 : requestHelper.sWriteFuncData.pBuffer
710 : ? requestHelper.sWriteFuncData.pBuffer
711 : : "(null)");
712 0 : CPLError(CE_Failure, CPLE_AppDefined,
713 : "DeleteObjects failed");
714 0 : }
715 : }
716 : else
717 : {
718 : #ifdef DEBUG_VERBOSE
719 : CPLDebug(GetDebugKey(), "%s",
720 : requestHelper.sWriteFuncData.pBuffer);
721 : #endif
722 2 : osResponse = requestHelper.sWriteFuncData.pBuffer;
723 : }
724 :
725 2 : curl_easy_cleanup(hCurlHandle);
726 : } while (bRetry);
727 :
728 : // Mark deleted files
729 6 : for (int j = i + 1 - nBatchSize; j <= i; j++)
730 : {
731 4 : auto nPos = osResponse.find(
732 : CPLSPrintf("Content-ID: <response-%d>", j + 1));
733 4 : if (nPos != std::string::npos)
734 : {
735 3 : nPos = osResponse.find("HTTP/1.1 ", nPos);
736 3 : if (nPos != std::string::npos)
737 : {
738 : const char *pszHTTPCode =
739 3 : osResponse.c_str() + nPos + strlen("HTTP/1.1 ");
740 3 : panRet[j] = (atoi(pszHTTPCode) == 204) ? 1 : 0;
741 : }
742 : }
743 : }
744 :
745 2 : osPOSTContent.clear();
746 : }
747 : }
748 1 : return panRet;
749 : }
750 :
751 : /************************************************************************/
752 : /* RmdirRecursive() */
753 : /************************************************************************/
754 :
755 0 : int VSIGSFSHandler::RmdirRecursive(const char *pszDirname)
756 : {
757 : // For debug / testing only
758 : const int nBatchSize = std::min(
759 0 : 100, atoi(CPLGetConfigOption("CPL_VSIGS_UNLINK_BATCH_SIZE", "100")));
760 :
761 0 : return RmdirRecursiveInternal(pszDirname, nBatchSize);
762 : }
763 :
764 : /************************************************************************/
765 : /* GetStreamingFilename() */
766 : /************************************************************************/
767 :
768 : std::string
769 0 : VSIGSFSHandler::GetStreamingFilename(const std::string &osFilename) const
770 : {
771 0 : if (STARTS_WITH(osFilename.c_str(), GetFSPrefix().c_str()))
772 0 : return "/vsigs_streaming/" + osFilename.substr(GetFSPrefix().size());
773 0 : return osFilename;
774 : }
775 :
776 : /************************************************************************/
777 : /* VSIGSHandle() */
778 : /************************************************************************/
779 :
780 27 : VSIGSHandle::VSIGSHandle(VSIGSFSHandler *poFSIn, const char *pszFilename,
781 27 : VSIGSHandleHelper *poHandleHelper)
782 27 : : IVSIS3LikeHandle(poFSIn, pszFilename, poHandleHelper->GetURL().c_str()),
783 27 : m_poHandleHelper(poHandleHelper)
784 : {
785 27 : }
786 :
787 : /************************************************************************/
788 : /* ~VSIGSHandle() */
789 : /************************************************************************/
790 :
791 54 : VSIGSHandle::~VSIGSHandle()
792 : {
793 27 : delete m_poHandleHelper;
794 54 : }
795 :
796 : /************************************************************************/
797 : /* GetCurlHeaders() */
798 : /************************************************************************/
799 :
800 : struct curl_slist *
801 22 : VSIGSHandle::GetCurlHeaders(const std::string &osVerb,
802 : const struct curl_slist *psExistingHeaders)
803 : {
804 22 : return m_poHandleHelper->GetCurlHeaders(osVerb, psExistingHeaders);
805 : }
806 :
807 : } /* end of namespace cpl */
808 :
809 : #endif // DOXYGEN_SKIP
810 : //! @endcond
811 :
812 : /************************************************************************/
813 : /* VSIInstallGSFileHandler() */
814 : /************************************************************************/
815 :
816 : /*!
817 : \brief Install /vsigs/ Google Cloud Storage file system handler
818 : (requires libcurl)
819 :
820 : \verbatim embed:rst
821 : See :ref:`/vsigs/ documentation <vsigs>`
822 : \endverbatim
823 :
824 : @since GDAL 2.2
825 : */
826 :
827 1225 : void VSIInstallGSFileHandler(void)
828 : {
829 1225 : VSIFileManager::InstallHandler("/vsigs/",
830 1225 : new cpl::VSIGSFSHandler("/vsigs/"));
831 1225 : }
832 :
833 : #endif /* HAVE_CURL */
|