diff --git a/GNUmakefile b/GNUmakefile index 8bc19fb..21702a5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -142,6 +142,12 @@ ifndef CFLAGS endif endif +ifeq ($(LIBS3_DEBUG),YES) + CFLAGS += -DLIBS3_DEBUG -DSIGNATURE_DEBUG +else + CFLAGS += -ULIBS3_DEBUG -USIGNATURE_DEBUG -DNDEBUG +endif + CFLAGS += -Wall -Werror -Wshadow -Wextra -Iinc \ $(CURL_CFLAGS) $(LIBXML2_CFLAGS) \ -DLIBS3_VER_MAJOR=\"$(LIBS3_VER_MAJOR)\" \ @@ -245,7 +251,7 @@ libs3: $(LIBS3_SHARED) $(LIBS3_STATIC) LIBS3_SOURCES := bucket.c bucket_metadata.c error_parser.c general.c \ object.c request.c request_context.c \ response_headers_handler.c service_access_logging.c \ - service.c simplexml.c util.c multipart.c + service.c simplexml.c util.c multipart.c md5base64.c $(LIBS3_SHARED): $(LIBS3_SOURCES:%.c=$(BUILD)/obj/%.do) $(QUIET_ECHO) $@: Building shared library diff --git a/inc/libs3.h b/inc/libs3.h index 3318ef2..aded9e4 100644 --- a/inc/libs3.h +++ b/inc/libs3.h @@ -2218,6 +2218,40 @@ void S3_delete_object(const S3BucketContext *bucketContext, const char *key, int timeoutMs, const S3ResponseHandler *handler, void *callbackData); +/** + * An array of allocated DeleteMultipleObjectSingleResult is passed to + * S3_delete_multiple_objects. The array must be allocated and freed by the + * client code. + **/ +typedef struct DeleteMultipleObjectSingleResult +{ + char key[S3_MAX_KEY_SIZE]; + int keyLen; + S3Status status; +} DeleteMultipleObjectSingleResult; + +/** + * Deletes multiple object from S3. + * + * @param bucketContext gives the bucket and associated parameters for this + * request + * @param keysCount is has the number of keys in the array + * @param keys is an array of key of the objects to delete + * @param requestContext if non-NULL, gives the S3RequestContext to add this + * request to, and does not perform the request immediately. If NULL, + * performs the request immediately and synchronously. + * @param timeoutMs if not 0 contains total request timeout in milliseconds + * @param handler gives the callbacks to call as the request is processed and + * completed + * @param callbackData will be passed in as the callbackData parameter to + * all callbacks for this request + **/ +void S3_delete_multiple_objects(const S3BucketContext *bucketContext, + int keysCount, const char *keys[], + DeleteMultipleObjectSingleResult **results, int *resultsLen, int *errorCount, + S3RequestContext *requestContext, + int timeoutMs, + const S3ResponseHandler *handler, void *callbackData); /** ************************************************************************** * Access Control List Functions @@ -2619,4 +2653,15 @@ void S3_list_multipart_uploads(S3BucketContext *bucketContext, } #endif +#ifdef LIBS3_DEBUG + +#define debug_printf(fmt, ...) \ + fprintf(stderr, fmt"\n", __VA_ARGS__) + +#else + +#define debug_printf(fmt, ...) + +#endif + #endif /* LIBS3_H */ diff --git a/inc/md5base64.h b/inc/md5base64.h new file mode 100644 index 0000000..5d6b966 --- /dev/null +++ b/inc/md5base64.h @@ -0,0 +1,41 @@ +/** ************************************************************************** + * md5base64.h + * + * Copyright 2008 Bryan Ischo + * + * This file is part of libs3. + * + * libs3 is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, version 3 or above of the License. You can also + * redistribute and/or modify it under the terms of the GNU General Public + * License, version 2 or above of the License. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of this library and its programs with the + * OpenSSL library, and distribute linked combinations including the two. + * + * libs3 is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with libs3, in a file named COPYING. If not, see + * . + * + * You should also have received a copy of the GNU General Public License + * version 2 along with libs3, in a file named COPYING-GPLv2. If not, see + * . + * + ************************************************************************** **/ + +#ifndef MD5BASE64_H +#define MD5BASE64_H + +#define MD5_BASE64_BUFFER_LENGTH 16 * 2 + +// Calculate MD5 and encode it as base64 +void generate_content_md5(const char* data, int size, char* retBuffer, int retBufferSize); + +#endif //MD5BASE64_H diff --git a/src/bucket_metadata.c b/src/bucket_metadata.c index 05f3e67..be308b4 100644 --- a/src/bucket_metadata.c +++ b/src/bucket_metadata.c @@ -33,15 +33,9 @@ #include #include -#ifndef __APPLE__ - #include - #include - #include - #include -#endif - #include "libs3.h" #include "request.h" +#include "md5base64.h" // Use a rather arbitrary max size for the document of 64K #define ACL_XML_DOC_MAXSIZE (64 * 1024) @@ -482,45 +476,6 @@ void S3_get_lifecycle(const S3BucketContext *bucketContext, } -#ifndef __APPLE__ -// Calculate MD5 and encode it as base64 -void generate_content_md5(const char* data, int size, - char* retBuffer, int retBufferSize) { - MD5_CTX mdContext; - BIO *bio, *b64; - BUF_MEM *bufferPtr; - - char md5Buffer[MD5_DIGEST_LENGTH]; - - MD5_Init(&mdContext); - MD5_Update(&mdContext, data, size); - MD5_Final((unsigned char*)md5Buffer, &mdContext); - - - b64 = BIO_new(BIO_f_base64()); - bio = BIO_new(BIO_s_mem()); - bio = BIO_push(b64, bio); - - BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Ignore newlines - write everything in one line - BIO_write(bio, md5Buffer, sizeof(md5Buffer)); - (void) BIO_flush(bio); - BIO_get_mem_ptr(bio, &bufferPtr); - (void) BIO_set_close(bio, BIO_NOCLOSE); - - if ((unsigned int)retBufferSize + 1 < bufferPtr->length) { - retBuffer[0] = '\0'; - BIO_free_all(bio); - return; - } - - memcpy(retBuffer, bufferPtr->data, bufferPtr->length); - retBuffer[bufferPtr->length] = '\0'; - - BIO_free_all(bio); -} -#endif - - void S3_set_lifecycle(const S3BucketContext *bucketContext, const char *lifecycleXmlDocument, S3RequestContext *requestContext, @@ -535,7 +490,7 @@ void S3_set_lifecycle(const S3BucketContext *bucketContext, (*(handler->completeCallback))(S3StatusNotSupported, 0, callbackData); return; #else - char md5Base64[MD5_DIGEST_LENGTH * 2]; + char md5Base64[MD5_BASE64_BUFFER_LENGTH]; SetXmlData *data = (SetXmlData *) malloc(sizeof(SetXmlData)); if (!data) { diff --git a/src/md5base64.c b/src/md5base64.c new file mode 100644 index 0000000..11bf048 --- /dev/null +++ b/src/md5base64.c @@ -0,0 +1,84 @@ +/** ************************************************************************** + * md5base64.c + * + * Copyright 2008 Bryan Ischo + * + * This file is part of libs3. + * + * libs3 is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, version 3 or above of the License. You can also + * redistribute and/or modify it under the terms of the GNU General Public + * License, version 2 or above of the License. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of this library and its programs with the + * OpenSSL library, and distribute linked combinations including the two. + * + * libs3 is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with libs3, in a file named COPYING. If not, see + * . + * + * You should also have received a copy of the GNU General Public License + * version 2 along with libs3, in a file named COPYING-GPLv2. If not, see + * . + * + ************************************************************************** **/ + +#ifndef __APPLE__ +#include +#include +#include +#include + +#include + +// Calculate MD5 and encode it as base64 +void generate_content_md5(const char* data, int size, + char* retBuffer, int retBufferSize) { + MD5_CTX mdContext; + BIO *bio, *b64; + BUF_MEM *bufferPtr; + + char md5Buffer[MD5_DIGEST_LENGTH]; + + MD5_Init(&mdContext); + MD5_Update(&mdContext, data, size); + MD5_Final((unsigned char*)md5Buffer, &mdContext); + + + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new(BIO_s_mem()); + bio = BIO_push(b64, bio); + + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Ignore newlines - write everything in one line + BIO_write(bio, md5Buffer, sizeof(md5Buffer)); + (void) BIO_flush(bio); + BIO_get_mem_ptr(bio, &bufferPtr); + (void) BIO_set_close(bio, BIO_NOCLOSE); + +#if OPENSSL_VERSION_NUMBER < 0x1000207fL + // Older version of OpenSSL have buffer lengths as ints + // 0x1000207fL is just an arbitrary version based on Ubuntu 16.04 + if (retBufferSize + 1 < bufferPtr->length) { +#else + // Newer version have size_t instead of int + if ((size_t)(retBufferSize + 1UL) < bufferPtr->length) { +#endif + retBuffer[0] = '\0'; + BIO_free_all(bio); + return; + } + + memcpy(retBuffer, bufferPtr->data, bufferPtr->length); + retBuffer[bufferPtr->length] = '\0'; + + BIO_free_all(bio); +} +#endif + diff --git a/src/object.c b/src/object.c index e8df4b0..4c0e753 100644 --- a/src/object.c +++ b/src/object.c @@ -34,7 +34,15 @@ #include #include "libs3.h" #include "request.h" +#include "md5base64.h" +#ifdef LIBS3_DEBUG +#define _return(status) \ + do { debug_printf("%s:%d return %s\n", __FILE__, __LINE__, S3_get_status_name(status)); return status; } while(0) +#else +#define _return(status) \ + return status +#endif // put object ---------------------------------------------------------------- @@ -401,3 +409,291 @@ void S3_delete_object(const S3BucketContext *bucketContext, const char *key, // Perform the request request_perform(¶ms, requestContext); } + +// delete multiple objects -------------------------------------------------------------- + +// Assuming average overhead of 28 chars per key; maximum 1000 keys supported +#define MULTI_DELETE_XML_DOC_MAXSIZE ((S3_MAX_KEY_SIZE + 28) * 1000) + +typedef struct DeleteMultipleObjectsData +{ + char md5Base64[MD5_BASE64_BUFFER_LENGTH]; + + // bodge to add Content-Type header + S3PutProperties putProperties; + + S3ResponsePropertiesCallback *responsePropertiesCallback; + S3ResponseCompleteCallback *responseCompleteCallback; + void *callbackData; + + string_buffer(deleteXmlDocument, MULTI_DELETE_XML_DOC_MAXSIZE); + int deleteXmlDocumentBytesWritten; + + string_buffer(deleteResponseXmlDocument, MULTI_DELETE_XML_DOC_MAXSIZE); + + int keysCount;; + + DeleteMultipleObjectSingleResult **results; + int *resultsLen; + + int *errorCount; +} DeleteMultipleObjectsData; + +static int deleteMultipleObjectDataCallback(int bufferSize, char *buffer, void *callbackData) +{ + DeleteMultipleObjectsData *dmoData = (DeleteMultipleObjectsData *) callbackData; + + int remaining = (dmoData->deleteXmlDocumentLen - dmoData->deleteXmlDocumentBytesWritten); +debug_printf("bufferSize: %d", bufferSize); + int toCopy = bufferSize > remaining ? remaining : bufferSize; + + if (!toCopy) { + return 0; + } + + memcpy(buffer, &(dmoData->deleteXmlDocument + [dmoData->deleteXmlDocumentBytesWritten]), toCopy); + + dmoData->deleteXmlDocumentBytesWritten += toCopy; + + return toCopy; +} + +static S3Status deleteMultipleObjectPropertiesCallback + (const S3ResponseProperties *responseProperties, void *callbackData) +{ + DeleteMultipleObjectsData *dmoData = (DeleteMultipleObjectsData *) callbackData; + +debug_printf("dmoData->responsePropertiesCallback %p", dmoData->responsePropertiesCallback); + + return (*(dmoData->responsePropertiesCallback)) + (responseProperties, dmoData->callbackData); +} + +static S3Status deleteMultipleObjectResponseDataCallback(int bufferSize, const char *buffer, + void *callbackData) +{ + DeleteMultipleObjectsData *dmoData = (DeleteMultipleObjectsData *) callbackData; + + int fit; + + string_buffer_append(dmoData->deleteResponseXmlDocument, buffer, bufferSize, fit); + + _return((fit ? S3StatusOK : S3StatusXmlDocumentTooLarge)); +} + +static S3Status convertDeleteMultipleObjectXmlCallback(const char *elementPath, + const char *data, int dataLen, + void *callbackData) +{ +debug_printf("convertDeleteMultipleObjectXmlCallback: elementPath:%s, data:%.*s", elementPath, dataLen, data); + DeleteMultipleObjectsData *dmoData = (DeleteMultipleObjectsData *) callbackData; + static int errorCodeLen = 0; + static char errorCode[100] = { '\0' }; + + if (data) { + if (dmoData->results) { + int fit; + int deletedNode = (0 == strcmp(elementPath, "DeleteResult/Deleted/Key")); + int errorNode = (0 == strcmp(elementPath, "DeleteResult/Error/Key")); + if (deletedNode || errorNode) { + string_buffer_append(dmoData->results[*(dmoData->resultsLen)]->key, data, dataLen, fit); + if (!fit) + return S3StatusKeyTooLong; + } + else if(!strcmp(elementPath, "DeleteResult/Error/Code")) { + dmoData->results[*(dmoData->resultsLen)]->status = S3StatusErrorUnknown; + string_buffer_append(errorCode, data, dataLen, fit); + if (fit) { + if (!strcmp(errorCode, "AccessDenied")) + dmoData->results[*(dmoData->resultsLen)]->status = S3StatusErrorAccessDenied; + else if (!strcmp(errorCode, "InternalError")) + dmoData->results[*(dmoData->resultsLen)]->status = S3StatusErrorInternalError; + } + } + } + } else { + if (dmoData->results) { + int deletedNode = (0 == strcmp(elementPath, "DeleteResult/Deleted")); + int errorNode = (0 == strcmp(elementPath, "DeleteResult/Error")); + if (deletedNode || errorNode) { + + (*(dmoData->resultsLen))++; + + if (errorNode && dmoData->errorCount) + (*(dmoData->errorCount))++; + + if (*(dmoData->resultsLen) < dmoData->keysCount) { +debug_printf("Init: dmoData->resultsLen: %d", *(dmoData->resultsLen)); +debug_printf("Init: dmoData->results[*(dmoData->resultsLen)]->key: %p", dmoData->results[*(dmoData->resultsLen)]->key); + string_buffer_initialize(dmoData->results[*(dmoData->resultsLen)]->key); + dmoData->results[*(dmoData->resultsLen)]->status = errorNode? S3StatusErrorUnknown: S3StatusOK; + + string_buffer_initialize(errorCode); + } + } + } + else if(dmoData->errorCount && !strcmp(elementPath, "DeleteResult/Error")) { + (*(dmoData->errorCount))++; + } + } + + return S3StatusOK; +} + +S3Status S3_convert_delete_multiple_response(DeleteMultipleObjectsData *dmoData) +{ + if (dmoData->errorCount || dmoData->results) { + // kill valgrind warning + string_buffer_initialize(dmoData->results[0]->key); + + // Use a simplexml parser + SimpleXml simpleXml; + simplexml_initialize(&simpleXml, &convertDeleteMultipleObjectXmlCallback, dmoData); + + S3Status status = simplexml_add(&simpleXml, dmoData->deleteResponseXmlDocument, dmoData->deleteResponseXmlDocumentLen); + + simplexml_deinitialize(&simpleXml); + + return status; + } + + return S3StatusOK; +} + +static void deleteMultipleObjectCompleteCallback(S3Status requestStatus, + const S3ErrorDetails *s3ErrorDetails, + void *callbackData) +{ + DeleteMultipleObjectsData *dmoData = (DeleteMultipleObjectsData *) callbackData; + + if (requestStatus == S3StatusOK) { + // Parse the document + requestStatus = S3_convert_delete_multiple_response(dmoData); + } + + (*(dmoData->responseCompleteCallback)) + (requestStatus, s3ErrorDetails, dmoData->callbackData); + + free(dmoData); +} + +S3Status generateDeleteMultipleObjectsXmlDocument(DeleteMultipleObjectsData *dmoData, int keysCount, const char *keys[]) +{ +#define append(fmt, ...) \ + do { \ + dmoData->deleteXmlDocumentLen += snprintf \ + (&(dmoData->deleteXmlDocument[dmoData->deleteXmlDocumentLen]), \ + sizeof(dmoData->deleteXmlDocument) - dmoData->deleteXmlDocumentLen - 1, \ + fmt, __VA_ARGS__); \ + if ((unsigned int)dmoData->deleteXmlDocumentLen >= sizeof(dmoData->deleteXmlDocument)) { \ + _return(S3StatusXmlDocumentTooLarge); \ + } \ + } while (0) + + append("%s", "false"); + int i = 0; + for (; i < keysCount; ++i) { + append("%s", keys[i]); + } + append("%s", ""); + + debug_printf("dmoData->deleteXmlDocumentLen: %*s", dmoData->deleteXmlDocumentLen, dmoData->deleteXmlDocument); + + return S3StatusOK; +} + +void S3_delete_multiple_objects(const S3BucketContext *bucketContext, + int keysCount, const char *keys[], + DeleteMultipleObjectSingleResult **results, int *resultsLen, int *errorCount, + S3RequestContext *requestContext, + int timeoutMs, + const S3ResponseHandler *handler, void *callbackData) +{ +#ifdef __APPLE__ + /* This request requires calculating MD5 sum. + * MD5 sum requires OpenSSL library, which is not used on Apple. + * TODO Implement some MD5+Base64 caculation on Apple + */ + (*(handler->completeCallback))(S3StatusNotSupported, 0, callbackData); + return; +#else + DeleteMultipleObjectsData *dmoData = (DeleteMultipleObjectsData *) malloc(sizeof(DeleteMultipleObjectsData)); + if (!dmoData) { + (*(handler->completeCallback))(S3StatusOutOfMemory, 0, callbackData); + return; + } + + dmoData->responsePropertiesCallback = handler->propertiesCallback; + dmoData->responseCompleteCallback = handler->completeCallback; + dmoData->callbackData = callbackData; + + dmoData->errorCount = errorCount; + + if (resultsLen && results) { + dmoData->results = results; + dmoData->resultsLen = resultsLen; + + // initialise the result length to 0 + *resultsLen = 0; + } else { + dmoData->results = 0; + dmoData->resultsLen = 0; + } + + dmoData->keysCount = keysCount; + + string_buffer_initialize(dmoData->deleteXmlDocument); + dmoData->deleteXmlDocumentBytesWritten = 0; + + string_buffer_initialize(dmoData->deleteResponseXmlDocument); + + *errorCount = 0; + + S3Status status = generateDeleteMultipleObjectsXmlDocument(dmoData, keysCount, keys); + if (status != S3StatusOK) { + free(dmoData); + (*(handler->completeCallback))(status, 0, callbackData); + return; + } + + // md5 for the request data + + generate_content_md5(dmoData->deleteXmlDocument, dmoData->deleteXmlDocumentLen, dmoData->md5Base64, sizeof(dmoData->md5Base64)); + + dmoData->putProperties = (S3PutProperties){ "application/xml", dmoData->md5Base64, 0, 0, 0, 0, 0, 0, 0, 0 }; + + // Set up the RequestParams + RequestParams params = + { + HttpRequestTypePOST, // httpRequestType + { bucketContext->hostName , // hostname + bucketContext->bucketName, // bucketName + bucketContext->protocol, // protocol + bucketContext->uriStyle, // uriStyle + bucketContext->accessKeyId, // accessKeyId + bucketContext->secretAccessKey, // secretAccessKey + bucketContext->securityToken, // securityToken + bucketContext->authRegion }, // authRegion + 0, // key + 0, // queryParams + "delete", // subResource + 0, // copySourceBucketName + 0, // copySourceKey + 0, // getConditions + 0, // startByte + 0, // byteCount + &dmoData->putProperties, // putProperties + &deleteMultipleObjectPropertiesCallback, // propertiesCallback + &deleteMultipleObjectDataCallback, // toS3Callback + dmoData->deleteXmlDocumentLen, // toS3CallbackTotalSize + &deleteMultipleObjectResponseDataCallback, // fromS3Callback + &deleteMultipleObjectCompleteCallback, // completeCallback + dmoData, // callbackData + timeoutMs // timeoutMs + }; + + // Perform the request + request_perform(¶ms, requestContext); +#endif +} diff --git a/src/s3.c b/src/s3.c index 4343c9e..67b1a25 100644 --- a/src/s3.c +++ b/src/s3.c @@ -166,7 +166,10 @@ static char putenvBufG[256]; #define TARGET_PREFIX_PREFIX_LEN (sizeof(TARGET_PREFIX_PREFIX) - 1) #define HTTP_METHOD_PREFIX "method=" #define HTTP_METHOD_PREFIX_LEN (sizeof(HTTP_METHOD_PREFIX) - 1) - +#define BUCKET_PREFIX "bucket=" +#define BUCKET_PREFIX_LEN (sizeof(BUCKET_PREFIX) - 1) +#define OBJECT_KEY "key=" +#define OBJECT_KEY_LEN (sizeof(OBJECT_KEY) - 1) // util ---------------------------------------------------------------------- @@ -242,6 +245,10 @@ static void usageExit(FILE *out) " delete : Delete a bucket or key\n" " [/] : Bucket or bucket/key to delete\n" "\n" +" delete-multiple : Delete multiple objects from one bucket\n" +" bucket : Bucket to delete from\n" +" key : key(s) to delete\n" +"\n" " list : List bucket contents\n" " : Bucket to list\n" " [prefix] : Prefix for results set\n" @@ -2015,6 +2022,104 @@ static void delete_object(int argc, char **argv, int optindex) S3_deinitialize(); } +// delete multiple objects ------------------------------------------------------------- + +static void delete_multiple_objects(int argc, char **argv, int optindex) +{ + (void) argc; + + const char *bucket = 0; + const char *keys[1000]; + int keysCount = 0; + + int errorsCount = 0; + int resultsCount = 0; + DeleteMultipleObjectSingleResult* results[1000]; + + while (optindex < argc) { + char *param = argv[optindex++]; + if (!strncmp(param, OBJECT_KEY, OBJECT_KEY_LEN)) { + results[keysCount] = (DeleteMultipleObjectSingleResult*) malloc(sizeof(DeleteMultipleObjectSingleResult)); + keys[keysCount++] = &(param[OBJECT_KEY_LEN]); + } + else if (!strncmp(param, BUCKET_PREFIX, BUCKET_PREFIX_LEN)) { + bucket = &(param[BUCKET_PREFIX_LEN]); + } + else { + fprintf(stderr, "\nERROR: Unknown param: %s\n", param); + usageExit(stderr); + } + } + + if (!bucket || !keysCount) { + if (!bucket) + fprintf(stderr, "\nERROR: Bucket not specified\n"); + if (!keysCount) + fprintf(stderr, "\nERROR: No keys specified\n"); + usageExit(stderr); + } + + S3_init(); + + S3BucketContext bucketContext = + { + 0, + bucket, + protocolG, + uriStyleG, + accessKeyIdG, + secretAccessKeyG, + 0, + awsRegionG + }; + + S3ResponseHandler responseHandler = + { + &responsePropertiesCallback, + &responseCompleteCallback + }; + + do { + S3_delete_multiple_objects( + &bucketContext, + keysCount, keys, + results, &resultsCount, &errorsCount, + 0, + timeoutMsG, + &responseHandler, 0); + } while (S3_status_is_retryable(statusG) && should_retry()); + + int i; + if (statusG != S3StatusOK) { + printError(); + } + else { + if (keysCount != resultsCount) { + fprintf(stderr, "\nERROR: Number of keys does not match results got from S3\n"); + } + + debug_printf("resultsCount: %d keysCount: %d", resultsCount, keysCount); + + for (i = 0; i < keysCount; ++i) { + debug_printf("results[i]->key %s", results[i]->key); + if (i < resultsCount) { + printf("\n%s/%s ", bucket, results[i]->key); + if (results[i]->status) { + printf("ERROR:%s", S3_get_status_name(results[i]->status)); + } + else { + printf("OK"); + } + } + } + printf("\n"); + } + + for (i = 0; i < keysCount; ++i) + free(results[i]); + + S3_deinitialize(); +} // put object ---------------------------------------------------------------- @@ -4001,6 +4106,9 @@ int main(int argc, char **argv) delete_bucket(argc, argv, optind); } } + else if (!strcmp(command, "delete-multiple")) { + delete_multiple_objects(argc, argv, optind); + } else if (!strcmp(command, "put")) { put_object(argc, argv, optind, NULL, NULL, 0); } diff --git a/src/simplexml.c b/src/simplexml.c index 4f26101..ec77796 100644 --- a/src/simplexml.c +++ b/src/simplexml.c @@ -122,6 +122,7 @@ static void saxCharacters(void *user_data, const xmlChar *ch, int len) return; } + debug_printf("simpleXml->elementPath: %s data:%.*s", simpleXml->elementPath, len, ch); simpleXml->status = (*(simpleXml->callback)) (simpleXml->elementPath, (char *) ch, len, simpleXml->callbackData); } @@ -198,6 +199,8 @@ void simplexml_deinitialize(SimpleXml *simpleXml) S3Status simplexml_add(SimpleXml *simpleXml, const char *data, int dataLen) { + debug_printf("XML buf: %.*s", dataLen, data); + if (!simpleXml->xmlParser && (!(simpleXml->xmlParser = xmlCreatePushParserCtxt (&saxHandlerG, simpleXml, 0, 0, 0)))) {