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