diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7001adaa95..75b3378414 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,6 +5,10 @@ Release Notes {#RELEASE_NOTES} This file contains a high-level description of this package's evolution. Releases are in reverse chronological order (most recent first). Note that, as of netcdf 4.2, the `netcdf-c++` and `netcdf-fortran` libraries have been separated into their own libraries. +## 4.9.4 - TBD + +* Clean up the S3 API for all non-libnczarr code. This continues the splitting of PR [Github #3068](https://github.com/Unidata/netcdf-c/pull/3068). +See [Github #3090](https://github.com/Unidata/netcdf-c/pull/3090) for more information. * Step 1 in splitting PR [Github #3068](https://github.com/Unidata/netcdf-c/pull/3068). Update ncjson.[ch] and ncproplist.[ch]. Also fix references to old API. Also fix include/netcdf_ncjson.h and include/netcdf_proplist.h builds. See [Github #3086](https://github.com/Unidata/netcdf-c/pull/3086) for more information. * Provide an auxilliary function, `ncaux_parse_provenance()`, that allows users to parse the _NCProperties attribute into a collection of character pointers. See [Github #3088](https://github.com/Unidata/netcdf-c/pull/3088) for more information. * Step 2 in splitting PR [Github #3068](https://github.com/Unidata/netcdf-c/pull/3068). Fix a namespace problem in tinyxml2.cpp. Note that this is a visual studio problem hence use of _MSC_VER. Also turn off DAP4 tests against Hyrax server until DAP4 spec problems are fixed. See [Github #3089](https://github.com/Unidata/netcdf-c/pull/3089) for more information. diff --git a/include/ncs3sdk.h b/include/ncs3sdk.h index adc7e456be..f81f670efc 100644 --- a/include/ncs3sdk.h +++ b/include/ncs3sdk.h @@ -14,10 +14,16 @@ /* Track the server type, if known */ typedef enum NCS3SVC {NCS3UNK=0, /* unknown */ - NCS3=1, /* s3.amazon.aws */ - NCS3GS=2 /* storage.googleapis.com */ + NCS3=1, /* s3.amazon.aws */ + NCS3GS=2, /* storage.googleapis.com */ +#ifdef NETCDF_ENABLE_ZOH + NCS3ZOH=4, /* ZoH Server */ +#endif } NCS3SVC; +/* Opaque Handles */ +struct NClist; + typedef struct NCS3INFO { char* host; /* non-null if other*/ char* region; /* region */ @@ -55,9 +61,10 @@ EXTERNL int NC_s3sdkbucketdelete(void* s3client, NCS3INFO* info, char** errmsgp) EXTERNL int NC_s3sdkinfo(void* client0, const char* bucket, const char* pathkey, unsigned long long* lenp, char** errmsgp); EXTERNL int NC_s3sdkread(void* client0, const char* bucket, const char* pathkey, unsigned long long start, unsigned long long count, void* content, char** errmsgp); EXTERNL int NC_s3sdkwriteobject(void* client0, const char* bucket, const char* pathkey, unsigned long long count, const void* content, char** errmsgp); -EXTERNL int NC_s3sdkclose(void* s3client0, NCS3INFO* info, int deleteit, char** errmsgp); -EXTERNL int NC_s3sdkgetkeys(void* s3client0, const char* bucket, const char* prefix, size_t* nkeysp, char*** keysp, char** errmsgp); -EXTERNL int NC_s3sdksearch(void* s3client0, const char* bucket, const char* prefixkey0, size_t* nkeysp, char*** keysp, char** errmsgp); +EXTERNL int NC_s3sdkclose(void* s3client0, char** errmsgp); +EXTERNL int NC_s3sdktruncate(void* s3client0, const char* bucket, const char* prefix, char** errmsgp); +EXTERNL int NC_s3sdklist(void* s3client0, const char* bucket, const char* prefix, size_t* nkeysp, char*** keysp, char** errmsgp); +EXTERNL int NC_s3sdklistall(void* s3client0, const char* bucket, const char* prefixkey0, size_t* nkeysp, char*** keysp, char** errmsgp); EXTERNL int NC_s3sdkdeletekey(void* client0, const char* bucket, const char* pathkey, char** errmsgp); /* From ds3util.c */ diff --git a/libdispatch/ds3util.c b/libdispatch/ds3util.c index 2b81f342bc..9401ace692 100644 --- a/libdispatch/ds3util.c +++ b/libdispatch/ds3util.c @@ -25,6 +25,7 @@ #include "nc4internal.h" #include "ncuri.h" #include "nclist.h" +#include "ncbytes.h" #include "ncrc.h" #include "nclog.h" #include "ncs3sdk.h" @@ -34,9 +35,6 @@ /* Alternate .aws directory location */ #define NC_TEST_AWS_DIR "NC_TEST_AWS_DIR" -#define AWSHOST ".amazonaws.com" -#define GOOGLEHOST "storage.googleapis.com" - enum URLFORMAT {UF_NONE=0, UF_VIRTUAL=1, UF_PATH=2, UF_S3=3, UF_OTHER=4}; /* Read these files in order and later overriding earlier */ @@ -47,6 +45,7 @@ static const char* awsconfigfiles[] = {".aws/config",".aws/credentials",NULL}; /* Forward */ static int endswith(const char* s, const char* suffix); +static void freeprofile(struct AWSprofile* profile); static void freeentry(struct AWSentry* e); static int awsparse(const char* text, NClist* profiles); @@ -78,9 +77,9 @@ NC_s3sdkenvironment(void) /* Rebuild an S3 url into a canonical path-style url. If region is not in the host, then use specified region -if provided, otherwise leave blank and let the S3 server deal with it. -@param url (in) the current url -@param s3 (in/out) NCS3INFO structure +if provided, otherwise us-east-1. +@param url (in) the current url +@param s3 (in/out) NCS3INFO structure @param pathurlp (out) the resulting pathified url string */ @@ -309,7 +308,13 @@ NC_s3urlprocess(NCURI* url, NCS3INFO* s3, NCURI** newurlp) /* Rebuild the URL to path format and get a usable region and optional bucket*/ if((stat = NC_s3urlrebuild(url,s3,&url2))) goto done; - s3->host = strdup(url2->host); + if(url2->port){ + char hostport[8192]; + snprintf(hostport,sizeof(hostport),"%s:%s",url2->host,url2->port); + s3->host = strdup(hostport); + }else{ + s3->host = strdup(url2->host); + } /* construct the rootkey minus the leading bucket */ pathsegments = nclistnew(); if((stat = NC_split_delim(url2->path,'/',pathsegments))) goto done; @@ -358,19 +363,29 @@ NC_s3clear(NCS3INFO* s3) } /* -Check if a url has indicators that signal an S3 or Google S3 url. +Check if a url has indicators that signal an S3 or Google S3 url or ZoH S3 url. +The rules are as follows: +1. If the protocol is "s3" or "gs3" or "zoh", then return (true,s3|gs3|zoh). +2. If the mode contains "s3" or "gs3" or "zoh", then return (true,s3|gs3|zoh). +3. Check the host name: +3.1 If the host ends with ".amazonaws.com", then return (true,s3). +3.1 If the host is "storage.googleapis.com", then return (true,gs3). +4. Otherwise return (false,unknown). */ int -NC_iss3(NCURI* uri, enum NCS3SVC* svcp) +NC_iss3(NCURI* uri, NCS3SVC* svcp) { int iss3 = 0; NCS3SVC svc = NCS3UNK; if(uri == NULL) goto done; /* not a uri */ - /* is the protocol "s3" or "gs3" ? */ + /* is the protocol "s3" or "gs3" or "zoh" ? */ if(strcasecmp(uri->protocol,"s3")==0) {iss3 = 1; svc = NCS3; goto done;} if(strcasecmp(uri->protocol,"gs3")==0) {iss3 = 1; svc = NCS3GS; goto done;} +#ifdef NETCDF_ENABLE_ZOH + if(strcasecmp(uri->protocol,"zoh")==0) {iss3 = 1; svc = NCS3ZOH; goto done;} +#endif /* Is "s3" or "gs3" in the mode list? */ if(NC_testmode(uri,"s3")) {iss3 = 1; svc = NCS3; goto done;} if(NC_testmode(uri,"gs3")) {iss3 = 1; svc = NCS3GS; goto done;} @@ -384,18 +399,71 @@ NC_iss3(NCURI* uri, enum NCS3SVC* svcp) return iss3; } -const char* -NC_s3dumps3info(NCS3INFO* info) +/**************************************************/ +/** +The .aws/config and .aws/credentials files +are in INI format (https://en.wikipedia.org/wiki/INI_file). +This format is not well defined, so the grammar used +here is restrictive. Here, the term "profile" is the same +as the INI term "section". + +The grammar used is as follows: + +Grammar: + +inifile: profilelist ; +profilelist: profile | profilelist profile ; +profile: '[' profilename ']' EOL entries ; +entries: empty | entries entry ; +entry: WORD = WORD EOL ; +profilename: WORD ; +Lexical: +WORD sequence of printable characters - [ \[\]=]+ +EOL '\n' | ';' + +Note: +1. The semicolon at beginning of a line signals a comment. +2. # comments are not allowed +3. Duplicate profiles or keys are ignored. +4. Escape characters are not supported. +*/ + +#define AWS_EOF (-1) +#define AWS_ERR (0) +#define AWS_WORD (0x10001) +#define AWS_EOL (0x10002) + +typedef struct AWSparser { + char* text; + char* pos; + size_t yylen; /* |yytext| */ + NCbytes* yytext; + int token; /* last token found */ + int pushback; /* allow 1-token pushback */ +} AWSparser; + +#ifdef LEXDEBUG +static const char* +tokenname(int token) { - static char text[8192]; - snprintf(text,sizeof(text),"host=%s region=%s bucket=%s rootkey=%s profile=%s", - (info->host?info->host:"null"), - (info->region?info->region:"null"), - (info->bucket?info->bucket:"null"), - (info->rootkey?info->rootkey:"null"), - (info->profile?info->profile:"null")); - return text; + static char num[32]; + switch(token) { + case AWS_EOF: return "EOF"; + case AWS_ERR: return "ERR"; + case AWS_WORD: return "WORD"; + default: snprintf(num,sizeof(num),"%d",token); return num; + } + return "UNKNOWN"; } +#endif + +/* +@param text of the aws credentials file +@param profiles list of form struct AWSprofile (see ncauth.h) +*/ + +#define LBR '[' +#define RBR ']' static void freeprofile(struct AWSprofile* profile) @@ -426,6 +494,19 @@ NC_s3freeprofilelist(NClist* profiles) } } +const char* +NC_s3dumps3info(NCS3INFO* info) +{ + static char text[8192]; + snprintf(text,sizeof(text),"host=%s region=%s bucket=%s rootkey=%s profile=%s", + (info->host?info->host:"null"), + (info->region?info->region:"null"), + (info->bucket?info->bucket:"null"), + (info->rootkey?info->rootkey:"null"), + (info->profile?info->profile:"null")); + return text; +} + /* Find, load, and parse the aws config &/or credentials file */ int NC_aws_load_credentials(NCglobalstate* gstate) @@ -707,15 +788,6 @@ tokenname(int token) } #endif -typedef struct AWSparser { - char* text; - char* pos; - size_t yylen; /* |yytext| */ - NCbytes* yytext; - int token; /* last token found */ - int pushback; /* allow 1-token pushback */ -} AWSparser; - static int awslex(AWSparser* parser) { diff --git a/libdispatch/nch5s3comms.c b/libdispatch/nch5s3comms.c index 29aedea326..0fc97deb6f 100644 --- a/libdispatch/nch5s3comms.c +++ b/libdispatch/nch5s3comms.c @@ -92,7 +92,7 @@ #include "netcdf.h" #include "ncuri.h" -#include "ncutil.h" +#include "netcdf_vutils.h" /*****************/ @@ -292,7 +292,6 @@ static int build_range(size_t offset, size_t len, char** rangep); static const char* verbtext(HTTPVerb verb); static int trace(CURL* curl, int onoff); static int sortheaders(VList* headers); -static int httptonc(long httpcode); static void hrb_node_free(hrb_node_t *node); #if S3COMMS_DEBUG_HRB @@ -789,7 +788,7 @@ NCH5_s3comms_s3r_close(s3r_t *handle) *---------------------------------------------------------------------------- */ int -NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long* sizep) +NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long* sizep, long* httpcodep) { int ret_value = SUCCEED; char* contentlength = NULL; @@ -806,7 +805,8 @@ NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long* sizep) if((ret_value = NCH5_s3comms_s3r_head(handle, url, "Content-Length", NULL, &httpcode, &contentlength))) HGOTO_ERROR(H5E_ARGS, ret_value, FAIL, "NCH5_s3comms_s3r_head failed."); - if((ret_value = httptonc(httpcode))) goto done; + /* Content-Length will not be defined if object does not exist */ + if(httpcode == 404) goto done; /****************** * PARSE RESPONSE * @@ -816,7 +816,7 @@ NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long* sizep) if(value == NULL) HGOTO_ERROR(H5E_ARGS, NC_EINVAL, FAIL, "could not find content length value"); value++; - content_length = strtoumax(value, NULL, 0); + content_length = (long long)strtoumax(value, NULL, 0); if (UINTMAX_MAX > SIZE_MAX && content_length > SIZE_MAX) HGOTO_ERROR(H5E_ARGS, NC_ERANGE, FAIL, "content_length overflows size_t"); @@ -825,9 +825,9 @@ NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long* sizep) "could not convert found \"Content-Length\" response (\"%s\")", contentlength); /* range is null-terminated, remember */ - if(sizep) {*sizep = (long long)content_length;} - done: + if(sizep) {*sizep = (long long)content_length;} + if(httpcodep) *httpcodep = httpcode; nullfree(contentlength); return UNTRACEX(ret_value,"size=%lld",(sizep?-1:*sizep)); } /* NCH5_s3comms_s3r_getsize */ @@ -859,17 +859,13 @@ NCH5_s3comms_s3r_deletekey(s3r_t *handle, const char* url, long* httpcodep) if((ret_value = NCH5_s3comms_s3r_execute(handle, url, HTTPDELETE, NULL, NULL, NULL, &httpcode, data))) HGOTO_ERROR(H5E_ARGS, ret_value, FAIL, "execute failed."); - - /****************** - * RESPONSE * - ******************/ - if((ret_value = httptonc(httpcode))) goto done; - if(httpcode != 204) - HGOTO_ERROR(H5E_ARGS, NC_ECANTREMOVE, FAIL, "deletekey failed."); + + /* Apparently, aws delivers a 204 response if it successfully deletes the key */ + if(httpcode == 204) httpcode = 200; /* treat 204 as success */ done: - vsfree(data); if(httpcodep) *httpcodep = httpcode; + vsfree(data); return UNTRACEX(ret_value,"httpcode=%d",INULL(httpcodep)); } /* NCH5_s3comms_s3r_getsize */ @@ -911,12 +907,8 @@ NCH5_s3comms_s3r_head(s3r_t *handle, const char* url, const char* header, const if (SUCCEED != NCH5_s3comms_s3r_execute(handle, url, HTTPHEAD, NULL, header, NULL, &httpcode, data)) HGOTO_ERROR(H5E_ARGS, NC_EINVAL, FAIL, "problem in reading during getsize."); - if((ret_value = httptonc(httpcode))) goto done; - if(header != NULL) { - if(vslength(data) == 0) - HGOTO_ERRORVA(H5E_ARGS, NC_EINVAL, FAIL, "HTTP metadata: header=%s; not found",header); - else if (vslength(data) > CURL_MAX_HTTP_HEADER) + if (vslength(data) > CURL_MAX_HTTP_HEADER) HGOTO_ERROR(H5E_ARGS, NC_EINVAL, FAIL, "HTTP metadata buffer overrun"); #if S3COMMS_DEBUG else @@ -930,7 +922,7 @@ NCH5_s3comms_s3r_head(s3r_t *handle, const char* url, const char* header, const if(header != NULL) { char* content; - content = vsextract(data); + content = vsextract(data); /* guaranteed to exist */ if(valuep) {*valuep = content;} } @@ -1222,7 +1214,7 @@ NCH5_s3comms_s3r_open(const char* root, NCS3SVC svc, const char *region, const c *---------------------------------------------------------------------------- */ int -NCH5_s3comms_s3r_read(s3r_t *handle, const char* url, size_t offset, size_t len, s3r_buf_t* dest) +NCH5_s3comms_s3r_read(s3r_t *handle, const char* url, size_t offset, size_t len, s3r_buf_t* dest, long* httpcodep) { char *rangebytesstr = NULL; int ret_value = SUCCEED; @@ -1251,9 +1243,9 @@ NCH5_s3comms_s3r_read(s3r_t *handle, const char* url, size_t offset, size_t len, if((ret_value = NCH5_s3comms_s3r_execute(handle, url, HTTPGET, rangebytesstr, NULL, NULL, &httpcode, wrap))) HGOTO_ERROR(H5E_ARGS, ret_value, FAIL, "execute failed."); - if((ret_value = httptonc(httpcode))) goto done; done: + if(httpcodep) *httpcodep = httpcode; (void)vsextract(wrap); vsfree(wrap); /* clean any malloc'd resources */ @@ -1271,7 +1263,7 @@ NCH5_s3comms_s3r_read(s3r_t *handle, const char* url, size_t offset, size_t len, *---------------------------------------------------------------------------- */ int -NCH5_s3comms_s3r_write(s3r_t *handle, const char* url, const s3r_buf_t* data) +NCH5_s3comms_s3r_write(s3r_t *handle, const char* url, const s3r_buf_t* data, long* httpcodep) { int ret_value = SUCCEED; VList* otherheaders = vlistnew(); @@ -1301,9 +1293,10 @@ NCH5_s3comms_s3r_write(s3r_t *handle, const char* url, const s3r_buf_t* data) vssetlength(wrap,data->count); if((ret_value = NCH5_s3comms_s3r_execute(handle, url, HTTPPUT, NULL, NULL, (const char**)vlistcontents(otherheaders), &httpcode, wrap))) HGOTO_ERROR(H5E_ARGS, ret_value, FAIL, "execute failed."); - if((ret_value = httptonc(httpcode))) goto done; + done: + if(httpcodep) *httpcodep = httpcode; (void)vsextract(wrap); vsfree(wrap); /* clean any malloc'd resources */ @@ -1321,7 +1314,7 @@ NCH5_s3comms_s3r_write(s3r_t *handle, const char* url, const s3r_buf_t* data) *---------------------------------------------------------------------------- */ int -NCH5_s3comms_s3r_getkeys(s3r_t *handle, const char* url, s3r_buf_t* response) +NCH5_s3comms_s3r_getkeys(s3r_t *handle, const char* url, s3r_buf_t* response, long* httpcodep) { int ret_value = SUCCEED; const char* otherheaders[3] = {"Content-Type", "application/xml", NULL}; @@ -1340,13 +1333,13 @@ NCH5_s3comms_s3r_getkeys(s3r_t *handle, const char* url, s3r_buf_t* response) if((SUCCEED != NCH5_s3comms_s3r_execute(handle, url, HTTPGET, NULL, NULL, otherheaders, &httpcode, content))) HGOTO_ERROR(H5E_ARGS, ret_value, FAIL, "execute failed."); - if((ret_value = httptonc(httpcode))) goto done; if(response) { response->count = vslength(content); response->content = vsextract(content); } done: + if(httpcodep) *httpcodep = httpcode; vsfree(content); /* clean any malloc'd resources */ curl_reset(handle); @@ -1421,7 +1414,7 @@ NCH5_s3comms_aws_canonical_request(VString* canonical_request_dest, VString* sig { hrb_node_t *node = NULL; int ret_value = SUCCEED; - int i; + size_t i; const char* sverb = verbtext(verb); const char* query_params = (query?query:""); @@ -1568,7 +1561,7 @@ NCH5_s3comms_HMAC_SHA256(const unsigned char *key, size_t key_len, const char *m #else if(CURLE_OK != Curl_hmacit(Curl_HMAC_SHA256, key, key_len, - msg, msg_len, + (const unsigned char*)msg, msg_len, md)) HGOTO_ERROR(H5E_ARGS, NC_EINTERNAL, FAIL, "Curl_hmacit failure."); #endif @@ -2052,7 +2045,7 @@ NCH5_s3comms_signing_key(unsigned char **mdp, const char *secret, const char *re HMAC(EVP_sha256(), (const unsigned char *)dateregionservicekey, SHA256_DIGEST_LENGTH, (const unsigned char *)"aws4_request", 12, md, NULL); #else - Curl_hmacit(Curl_HMAC_SHA256, (const unsigned char *)AWS4_secret, (int)nulllen(AWS4_secret), + Curl_hmacit(Curl_HMAC_SHA256, (const unsigned char *)AWS4_secret, nulllen(AWS4_secret), (const unsigned char *)iso8601now, 8, /* 8 --> length of 8 --> "yyyyMMDD" */ datekey); Curl_hmacit(Curl_HMAC_SHA256, (const unsigned char *)datekey, SHA256_DIGEST_LENGTH, (const unsigned char *)region, @@ -2390,7 +2383,8 @@ build_request(s3r_t* handle, NCURI* purl, VString* payload, HTTPVerb verb) { - int i,ret_value = SUCCEED; + int ret_value = SUCCEED; + size_t i; struct curl_slist *curlheaders = NULL; hrb_node_t *node = NULL; hrb_t *request = NULL; @@ -2788,32 +2782,6 @@ sortheaders(VList* headers) return (ret_value); } -static int -httptonc(long httpcode) -{ - int stat = NC_NOERR; - if(httpcode <= 99) stat = NC_EINTERNAL; /* should never happen */ - else if(httpcode <= 199) - stat = NC_NOERR; /* I guess */ - else if(httpcode <= 299) { - switch (httpcode) { - default: stat = NC_NOERR; break; - } - } else if(httpcode <= 399) - stat = NC_NOERR; /* ? */ - else if(httpcode <= 499) { - switch (httpcode) { - case 400: stat = NC_EINVAL; break; - case 401: case 402: case 403: - stat = NC_EAUTH; break; - case 404: stat = NC_EEMPTY; break; - default: stat = NC_EINVAL; break; - } - } else - stat = NC_ES3; - return stat; -} - /**************************************************/ /* Request Tracing */ diff --git a/libdispatch/nch5s3comms.h b/libdispatch/nch5s3comms.h index 7cc482df66..f7c383e037 100644 --- a/libdispatch/nch5s3comms.h +++ b/libdispatch/nch5s3comms.h @@ -506,13 +506,13 @@ EXTERNL s3r_t *NCH5_s3comms_s3r_open(const char* root, NCS3SVC svc, const char* EXTERNL int NCH5_s3comms_s3r_close(s3r_t *handle); -EXTERNL int NCH5_s3comms_s3r_read(s3r_t *handle, const char* url, size_t offset, size_t len, s3r_buf_t* data); +EXTERNL int NCH5_s3comms_s3r_read(s3r_t *handle, const char* url, size_t offset, size_t len, s3r_buf_t* data, long* httpcodep); -EXTERNL int NCH5_s3comms_s3r_write(s3r_t *handle, const char* url, const s3r_buf_t* data); +EXTERNL int NCH5_s3comms_s3r_write(s3r_t *handle, const char* url, const s3r_buf_t* data, long* httpcodep); -EXTERNL int NCH5_s3comms_s3r_getkeys(s3r_t *handle, const char* url, s3r_buf_t* response); +EXTERNL int NCH5_s3comms_s3r_getkeys(s3r_t *handle, const char* url, s3r_buf_t* response, long* httpcodep); -EXTERNL int NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long * sizep); +EXTERNL int NCH5_s3comms_s3r_getsize(s3r_t *handle, const char* url, long long * sizep, long* httpcodep); EXTERNL int NCH5_s3comms_s3r_deletekey(s3r_t *handle, const char* url, long* httpcodep); diff --git a/libdispatch/ncs3sdk_h5.c b/libdispatch/ncs3sdk_h5.c index 359ab2f8b3..27bb0e2038 100644 --- a/libdispatch/ncs3sdk_h5.c +++ b/libdispatch/ncs3sdk_h5.c @@ -101,6 +101,7 @@ static int s3objectsinfo(NClist* contents, NClist* keys, NClist* lens); static int s3commonprefixes(NClist* list, NClist* keys); static int mergekeysets(NClist*,NClist*,NClist*); static int rawtokeys(s3r_buf_t* response, NClist* keys, NClist* lengths, struct LISTOBJECTSV2** listv2p); +static int httptonc(long httpcode); static int queryadd(NClist* query, const char* key, const char* value); static int queryend(NClist* query, char** querystring); @@ -193,7 +194,7 @@ NC_s3sdkcreateclient(NCS3INFO* info) done: nullfree(urlroot); if(stat && s3client) { - NC_s3sdkclose(s3client,info,0,NULL); + NC_s3sdkclose(s3client,NULL); s3client = NULL; } NCNILTRACE(NC_NOERR); @@ -213,6 +214,7 @@ NC_s3sdkbucketexists(void* s3client0, const char* bucket, int* existsp, char** e if((stat = makes3fullpath(s3client->rooturl,bucket,NULL,NULL,url))) goto done; if((stat = NCH5_s3comms_s3r_head(s3client->h5s3client, ncbytescontents(url), NULL, NULL, &httpcode, NULL))) goto done; + stat = httptonc(httpcode); if(existsp) {*existsp = (stat == 0 && httpcode == 200);} done: @@ -255,7 +257,7 @@ NC_s3sdkbucketdelete(void* s3client0, NCS3INFO* info, char** errmsgp) /* @return NC_NOERR if key points to a content-bearing object. -@return NC_EEMPTY if object at key has no content. +@return NC_ENOOBJECT if object at key does not exist @return NC_EXXX return true error */ EXTERNL int @@ -265,13 +267,15 @@ NC_s3sdkinfo(void* s3client0, const char* bucket, const char* pathkey, size64_t* NCS3CLIENT* s3client = (NCS3CLIENT*)s3client0; NCbytes* url = ncbytesnew(); long long len = -1; + long httpcode = 0; NCTRACE(11,"bucket=%s pathkey=%s",bucket,pathkey); if((stat = makes3fullpath(s3client->rooturl,bucket,pathkey,NULL,url))) goto done; - if((stat = NCH5_s3comms_s3r_getsize(s3client->h5s3client, ncbytescontents(url), &len))) goto done; + if((stat = NCH5_s3comms_s3r_getsize(s3client->h5s3client, ncbytescontents(url), &len, &httpcode))) goto done; + stat = httptonc(httpcode); - if(lenp) {*lenp = len;} + if(lenp) {*lenp = (size64_t)len;} done: ncbytesfree(url); @@ -289,6 +293,7 @@ NC_s3sdkread(void* s3client0, const char* bucket, const char* pathkey, size64_t NCS3CLIENT* s3client = (NCS3CLIENT*)s3client0; NCbytes* url = ncbytesnew(); struct s3r_buf_t data = {0,NULL}; + long httpcode = 0; NCTRACE(11,"bucket=%s pathkey=%s start=%llu count=%llu content=%p",bucket,pathkey,start,count,content); @@ -297,8 +302,8 @@ NC_s3sdkread(void* s3client0, const char* bucket, const char* pathkey, size64_t /* Read the data */ data.count = count; data.content = content; - if((stat = NCH5_s3comms_s3r_read(s3client->h5s3client,ncbytescontents(url),(size_t)start,(size_t)count,&data))) goto done; - + if((stat = NCH5_s3comms_s3r_read(s3client->h5s3client,ncbytescontents(url),(size_t)start,(size_t)count,&data,&httpcode))) goto done; + stat = httptonc(httpcode); done: ncbytesfree(url); return NCUNTRACE(stat); @@ -315,6 +320,7 @@ NC_s3sdkwriteobject(void* s3client0, const char* bucket, const char* pathkey, s NCS3CLIENT* s3client = (NCS3CLIENT*)s3client0; NCbytes* url = ncbytesnew(); s3r_buf_t data; + long httpcode = 0; NCTRACE(11,"bucket=%s pathkey=%s count=%llu content=%p",bucket,pathkey,count,content); @@ -323,7 +329,8 @@ NC_s3sdkwriteobject(void* s3client0, const char* bucket, const char* pathkey, s /* Write the data */ data.count = count; data.content = (void*)content; - if((stat = NCH5_s3comms_s3r_write(s3client->h5s3client,ncbytescontents(url),&data))) goto done; + if((stat = NCH5_s3comms_s3r_write(s3client->h5s3client,ncbytescontents(url),&data,&httpcode))) goto done; + stat = httptonc(httpcode); done: ncbytesfree(url); @@ -331,28 +338,49 @@ NC_s3sdkwriteobject(void* s3client0, const char* bucket, const char* pathkey, s } EXTERNL int -NC_s3sdkclose(void* s3client0, NCS3INFO* info, int deleteit, char** errmsgp) +NC_s3sdkclose(void* s3client0, char** errmsgp) { int stat = NC_NOERR; NCS3CLIENT* s3client = (NCS3CLIENT*)s3client0; - NCTRACE(11,"info=%s deleteit=%d",NC_s3dumps3info(info),deleteit); - - if(deleteit) { - /* Delete the root key; ok it if does not exist */ - switch (stat = NC_s3sdkdeletekey(s3client0,info->bucket,info->rootkey,errmsgp)) { - case NC_NOERR: break; - case NC_EEMPTY: case NC_ENOTFOUND: stat = NC_NOERR; break; - default: break; + NCTRACE(11,""); + s3client_destroy(s3client); + return NCUNTRACE(stat); +} + +EXTERNL int +NC_s3sdktruncate(void* s3client0, const char* bucket, const char* prefix, char** errmsgp) +{ + int stat = NC_NOERR; + char* errmsg = NULL; + size_t nkeys; + char** keys = NULL; + NCS3CLIENT* s3client = (NCS3CLIENT*)s3client0; + + NCTRACE(11,"bucket=%s prefix=%s",bucket,prefix); + + if((stat = NC_s3sdklistall(s3client0,bucket,prefix,&nkeys,&keys,&errmsg))) goto done; + + if(nkeys > 0 && keys != NULL) { + size_t i; + /* Sort the list -- shortest first */ + NC_sortenvv(nkeys,keys); + for(i=0;ih5s3client, ncbytescontents(listurl), &response))) goto done; + if((stat = NCH5_s3comms_s3r_getkeys(s3client->h5s3client, ncbytescontents(listurl), &response, &httpcode))) goto done; + if((stat = httptonc(httpcode))) goto done; if((stat = rawtokeys(&response,allkeys,NULL,&listv2))) goto done; istruncated = (strcasecmp(listv2->istruncated,"true")==0?1:0); nullfree(continuetoken); @@ -430,19 +460,19 @@ In theory, the returned list should be sorted in lexical order, but it possible that it is not. */ EXTERNL int -NC_s3sdkgetkeys(void* s3client0, const char* bucket, const char* prefixkey0, size_t* nkeysp, char*** keysp, char** errmsgp) +NC_s3sdklist(void* s3client0, const char* bucket, const char* prefixkey0, size_t* nkeysp, char*** keysp, char** errmsgp) { NCTRACE(11,"bucket=%s prefixkey0=%s",bucket,prefixkey0); return NCUNTRACE(getkeys(s3client0, bucket, prefixkey0, "/", nkeysp, keysp, errmsgp)); } /* -Return a list of full keys of legal objects immediately below a specified key. +Return a list of full keys of legal objects below a specified key. Not necessarily sorted. Essentially same as getkeys, but with no delimiter. */ EXTERNL int -NC_s3sdksearch(void* s3client0, const char* bucket, const char* prefixkey0, size_t* nkeysp, char*** keysp, char** errmsgp) +NC_s3sdklistall(void* s3client0, const char* bucket, const char* prefixkey0, size_t* nkeysp, char*** keysp, char** errmsgp) { NCTRACE(11,"bucket=%s prefixkey0=%s",bucket,prefixkey0); return NCUNTRACE(getkeys(s3client0, bucket, prefixkey0, NULL, nkeysp, keysp, errmsgp)); @@ -461,6 +491,11 @@ NC_s3sdkdeletekey(void* s3client0, const char* bucket, const char* pathkey, char if((stat = makes3fullpath(s3client->rooturl,bucket,pathkey,NULL,url))) goto done; if((stat = NCH5_s3comms_s3r_deletekey(s3client->h5s3client, ncbytescontents(url), &httpcode))) goto done; + switch(stat = httptonc(httpcode)) { + case NC_NOERR: break; + case NC_ENOOBJECT: break; /* does not exist */ + default: goto done; + } done: ncbytesfree(url); @@ -700,8 +735,8 @@ parse_listbucketresult(char* xml, unsigned long long xmllen, struct LISTOBJECTSV if(resultp) {*resultp = result; result = NULL;} done: - if(result) reclaim_listobjectsv2(result); if(doc) ncxml_free(doc); + if(result) reclaim_listobjectsv2(result); return NCTHROW(stat); } @@ -1061,3 +1096,33 @@ queryinsert(NClist* list, char* ekey, char* evalue) return NCTHROW(stat); } +static int +httptonc(long httpcode) +{ + int stat = NC_NOERR; + if(httpcode == 0) + stat = NC_NOERR; + else if(httpcode <= 99) + stat = NC_EINTERNAL; /* should never happen */ + else if(httpcode <= 199) + stat = NC_NOERR; /* I guess */ + else if(httpcode <= 299) { + switch (httpcode) { + case 200: stat = NC_NOERR; break; + case 204: stat = NC_ENOOBJECT; break; + default: stat = NC_NOERR; break; + } + } else if(httpcode <= 399) + stat = NC_NOERR; /* ? */ + else if(httpcode <= 499) { + switch (httpcode) { + case 400: stat = NC_EINVAL; break; + case 401: case 402: case 403: + stat = NC_EAUTH; break; + case 404: stat = NC_ENOOBJECT; break; + default: stat = NC_EINVAL; break; + } + } else + stat = NC_ES3; + return stat; +} diff --git a/libsrc/s3io.c b/libsrc/s3io.c index f8bd941b42..d4381da7a9 100644 --- a/libsrc/s3io.c +++ b/libsrc/s3io.c @@ -184,7 +184,7 @@ s3io_open(const char* path, /* Get the size */ switch (status = NC_s3sdkinfo(s3io->s3client,s3io->s3.bucket,s3io->s3.rootkey,(long long unsigned*)&s3io->size,&s3io->errmsg)) { case NC_NOERR: break; - case NC_EEMPTY: + case NC_ENOOBJECT: s3io->size = 0; goto done; default: @@ -253,7 +253,9 @@ s3io_close(ncio* nciop, int deleteit) assert(s3io != NULL); if(s3io->s3client && s3io->s3.bucket && s3io->s3.rootkey) { - NC_s3sdkclose(s3io->s3client, &s3io->s3, deleteit, &s3io->errmsg); + if(deleteit) + NC_s3sdktruncate(s3io->s3client, s3io->s3.bucket, s3io->s3.rootkey, &s3io->errmsg); + NC_s3sdkclose(s3io->s3client, &s3io->errmsg); } s3io->s3client = NULL; NC_s3clear(&s3io->s3); diff --git a/nczarr_test/s3util.c b/nczarr_test/s3util.c index 9f0c433f73..56fc0cda54 100644 --- a/nczarr_test/s3util.c +++ b/nczarr_test/s3util.c @@ -83,7 +83,7 @@ static void printcontent(size64_t len, const char* content, nc_type nctype); static void usage(void) { - fprintf(stderr,"usage: s3util list|print|upload|download|clear -u [-k ]\n"); + fprintf(stderr,"usage: s3util list|print|upload|download|clear -u [-k ] [-f ]\n"); exit(1); } @@ -112,7 +112,10 @@ static int s3shutdown(int deleteit) { int stat = NC_NOERR; - stat = NC_s3sdkclose(s3sdk.s3client, &s3sdk.s3, deleteit, &s3sdk.errmsg); + if(deleteit) { + stat = s3clear(); + } + stat = NC_s3sdkclose(s3sdk.s3client, &s3sdk.errmsg); return stat; } @@ -258,13 +261,13 @@ s3list(void) if(s3setup()) goto done; - stat = NC_s3sdksearch(s3sdk.s3client, s3sdk.s3.bucket, s3sdk.s3.rootkey, &nkeys, &keys, &s3sdk.errmsg); + stat = NC_s3sdklistall(s3sdk.s3client, s3sdk.s3.bucket, s3sdk.s3.rootkey, &nkeys, &keys, &s3sdk.errmsg); if(stat) goto done; if(nkeys > 0) { size_t i; /* Sort the list -- shortest first */ - nczm_sortenvv(nkeys,keys); + NC_sortenvv(nkeys,keys); for(i=0;i 0 && keys != NULL) { size_t i; /* Sort the list -- shortest first */ - nczm_sortenvv(nkeys,keys); + NC_sortenvv(nkeys,keys); if(dumpoptions.verbose) { printf("deleted keys:\n"); for(i=0;i info=%s\n",dumpoptions.url,NC_s3dumps3info(&s3info)); #endif if((s3client = NC_s3sdkcreateclient(&s3info))==NULL) {CHECK(NC_ES3);} - CHECK(NC_s3sdkgetkeys(s3client, s3info.bucket, dumpoptions.key, &nkeys, &keys, NULL)); - printf("testgetkeys: nkeys=%u; keys:\n",(unsigned)nkeys); + CHECK(NC_s3sdklist(s3client, s3info.bucket, dumpoptions.key, &nkeys, &keys, NULL)); + printf("testlist: nkeys=%u; keys:\n",(unsigned)nkeys); for(i=0;i info=%s\n",dumpoptions.url,NC_s3dumps3info(&s3info)); #endif if((s3client = NC_s3sdkcreateclient(&s3info))==NULL) {CHECK(NC_ES3);} - CHECK(NC_s3sdksearch(s3client, s3info.bucket, dumpoptions.key, &nkeys, &keys, NULL)); - printf("testsearch: nkeys=%u; keys:\n",(unsigned)nkeys); + CHECK(NC_s3sdklistall(s3client, s3info.bucket, dumpoptions.key, &nkeys, &keys, NULL)); + printf("testlistall: nkeys=%u; keys:\n",(unsigned)nkeys); for(i=0;i +#endif +#ifdef HAVE_GETOPT_H +#include +#endif + +#if defined(_WIN32) && !defined(__MINGW32__) +#include "XGetopt.h" +#endif + +#include "zincludes.h" +#include "ncs3sdk.h" +#include "ncpathmgr.h" +#include "nclog.h" +#include "ncuri.h" +#include "netcdf_aux.h" + +#undef NODELETE + +#undef DEBUG + +#define DATANAME "data" + +typedef enum S3op { +S3OP_NONE=0, +S3OP_LIST=1, +S3OP_CLEAR=2, +S3OP_PRINT=3, +S3OP_UPLOAD=4, +S3OP_DOWNLOAD=5, +} S3op; + +static struct S3ops { + S3op s3op; + const char* opnames[3]; +} s3ops[] = { +{S3OP_LIST,{"list","l",NULL}}, +{S3OP_CLEAR,{"clear","c",NULL}}, +{S3OP_PRINT,{"print","p",NULL}}, +{S3OP_UPLOAD,{"upload","u",NULL}}, +{S3OP_DOWNLOAD,{"download","d",NULL}}, +{S3OP_NONE,{NULL,NULL,NULL}}, +}; + +/* Command line options */ +struct Dumpptions { + int debug; + int verbose; + S3op s3op; + NCURI* url; + char* key; /* via -k flag */ + char* rootkey; /* from url | key */ + nc_type nctype; /* for printing content */ + char* filename; + char* profile; +} dumpoptions; + +struct S3SDK { + NCS3INFO s3; + void* s3client; + char* errmsg; +} s3sdk; + +/* Forward */ +static int s3list(void); +static int s3clear(void); +static int s3print(void); +static int s3upload(void); +static int s3download(void); +static nc_type typefor(const char* t); +static void printcontent(size64_t len, const char* content, nc_type nctype); + +static void +usage(void) +{ + fprintf(stderr,"usage: s3util list|print|upload|download|clear -u [-k ] [-f ]\n"); + exit(1); +} + +static S3op +decodeop(const char* name) +{ + struct S3ops* s3op = s3ops; + const char** s = NULL; + for(;s3op->opnames[0] != NULL;s3op++) { + for(s=s3op->opnames;*s;s++) { + if(strcasecmp(*s,name)==0) return s3op->s3op; + } + } + return S3OP_NONE; +} + +static int +s3setup(void) +{ + int stat = NC_NOERR; + s3sdk.s3client = NC_s3sdkcreateclient(&s3sdk.s3); + return stat; +} + +static int +s3shutdown(int deleteit) +{ + int stat = NC_NOERR; + if(deleteit) { + stat = s3clear(); + } + stat = NC_s3sdkclose(s3sdk.s3client, &s3sdk.errmsg); + return stat; +} + +int +main(int argc, char** argv) +{ + int stat = NC_NOERR; + int c; + char* tmp = NULL; + + nc_initialize(); + + memset((void*)&dumpoptions,0,sizeof(dumpoptions)); + + dumpoptions.nctype = NC_UBYTE; /* default */ + + while ((c = getopt(argc, argv, "df:hk:p:t:T:u:v")) != EOF) { + switch(c) { + case 'd': + dumpoptions.debug = 1; + break; + case 'f': + dumpoptions.filename = strdup(optarg); + break; + case 'h': + usage(); + return 0; + case 'k': { + size_t len = strlen(optarg); + dumpoptions.key = (char*)malloc(len+1+1); + if(*optarg != '/') { + fprintf(stderr,"warning: -k option does not start with '/': %s",optarg); + dumpoptions.key[0] = '/'; + memcpy(dumpoptions.key+1,optarg,len); + len++; + } else + memcpy(dumpoptions.key,optarg,strlen(optarg)); + dumpoptions.key[len] = '\0'; + } break; + case 'p': + dumpoptions.profile = strdup(optarg); + break; + case 't': + dumpoptions.nctype = typefor(optarg); + break; + case 'u': { + char* p = NC_shellUnescape(optarg); + ncuriparse(p,&dumpoptions.url); + nullfree(p); + if(dumpoptions.url == NULL) { + fprintf(stderr,"malformed -u option: %s",optarg); + stat = NC_EINVAL; + goto done; + } + } break; + case 'v': + dumpoptions.verbose = 1; + break; + case 'T': + nctracelevel(atoi(optarg)); + break; + case '?': + fprintf(stderr,"unknown option\n"); + stat = NC_EINVAL; + goto done; + } + } + + /* get command argument */ + argc -= optind; + argv += optind; + + if (argc > 1) { + int j; + fprintf(stderr, "s3util: only one command argument permitted:"); + for(j=0;j 0) { + size_t i; + /* Sort the list -- shortest first */ + NC_sortenvv(nkeys,keys); + for(i=0;i\n"); + +done: + s3shutdown(0); + NCZ_freeenvv(nkeys,keys); + return stat; +} + +static int +s3clear(void) +{ + int stat = NC_NOERR; + size_t nkeys = 0; + char** keys = NULL; + + if(s3setup()) goto done; + + if((stat = NC_s3sdklistall(s3sdk.s3client, s3sdk.s3.bucket, s3sdk.s3.rootkey, &nkeys, &keys, &s3sdk.errmsg))) goto done; + + if(nkeys > 0 && keys != NULL) { + size_t i; + /* Sort the list -- shortest first */ + NC_sortenvv(nkeys,keys); + if(dumpoptions.verbose) { + printf("deleted keys:\n"); + for(i=0;i\n"); + return; + } + + if(nctype == NC_STRING) printf("|"); + for(i=0;i 0) printf(", "); + switch(nctype) { + case NC_BYTE: printf("%d",((char*)content)[i]); break; + case NC_SHORT: printf("%d",((short*)content)[i]); break; + case NC_INT: printf("%d",((int*)content)[i]); break; + case NC_INT64: printf("%lld",((long long*)content)[i]); break; + case NC_UBYTE: printf("%u",((unsigned char*)content)[i]); break; + case NC_USHORT: printf("%u",((unsigned short*)content)[i]); break; + case NC_UINT: printf("%u",((unsigned int*)content)[i]); break; + case NC_UINT64: printf("%llu",((unsigned long long*)content)[i]); break; + case NC_FLOAT: printf("%f",((float*)content)[i]); break; + case NC_DOUBLE: printf("%lf",((double*)content)[i]); break; + case NC_STRING: putc(content[i],stdout); break; + default: abort(); + } + } + if(nctype == NC_STRING) printf("|\n"); +} + +static nc_type +typefor(const char* t) +{ + if(strcmp(t,"NC_BYTE")==0) return NC_BYTE; + else if(strcmp(t,"NC_SHORT")==0) return NC_SHORT; + else if(strcmp(t,"NC_INT")==0) return NC_INT; + else if(strcmp(t,"NC_INT64")==0) return NC_INT64; + else if(strcmp(t,"NC_UBYTE")==0) return NC_UBYTE; + else if(strcmp(t,"NC_USHORT")==0) return NC_USHORT; + else if(strcmp(t,"NC_UINT")==0) return NC_UINT; + else if(strcmp(t,"NC_UINT64")==0) return NC_UINT64; + else if(strcmp(t,"NC_FLOAT")==0) return NC_FLOAT; + else if(strcmp(t,"NC_DOUBLE")==0) return NC_DOUBLE; + else if(strcmp(t,"NC_STRING")==0) return NC_STRING; + return NC_NAT; +}