Fix BlueSky build on modern Linux and libs3.
[bluesky.git] / libs3-1.4 / src / s3.c
1 /** **************************************************************************
2  * s3.c
3  * 
4  * Copyright 2008 Bryan Ischo <bryan@ischo.com>
5  * 
6  * This file is part of libs3.
7  * 
8  * libs3 is free software: you can redistribute it and/or modify it under the
9  * terms of the GNU General Public License as published by the Free Software
10  * Foundation, version 3 of the License.
11  *
12  * In addition, as a special exception, the copyright holders give
13  * permission to link the code of this library and its programs with the
14  * OpenSSL library, and distribute linked combinations including the two.
15  *
16  * libs3 is distributed in the hope that it will be useful, but WITHOUT ANY
17  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License version 3
22  * along with libs3, in a file named COPYING.  If not, see
23  * <http://www.gnu.org/licenses/>.
24  *
25  ************************************************************************** **/
26
27 /**
28  * This is a 'driver' program that simply converts command-line input into
29  * calls to libs3 functions, and prints the results.
30  **/
31
32 #include <ctype.h>
33 #include <getopt.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <time.h>
41 #include <unistd.h>
42 #include "libs3.h"
43
44 // Some Windows stuff
45 #ifndef FOPEN_EXTRA_FLAGS
46 #define FOPEN_EXTRA_FLAGS ""
47 #endif
48
49 // Also needed for Windows, because somehow MinGW doesn't define this
50 extern int putenv(char *);
51
52
53 // Command-line options, saved as globals ------------------------------------
54
55 static int forceG = 0;
56 static int showResponsePropertiesG = 0;
57 static S3Protocol protocolG = S3ProtocolHTTPS;
58 static S3UriStyle uriStyleG = S3UriStylePath;
59 static int retriesG = 5;
60
61
62 // Environment variables, saved as globals ----------------------------------
63
64 static const char *accessKeyIdG = 0;
65 static const char *secretAccessKeyG = 0;
66
67
68 // Request results, saved as globals -----------------------------------------
69
70 static int statusG = 0;
71 static char errorDetailsG[4096] = { 0 };
72
73
74 // Other globals -------------------------------------------------------------
75
76 static char putenvBufG[256];
77
78
79 // Option prefixes -----------------------------------------------------------
80
81 #define LOCATION_PREFIX "location="
82 #define LOCATION_PREFIX_LEN (sizeof(LOCATION_PREFIX) - 1)
83 #define CANNED_ACL_PREFIX "cannedAcl="
84 #define CANNED_ACL_PREFIX_LEN (sizeof(CANNED_ACL_PREFIX) - 1)
85 #define PREFIX_PREFIX "prefix="
86 #define PREFIX_PREFIX_LEN (sizeof(PREFIX_PREFIX) - 1)
87 #define MARKER_PREFIX "marker="
88 #define MARKER_PREFIX_LEN (sizeof(MARKER_PREFIX) - 1)
89 #define DELIMITER_PREFIX "delimiter="
90 #define DELIMITER_PREFIX_LEN (sizeof(DELIMITER_PREFIX) - 1)
91 #define MAXKEYS_PREFIX "maxkeys="
92 #define MAXKEYS_PREFIX_LEN (sizeof(MAXKEYS_PREFIX) - 1)
93 #define FILENAME_PREFIX "filename="
94 #define FILENAME_PREFIX_LEN (sizeof(FILENAME_PREFIX) - 1)
95 #define CONTENT_LENGTH_PREFIX "contentLength="
96 #define CONTENT_LENGTH_PREFIX_LEN (sizeof(CONTENT_LENGTH_PREFIX) - 1)
97 #define CACHE_CONTROL_PREFIX "cacheControl="
98 #define CACHE_CONTROL_PREFIX_LEN (sizeof(CACHE_CONTROL_PREFIX) - 1)
99 #define CONTENT_TYPE_PREFIX "contentType="
100 #define CONTENT_TYPE_PREFIX_LEN (sizeof(CONTENT_TYPE_PREFIX) - 1)
101 #define MD5_PREFIX "md5="
102 #define MD5_PREFIX_LEN (sizeof(MD5_PREFIX) - 1)
103 #define CONTENT_DISPOSITION_FILENAME_PREFIX "contentDispositionFilename="
104 #define CONTENT_DISPOSITION_FILENAME_PREFIX_LEN \
105     (sizeof(CONTENT_DISPOSITION_FILENAME_PREFIX) - 1)
106 #define CONTENT_ENCODING_PREFIX "contentEncoding="
107 #define CONTENT_ENCODING_PREFIX_LEN (sizeof(CONTENT_ENCODING_PREFIX) - 1)
108 #define EXPIRES_PREFIX "expires="
109 #define EXPIRES_PREFIX_LEN (sizeof(EXPIRES_PREFIX) - 1)
110 #define X_AMZ_META_PREFIX "x-amz-meta-"
111 #define X_AMZ_META_PREFIX_LEN (sizeof(X_AMZ_META_PREFIX) - 1)
112 #define IF_MODIFIED_SINCE_PREFIX "ifModifiedSince="
113 #define IF_MODIFIED_SINCE_PREFIX_LEN (sizeof(IF_MODIFIED_SINCE_PREFIX) - 1)
114 #define IF_NOT_MODIFIED_SINCE_PREFIX "ifNotmodifiedSince="
115 #define IF_NOT_MODIFIED_SINCE_PREFIX_LEN \
116     (sizeof(IF_NOT_MODIFIED_SINCE_PREFIX) - 1)
117 #define IF_MATCH_PREFIX "ifMatch="
118 #define IF_MATCH_PREFIX_LEN (sizeof(IF_MATCH_PREFIX) - 1)
119 #define IF_NOT_MATCH_PREFIX "ifNotMatch="
120 #define IF_NOT_MATCH_PREFIX_LEN (sizeof(IF_NOT_MATCH_PREFIX) - 1)
121 #define START_BYTE_PREFIX "startByte="
122 #define START_BYTE_PREFIX_LEN (sizeof(START_BYTE_PREFIX) - 1)
123 #define BYTE_COUNT_PREFIX "byteCount="
124 #define BYTE_COUNT_PREFIX_LEN (sizeof(BYTE_COUNT_PREFIX) - 1)
125 #define ALL_DETAILS_PREFIX "allDetails="
126 #define ALL_DETAILS_PREFIX_LEN (sizeof(ALL_DETAILS_PREFIX) - 1)
127 #define NO_STATUS_PREFIX "noStatus="
128 #define NO_STATUS_PREFIX_LEN (sizeof(NO_STATUS_PREFIX) - 1)
129 #define RESOURCE_PREFIX "resource="
130 #define RESOURCE_PREFIX_LEN (sizeof(RESOURCE_PREFIX) - 1)
131 #define TARGET_BUCKET_PREFIX "targetBucket="
132 #define TARGET_BUCKET_PREFIX_LEN (sizeof(TARGET_BUCKET_PREFIX) - 1)
133 #define TARGET_PREFIX_PREFIX "targetPrefix="
134 #define TARGET_PREFIX_PREFIX_LEN (sizeof(TARGET_PREFIX_PREFIX) - 1)
135
136
137 // util ----------------------------------------------------------------------
138
139 static void S3_init()
140 {
141     S3Status status;
142     if ((status = S3_initialize("s3", S3_INIT_ALL))
143         != S3StatusOK) {
144         fprintf(stderr, "Failed to initialize libs3: %s\n", 
145                 S3_get_status_name(status));
146         exit(-1);
147     }
148 }
149
150
151 static void printError()
152 {
153     if (statusG < S3StatusErrorAccessDenied) {
154         fprintf(stderr, "\nERROR: %s\n", S3_get_status_name(statusG));
155     }
156     else {
157         fprintf(stderr, "\nERROR: %s\n", S3_get_status_name(statusG));
158         fprintf(stderr, "%s\n", errorDetailsG);
159     }
160 }
161
162
163 static void usageExit(FILE *out)
164 {
165     fprintf(out,
166 "\n Options:\n"
167 "\n"
168 "   Command Line:\n"
169 "\n"
170 "   -f/--force           : force operation despite warnings\n"
171 "   -h/--vhost-style     : use virtual-host-style URIs (default is "
172                           "path-style)\n"
173 "   -u/--unencrypted     : unencrypted (use HTTP instead of HTTPS)\n"
174 "   -s/--show-properties : show response properties on stdout\n"
175 "   -r/--retries         : retry retryable failures this number of times\n"
176 "                          (default is 5)\n"
177 "\n"
178 "   Environment:\n"
179 "\n"
180 "   S3_ACCESS_KEY_ID     : S3 access key ID (required)\n"
181 "   S3_SECRET_ACCESS_KEY : S3 secret access key (required)\n"
182 "\n" 
183 " Commands (with <required parameters> and [optional parameters]) :\n"
184 "\n"
185 "   (NOTE: all command parameters take a value and are specified using the\n"
186 "          pattern parameter=value)\n"
187 "\n"
188 "   help                 : Prints this help text\n"
189 "\n"
190 "   list                 : Lists owned buckets\n"
191 "     [allDetails]       : Show full details\n"
192 "\n"
193 "   test                 : Tests a bucket for existence and accessibility\n"
194 "     <bucket>           : Bucket to test\n"
195 "\n"
196 "   create               : Create a new bucket\n"
197 "     <bucket>           : Bucket to create\n"
198 "     [cannedAcl]        : Canned ACL for the bucket (see Canned ACLs)\n"
199 "     [location]         : Location for bucket (for example, EU)\n"
200 "\n"
201 "   delete               : Delete a bucket or key\n"
202 "     <bucket>[/<key>]   : Bucket or bucket/key to delete\n"
203 "\n"
204 "   list                 : List bucket contents\n"
205 "     <bucket>           : Bucket to list\n"
206 "     [prefix]           : Prefix for results set\n"
207 "     [marker]           : Where in results set to start listing\n"
208 "     [delimiter]        : Delimiter for rolling up results set\n"
209 "     [maxkeys]          : Maximum number of keys to return in results set\n"
210 "     [allDetails]       : Show full details for each key\n"
211 "\n"
212 "   getacl               : Get the ACL of a bucket or key\n"
213 "     <bucket>[/<key>]   : Bucket or bucket/key to get the ACL of\n"
214 "     [filename]         : Output filename for ACL (default is stdout)\n"
215 "\n"
216 "   setacl               : Set the ACL of a bucket or key\n"
217 "     <bucket>[/<key>]   : Bucket or bucket/key to set the ACL of\n"
218 "     [filename]         : Input filename for ACL (default is stdin)\n"
219 "\n"
220 "   getlogging           : Get the logging status of a bucket\n"
221 "     <bucket>           : Bucket to get the logging status of\n"
222 "     [filename]         : Output filename for ACL (default is stdout)\n"
223 "\n"
224 "   setlogging           : Set the logging status of a bucket\n"
225 "     <bucket>           : Bucket to set the logging status of\n"
226 "     [targetBucket]     : Target bucket to log to; if not present, disables\n"
227 "                          logging\n"
228 "     [targetPrefix]     : Key prefix to use for logs\n"
229 "     [filename]         : Input filename for ACL (default is stdin)\n"
230 "\n"
231 "   put                  : Puts an object\n"
232 "     <bucket>/<key>     : Bucket/key to put object to\n"
233 "     [filename]         : Filename to read source data from "
234                           "(default is stdin)\n"
235 "     [contentLength]    : How many bytes of source data to put (required if\n"
236 "                          source file is stdin)\n"
237 "     [cacheControl]     : Cache-Control HTTP header string to associate with\n"
238 "                          object\n"
239 "     [contentType]      : Content-Type HTTP header string to associate with\n"
240 "                          object\n"
241 "     [md5]              : MD5 for validating source data\n"
242 "     [contentDispositionFilename] : Content-Disposition filename string to\n"
243 "                          associate with object\n"
244 "     [contentEncoding]  : Content-Encoding HTTP header string to associate\n"
245 "                          with object\n"
246 "     [expires]          : Expiration date to associate with object\n"
247 "     [cannedAcl]        : Canned ACL for the object (see Canned ACLs)\n"
248 "     [x-amz-meta-...]]  : Metadata headers to associate with the object\n"
249 "\n"
250 "   copy                 : Copies an object; if any options are set, the "
251                           "entire\n"
252 "                          metadata of the object is replaced\n"
253 "     <sourcebucket>/<sourcekey> : Source bucket/key\n"
254 "     <destbucket>/<destkey> : Destination bucket/key\n"
255 "     [cacheControl]     : Cache-Control HTTP header string to associate with\n"
256 "                          object\n"
257 "     [contentType]      : Content-Type HTTP header string to associate with\n"
258 "                          object\n"
259 "     [contentDispositionFilename] : Content-Disposition filename string to\n"
260 "                          associate with object\n"
261 "     [contentEncoding]  : Content-Encoding HTTP header string to associate\n"
262 "                          with object\n"
263 "     [expires]          : Expiration date to associate with object\n"
264 "     [cannedAcl]        : Canned ACL for the object (see Canned ACLs)\n"
265 "     [x-amz-meta-...]]  : Metadata headers to associate with the object\n"
266 "\n"
267 "   get                  : Gets an object\n"
268 "     <buckey>/<key>     : Bucket/key of object to get\n"
269 "     [filename]         : Filename to write object data to (required if -s\n"
270 "                          command line parameter was used)\n"
271 "     [ifModifiedSince]  : Only return the object if it has been modified "
272                           "since\n"
273 "                          this date\n"
274 "     [ifNotmodifiedSince] : Only return the object if it has not been "
275                           "modified\n"
276 "                          since this date\n"
277 "     [ifMatch]          : Only return the object if its ETag header matches\n"
278 "                          this string\n"
279 "     [ifNotMatch]       : Only return the object if its ETag header does "
280                           "not\n"
281 "                          match this string\n"
282 "     [startByte]        : First byte of byte range to return\n"
283 "     [byteCount]        : Number of bytes of byte range to return\n"
284 "\n"
285 "   head                 : Gets only the headers of an object, implies -s\n"
286 "     <bucket>/<key>     : Bucket/key of object to get headers of\n"
287 "\n"
288 "   gqs                  : Generates an authenticated query string\n"
289 "     <bucket>[/<key>]   : Bucket or bucket/key to generate query string for\n"
290 "     [expires]          : Expiration date for query string\n"
291 "     [resource]         : Sub-resource of key for query string, without a\n"
292 "                          leading '?', for example, \"torrent\"\n"
293 "\n"
294 " Canned ACLs:\n"
295 "\n"
296 "  The following canned ACLs are supported:\n"
297 "    private (default), public-read, public-read-write, authenticated-read\n"
298 "\n"
299 " ACL Format:\n"
300 "\n"
301 "  For the getacl and setacl commands, the format of the ACL list is:\n"
302 "  1) An initial line giving the owner id in this format:\n"
303 "       OwnerID <Owner ID> <Owner Display Name>\n"
304 "  2) Optional header lines, giving column headers, starting with the\n"
305 "     word \"Type\", or with some number of dashes\n"
306 "  3) Grant lines, of the form:\n"
307 "       <Grant Type> (whitespace) <Grantee> (whitespace) <Permission>\n"
308 "     where Grant Type is one of: Email, UserID, or Group, and\n"
309 "     Grantee is the identification of the grantee based on this type,\n"
310 "     and Permission is one of: READ, WRITE, READ_ACP, or FULL_CONTROL.\n"
311 "\n"
312 "  Note that the easiest way to modify an ACL is to first get it, saving it\n"
313 "  into a file, then modifying the file, and then setting the modified file\n"
314 "  back as the new ACL for the bucket/object.\n"
315 "\n"
316 " Date Format:\n"
317 "\n"
318 "  The format for dates used in parameters is as ISO 8601 dates, i.e.\n"
319 "  YYYY-MM-DDTHH:MM:SS[.s...][T/+-dd:dd].  Examples:\n"
320 "      2008-07-29T20:36:14.0023T\n"
321 "      2008-07-29T20:36:14.0023+06:00\n"
322 "      2008-07-29T20:36:14.0023-10:00\n"
323 "\n");
324
325     exit(-1);
326 }
327
328
329 static uint64_t convertInt(const char *str, const char *paramName)
330 {
331     uint64_t ret = 0;
332
333     while (*str) {
334         if (!isdigit(*str)) {
335             fprintf(stderr, "\nERROR: Nondigit in %s parameter: %c\n", 
336                     paramName, *str);
337             usageExit(stderr);
338         }
339         ret *= 10;
340         ret += (*str++ - '0');
341     }
342
343     return ret;
344 }
345
346
347 typedef struct growbuffer
348 {
349     // The total number of bytes, and the start byte
350     int size;
351     // The start byte
352     int start;
353     // The blocks
354     char data[64 * 1024];
355     struct growbuffer *prev, *next;
356 } growbuffer;
357
358
359 // returns nonzero on success, zero on out of memory
360 static int growbuffer_append(growbuffer **gb, const char *data, int dataLen)
361 {
362     while (dataLen) {
363         growbuffer *buf = *gb ? (*gb)->prev : 0;
364         if (!buf || (buf->size == sizeof(buf->data))) {
365             buf = (growbuffer *) malloc(sizeof(growbuffer));
366             if (!buf) {
367                 return 0;
368             }
369             buf->size = 0;
370             buf->start = 0;
371             if (*gb) {
372                 buf->prev = (*gb)->prev;
373                 buf->next = *gb;
374                 (*gb)->prev->next = buf;
375                 (*gb)->prev = buf;
376             }
377             else {
378                 buf->prev = buf->next = buf;
379                 *gb = buf;
380             }
381         }
382
383         int toCopy = (sizeof(buf->data) - buf->size);
384         if (toCopy > dataLen) {
385             toCopy = dataLen;
386         }
387
388         memcpy(&(buf->data[buf->size]), data, toCopy);
389         
390         buf->size += toCopy, data += toCopy, dataLen -= toCopy;
391     }
392
393     return 1;
394 }
395
396
397 static void growbuffer_read(growbuffer **gb, int amt, int *amtReturn, 
398                             char *buffer)
399 {
400     *amtReturn = 0;
401
402     growbuffer *buf = *gb;
403
404     if (!buf) {
405         return;
406     }
407
408     *amtReturn = (buf->size > amt) ? amt : buf->size;
409
410     memcpy(buffer, &(buf->data[buf->start]), *amtReturn);
411     
412     buf->start += *amtReturn, buf->size -= *amtReturn;
413
414     if (buf->size == 0) {
415         if (buf->next == buf) {
416             *gb = 0;
417         }
418         else {
419             *gb = buf->next;
420         }
421         free(buf);
422     }
423 }
424
425
426 static void growbuffer_destroy(growbuffer *gb)
427 {
428     growbuffer *start = gb;
429
430     while (gb) {
431         growbuffer *next = gb->next;
432         free(gb);
433         gb = (next == start) ? 0 : next;
434     }
435 }
436
437
438 // Convenience utility for making the code look nicer.  Tests a string
439 // against a format; only the characters specified in the format are
440 // checked (i.e. if the string is longer than the format, the string still
441 // checks out ok).  Format characters are:
442 // d - is a digit
443 // anything else - is that character
444 // Returns nonzero the string checks out, zero if it does not.
445 static int checkString(const char *str, const char *format)
446 {
447     while (*format) {
448         if (*format == 'd') {
449             if (!isdigit(*str)) {
450                 return 0;
451             }
452         }
453         else if (*str != *format) {
454             return 0;
455         }
456         str++, format++;
457     }
458
459     return 1;
460 }
461
462
463 static int64_t parseIso8601Time(const char *str)
464 {
465     // Check to make sure that it has a valid format
466     if (!checkString(str, "dddd-dd-ddTdd:dd:dd")) {
467         return -1;
468     }
469
470 #define nextnum() (((*str - '0') * 10) + (*(str + 1) - '0'))
471
472     // Convert it
473     struct tm stm;
474     memset(&stm, 0, sizeof(stm));
475
476     stm.tm_year = (nextnum() - 19) * 100;
477     str += 2;
478     stm.tm_year += nextnum();
479     str += 3;
480
481     stm.tm_mon = nextnum() - 1;
482     str += 3;
483
484     stm.tm_mday = nextnum();
485     str += 3;
486
487     stm.tm_hour = nextnum();
488     str += 3;
489
490     stm.tm_min = nextnum();
491     str += 3;
492
493     stm.tm_sec = nextnum();
494     str += 2;
495
496     stm.tm_isdst = -1;
497
498     // This is hokey but it's the recommended way ...
499     char *tz = getenv("TZ");
500     snprintf(putenvBufG, sizeof(putenvBufG), "TZ=UTC");
501     putenv(putenvBufG);
502
503     int64_t ret = mktime(&stm);
504
505     snprintf(putenvBufG, sizeof(putenvBufG), "TZ=%s", tz ? tz : "");
506     putenv(putenvBufG);
507
508     // Skip the millis
509
510     if (*str == '.') {
511         str++;
512         while (isdigit(*str)) {
513             str++;
514         }
515     }
516     
517     if (checkString(str, "-dd:dd") || checkString(str, "+dd:dd")) {
518         int sign = (*str++ == '-') ? -1 : 1;
519         int hours = nextnum();
520         str += 3;
521         int minutes = nextnum();
522         ret += (-sign * (((hours * 60) + minutes) * 60));
523     }
524     // Else it should be Z to be a conformant time string, but we just assume
525     // that it is rather than enforcing that
526
527     return ret;
528 }
529
530
531 // Simple ACL format:  Lines of this format:
532 // Type - ignored
533 // Starting with a dash - ignored
534 // Email email_address permission
535 // UserID user_id (display_name) permission
536 // Group Authenticated AWS Users permission
537 // Group All Users  permission
538 // permission is one of READ, WRITE, READ_ACP, WRITE_ACP, FULL_CONTROL
539 static int convert_simple_acl(char *aclXml, char *ownerId,
540                               char *ownerDisplayName,
541                               int *aclGrantCountReturn,
542                               S3AclGrant *aclGrants)
543 {
544     *aclGrantCountReturn = 0;
545     *ownerId = 0;
546     *ownerDisplayName = 0;
547
548 #define SKIP_SPACE(require_more)                \
549     do {                                        \
550         while (isspace(*aclXml)) {              \
551             aclXml++;                           \
552         }                                       \
553         if (require_more && !*aclXml) {         \
554             return 0;                           \
555         }                                       \
556     } while (0)
557     
558 #define COPY_STRING_MAXLEN(field, maxlen)               \
559     do {                                                \
560         SKIP_SPACE(1);                                  \
561         int len = 0;                                    \
562         while ((len < maxlen) && !isspace(*aclXml)) {   \
563             field[len++] = *aclXml++;                   \
564         }                                               \
565         field[len] = 0;                                 \
566     } while (0)
567
568 #define COPY_STRING(field)                              \
569     COPY_STRING_MAXLEN(field, (int) (sizeof(field) - 1))
570
571     while (1) {
572         SKIP_SPACE(0);
573
574         if (!*aclXml) {
575             break;
576         }
577         
578         // Skip Type lines and dash lines
579         if (!strncmp(aclXml, "Type", sizeof("Type") - 1) ||
580             (*aclXml == '-')) {
581             while (*aclXml && ((*aclXml != '\n') && (*aclXml != '\r'))) {
582                 aclXml++;
583             }
584             continue;
585         }
586         
587         if (!strncmp(aclXml, "OwnerID", sizeof("OwnerID") - 1)) {
588             aclXml += sizeof("OwnerID") - 1;
589             COPY_STRING_MAXLEN(ownerId, S3_MAX_GRANTEE_USER_ID_SIZE);
590             SKIP_SPACE(1);
591             COPY_STRING_MAXLEN(ownerDisplayName,
592                                S3_MAX_GRANTEE_DISPLAY_NAME_SIZE);
593             continue;
594         }
595
596         if (*aclGrantCountReturn == S3_MAX_ACL_GRANT_COUNT) {
597             return 0;
598         }
599
600         S3AclGrant *grant = &(aclGrants[(*aclGrantCountReturn)++]);
601
602         if (!strncmp(aclXml, "Email", sizeof("Email") - 1)) {
603             grant->granteeType = S3GranteeTypeAmazonCustomerByEmail;
604             aclXml += sizeof("Email") - 1;
605             COPY_STRING(grant->grantee.amazonCustomerByEmail.emailAddress);
606         }
607         else if (!strncmp(aclXml, "UserID", sizeof("UserID") - 1)) {
608             grant->granteeType = S3GranteeTypeCanonicalUser;
609             aclXml += sizeof("UserID") - 1;
610             COPY_STRING(grant->grantee.canonicalUser.id);
611             SKIP_SPACE(1);
612             // Now do display name
613             COPY_STRING(grant->grantee.canonicalUser.displayName);
614         }
615         else if (!strncmp(aclXml, "Group", sizeof("Group") - 1)) {
616             aclXml += sizeof("Group") - 1;
617             SKIP_SPACE(1);
618             if (!strncmp(aclXml, "Authenticated AWS Users",
619                          sizeof("Authenticated AWS Users") - 1)) {
620                 grant->granteeType = S3GranteeTypeAllAwsUsers;
621                 aclXml += (sizeof("Authenticated AWS Users") - 1);
622             }
623             else if (!strncmp(aclXml, "All Users", sizeof("All Users") - 1)) {
624                 grant->granteeType = S3GranteeTypeAllUsers;
625                 aclXml += (sizeof("All Users") - 1);
626             }
627             else if (!strncmp(aclXml, "Log Delivery", 
628                               sizeof("Log Delivery") - 1)) {
629                 grant->granteeType = S3GranteeTypeLogDelivery;
630                 aclXml += (sizeof("Log Delivery") - 1);
631             }
632             else {
633                 return 0;
634             }
635         }
636         else {
637             return 0;
638         }
639
640         SKIP_SPACE(1);
641         
642         if (!strncmp(aclXml, "READ_ACP", sizeof("READ_ACP") - 1)) {
643             grant->permission = S3PermissionReadACP;
644             aclXml += (sizeof("READ_ACP") - 1);
645         }
646         else if (!strncmp(aclXml, "READ", sizeof("READ") - 1)) {
647             grant->permission = S3PermissionRead;
648             aclXml += (sizeof("READ") - 1);
649         }
650         else if (!strncmp(aclXml, "WRITE_ACP", sizeof("WRITE_ACP") - 1)) {
651             grant->permission = S3PermissionWriteACP;
652             aclXml += (sizeof("WRITE_ACP") - 1);
653         }
654         else if (!strncmp(aclXml, "WRITE", sizeof("WRITE") - 1)) {
655             grant->permission = S3PermissionWrite;
656             aclXml += (sizeof("WRITE") - 1);
657         }
658         else if (!strncmp(aclXml, "FULL_CONTROL", 
659                           sizeof("FULL_CONTROL") - 1)) {
660             grant->permission = S3PermissionFullControl;
661             aclXml += (sizeof("FULL_CONTROL") - 1);
662         }
663     }
664
665     return 1;
666 }
667
668
669 static int should_retry()
670 {
671     if (retriesG--) {
672         // Sleep before next retry; start out with a 1 second sleep
673         static int retrySleepInterval = 1;
674         sleep(retrySleepInterval);
675         // Next sleep 1 second longer
676         retrySleepInterval++;
677         return 1;
678     }
679
680     return 0;
681 }
682
683
684 static struct option longOptionsG[] =
685 {
686     { "force",                no_argument,        0,  'f' },
687     { "vhost-style",          no_argument,        0,  'h' },
688     { "unencrypted",          no_argument,        0,  'u' },
689     { "show-properties",      no_argument,        0,  's' },
690     { "retries",              required_argument,  0,  'r' },
691     { 0,                      0,                  0,   0  }
692 };
693
694
695 // response properties callback ----------------------------------------------
696
697 // This callback does the same thing for every request type: prints out the
698 // properties if the user has requested them to be so
699 static S3Status responsePropertiesCallback
700     (const S3ResponseProperties *properties, void *callbackData)
701 {
702     (void) callbackData;
703
704     if (!showResponsePropertiesG) {
705         return S3StatusOK;
706     }
707
708 #define print_nonnull(name, field)                                 \
709     do {                                                           \
710         if (properties-> field) {                                  \
711             printf("%s: %s\n", name, properties-> field);          \
712         }                                                          \
713     } while (0)
714     
715     print_nonnull("Content-Type", contentType);
716     print_nonnull("Request-Id", requestId);
717     print_nonnull("Request-Id-2", requestId2);
718     if (properties->contentLength > 0) {
719         printf("Content-Length: %lld\n", 
720                (unsigned long long) properties->contentLength);
721     }
722     print_nonnull("Server", server);
723     print_nonnull("ETag", eTag);
724     if (properties->lastModified > 0) {
725         char timebuf[256];
726         time_t t = (time_t) properties->lastModified;
727         // gmtime is not thread-safe but we don't care here.
728         strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&t));
729         printf("Last-Modified: %s\n", timebuf);
730     }
731     int i;
732     for (i = 0; i < properties->metaDataCount; i++) {
733         printf("x-amz-meta-%s: %s\n", properties->metaData[i].name,
734                properties->metaData[i].value);
735     }
736
737     return S3StatusOK;
738 }
739
740
741 // response complete callback ------------------------------------------------
742
743 // This callback does the same thing for every request type: saves the status
744 // and error stuff in global variables
745 static void responseCompleteCallback(S3Status status,
746                                      const S3ErrorDetails *error, 
747                                      void *callbackData)
748 {
749     (void) callbackData;
750
751     statusG = status;
752     // Compose the error details message now, although we might not use it.
753     // Can't just save a pointer to [error] since it's not guaranteed to last
754     // beyond this callback
755     int len = 0;
756     if (error && error->message) {
757         len += snprintf(&(errorDetailsG[len]), sizeof(errorDetailsG) - len,
758                         "  Message: %s\n", error->message);
759     }
760     if (error && error->resource) {
761         len += snprintf(&(errorDetailsG[len]), sizeof(errorDetailsG) - len,
762                         "  Resource: %s\n", error->resource);
763     }
764     if (error && error->furtherDetails) {
765         len += snprintf(&(errorDetailsG[len]), sizeof(errorDetailsG) - len,
766                         "  Further Details: %s\n", error->furtherDetails);
767     }
768     if (error && error->extraDetailsCount) {
769         len += snprintf(&(errorDetailsG[len]), sizeof(errorDetailsG) - len,
770                         "%s", "  Extra Details:\n");
771         int i;
772         for (i = 0; i < error->extraDetailsCount; i++) {
773             len += snprintf(&(errorDetailsG[len]), 
774                             sizeof(errorDetailsG) - len, "    %s: %s\n", 
775                             error->extraDetails[i].name,
776                             error->extraDetails[i].value);
777         }
778     }
779 }
780
781
782 // list service --------------------------------------------------------------
783
784 typedef struct list_service_data
785 {
786     int headerPrinted;
787     int allDetails;
788 } list_service_data;
789
790
791 static void printListServiceHeader(int allDetails)
792 {
793     printf("%-56s  %-20s", "                         Bucket",
794            "      Created");
795     if (allDetails) {
796         printf("  %-64s  %-12s", 
797                "                            Owner ID",
798                "Display Name");
799     }
800     printf("\n");
801     printf("--------------------------------------------------------  "
802            "--------------------");
803     if (allDetails) {
804         printf("  -------------------------------------------------"
805                "---------------  ------------");
806     }
807     printf("\n");
808 }
809
810
811 static S3Status listServiceCallback(const char *ownerId, 
812                                     const char *ownerDisplayName,
813                                     const char *bucketName,
814                                     int64_t creationDate, void *callbackData)
815 {
816     list_service_data *data = (list_service_data *) callbackData;
817
818     if (!data->headerPrinted) {
819         data->headerPrinted = 1;
820         printListServiceHeader(data->allDetails);
821     }
822
823     char timebuf[256];
824     if (creationDate >= 0) {
825         time_t t = (time_t) creationDate;
826         strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&t));
827     }
828     else {
829         timebuf[0] = 0;
830     }
831
832     printf("%-56s  %-20s", bucketName, timebuf);
833     if (data->allDetails) {
834         printf("  %-64s  %-12s", ownerId ? ownerId : "", 
835                ownerDisplayName ? ownerDisplayName : "");
836     }
837     printf("\n");
838
839     return S3StatusOK;
840 }
841
842
843 static void list_service(int allDetails)
844 {
845     list_service_data data;
846
847     data.headerPrinted = 0;
848     data.allDetails = allDetails;
849
850     S3_init();
851
852     S3ListServiceHandler listServiceHandler =
853     {
854         { &responsePropertiesCallback, &responseCompleteCallback },
855         &listServiceCallback
856     };
857
858     do {
859         S3_list_service(protocolG, accessKeyIdG, secretAccessKeyG, 0, 
860                         &listServiceHandler, &data);
861     } while (S3_status_is_retryable(statusG) && should_retry());
862
863     if (statusG == S3StatusOK) {
864         if (!data.headerPrinted) {
865             printListServiceHeader(allDetails);
866         }
867     }
868     else {
869         printError();
870     }
871
872     S3_deinitialize();
873 }
874
875
876 // test bucket ---------------------------------------------------------------
877
878 static void test_bucket(int argc, char **argv, int optindex)
879 {
880     // test bucket
881     if (optindex == argc) {
882         fprintf(stderr, "\nERROR: Missing parameter: bucket\n");
883         usageExit(stderr);
884     }
885
886     const char *bucketName = argv[optindex++];
887
888     if (optindex != argc) {
889         fprintf(stderr, "\nERROR: Extraneous parameter: %s\n", argv[optindex]);
890         usageExit(stderr);
891     }
892
893     S3_init();
894
895     S3ResponseHandler responseHandler =
896     {
897         &responsePropertiesCallback, &responseCompleteCallback
898     };
899
900     char locationConstraint[64];
901     do {
902         S3_test_bucket(protocolG, uriStyleG, accessKeyIdG, secretAccessKeyG,
903                        bucketName, sizeof(locationConstraint),
904                        locationConstraint, 0, &responseHandler, 0);
905     } while (S3_status_is_retryable(statusG) && should_retry());
906
907     const char *result;
908
909     switch (statusG) {
910     case S3StatusOK:
911         // bucket exists
912         result = locationConstraint[0] ? locationConstraint : "USA";
913         break;
914     case S3StatusErrorNoSuchBucket:
915         result = "Does Not Exist";
916         break;
917     case S3StatusErrorAccessDenied:
918         result = "Access Denied";
919         break;
920     default:
921         result = 0;
922         break;
923     }
924
925     if (result) {
926         printf("%-56s  %-20s\n", "                         Bucket",
927                "       Status");
928         printf("--------------------------------------------------------  "
929                "--------------------\n");
930         printf("%-56s  %-20s\n", bucketName, result);
931     }
932     else {
933         printError();
934     }
935
936     S3_deinitialize();
937 }
938
939
940 // create bucket -------------------------------------------------------------
941
942 static void create_bucket(int argc, char **argv, int optindex)
943 {
944     if (optindex == argc) {
945         fprintf(stderr, "\nERROR: Missing parameter: bucket\n");
946         usageExit(stderr);
947     }
948
949     const char *bucketName = argv[optindex++];
950
951     if (!forceG && (S3_validate_bucket_name
952                     (bucketName, S3UriStyleVirtualHost) != S3StatusOK)) {
953         fprintf(stderr, "\nWARNING: Bucket name is not valid for "
954                 "virtual-host style URI access.\n");
955         fprintf(stderr, "Bucket not created.  Use -f option to force the "
956                 "bucket to be created despite\n");
957         fprintf(stderr, "this warning.\n\n");
958         exit(-1);
959     }
960
961     const char *locationConstraint = 0;
962     S3CannedAcl cannedAcl = S3CannedAclPrivate;
963     while (optindex < argc) {
964         char *param = argv[optindex++];
965         if (!strncmp(param, LOCATION_PREFIX, LOCATION_PREFIX_LEN)) {
966             locationConstraint = &(param[LOCATION_PREFIX_LEN]);
967         }
968         else if (!strncmp(param, CANNED_ACL_PREFIX, CANNED_ACL_PREFIX_LEN)) {
969             char *val = &(param[CANNED_ACL_PREFIX_LEN]);
970             if (!strcmp(val, "private")) {
971                 cannedAcl = S3CannedAclPrivate;
972             }
973             else if (!strcmp(val, "public-read")) {
974                 cannedAcl = S3CannedAclPublicRead;
975             }
976             else if (!strcmp(val, "public-read-write")) {
977                 cannedAcl = S3CannedAclPublicReadWrite;
978             }
979             else if (!strcmp(val, "authenticated-read")) {
980                 cannedAcl = S3CannedAclAuthenticatedRead;
981             }
982             else {
983                 fprintf(stderr, "\nERROR: Unknown canned ACL: %s\n", val);
984                 usageExit(stderr);
985             }
986         }
987         else {
988             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
989             usageExit(stderr);
990         }
991     }
992
993     S3_init();
994
995     S3ResponseHandler responseHandler =
996     {
997         &responsePropertiesCallback, &responseCompleteCallback
998     };
999
1000     do {
1001         S3_create_bucket(protocolG, accessKeyIdG, secretAccessKeyG,
1002                          bucketName, cannedAcl, locationConstraint, 0,
1003                          &responseHandler, 0);
1004     } while (S3_status_is_retryable(statusG) && should_retry());
1005
1006     if (statusG == S3StatusOK) {
1007         printf("Bucket successfully created.\n");
1008     }
1009     else {
1010         printError();
1011     }
1012     
1013     S3_deinitialize();
1014 }
1015
1016
1017 // delete bucket -------------------------------------------------------------
1018
1019 static void delete_bucket(int argc, char **argv, int optindex)
1020 {
1021     if (optindex == argc) {
1022         fprintf(stderr, "\nERROR: Missing parameter: bucket\n");
1023         usageExit(stderr);
1024     }
1025
1026     const char *bucketName = argv[optindex++];
1027
1028     if (optindex != argc) {
1029         fprintf(stderr, "\nERROR: Extraneous parameter: %s\n", argv[optindex]);
1030         usageExit(stderr);
1031     }
1032
1033     S3_init();
1034
1035     S3ResponseHandler responseHandler =
1036     {
1037         &responsePropertiesCallback, &responseCompleteCallback
1038     };
1039
1040     do {
1041         S3_delete_bucket(protocolG, uriStyleG, accessKeyIdG, secretAccessKeyG,
1042                          bucketName, 0, &responseHandler, 0);
1043     } while (S3_status_is_retryable(statusG) && should_retry());
1044
1045     if (statusG != S3StatusOK) {
1046         printError();
1047     }
1048
1049     S3_deinitialize();
1050 }
1051
1052
1053 // list bucket ---------------------------------------------------------------
1054
1055 typedef struct list_bucket_callback_data
1056 {
1057     int isTruncated;
1058     char nextMarker[1024];
1059     int keyCount;
1060     int allDetails;
1061 } list_bucket_callback_data;
1062
1063
1064 static void printListBucketHeader(int allDetails)
1065 {
1066     printf("%-50s  %-20s  %-5s", 
1067            "                       Key", 
1068            "   Last Modified", "Size");
1069     if (allDetails) {
1070         printf("  %-34s  %-64s  %-12s", 
1071                "               ETag", 
1072                "                            Owner ID",
1073                "Display Name");
1074     }
1075     printf("\n");
1076     printf("--------------------------------------------------  "
1077            "--------------------  -----");
1078     if (allDetails) {
1079         printf("  ----------------------------------  "
1080                "-------------------------------------------------"
1081                "---------------  ------------");
1082     }
1083     printf("\n");
1084 }
1085
1086
1087 static S3Status listBucketCallback(int isTruncated, const char *nextMarker,
1088                                    int contentsCount, 
1089                                    const S3ListBucketContent *contents,
1090                                    int commonPrefixesCount,
1091                                    const char **commonPrefixes,
1092                                    void *callbackData)
1093 {
1094     list_bucket_callback_data *data = 
1095         (list_bucket_callback_data *) callbackData;
1096
1097     data->isTruncated = isTruncated;
1098     // This is tricky.  S3 doesn't return the NextMarker if there is no
1099     // delimiter.  Why, I don't know, since it's still useful for paging
1100     // through results.  We want NextMarker to be the last content in the
1101     // list, so set it to that if necessary.
1102     if ((!nextMarker || !nextMarker[0]) && contentsCount) {
1103         nextMarker = contents[contentsCount - 1].key;
1104     }
1105     if (nextMarker) {
1106         snprintf(data->nextMarker, sizeof(data->nextMarker), "%s", 
1107                  nextMarker);
1108     }
1109     else {
1110         data->nextMarker[0] = 0;
1111     }
1112     
1113     if (contentsCount && !data->keyCount) {
1114         printListBucketHeader(data->allDetails);
1115     }
1116
1117     int i;
1118     for (i = 0; i < contentsCount; i++) {
1119         const S3ListBucketContent *content = &(contents[i]);
1120         char timebuf[256];
1121         if (0) {
1122             time_t t = (time_t) content->lastModified;
1123             strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ",
1124                      gmtime(&t));
1125             printf("\nKey: %s\n", content->key);
1126             printf("Last Modified: %s\n", timebuf);
1127             printf("ETag: %s\n", content->eTag);
1128             printf("Size: %llu\n", (unsigned long long) content->size);
1129             if (content->ownerId) {
1130                 printf("Owner ID: %s\n", content->ownerId);
1131             }
1132             if (content->ownerDisplayName) {
1133                 printf("Owner Display Name: %s\n", content->ownerDisplayName);
1134             }
1135         }
1136         else {
1137             time_t t = (time_t) content->lastModified;
1138             strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ", 
1139                      gmtime(&t));
1140             char sizebuf[16];
1141             if (content->size < 100000) {
1142                 sprintf(sizebuf, "%5llu", (unsigned long long) content->size);
1143             }
1144             else if (content->size < (1024 * 1024)) {
1145                 sprintf(sizebuf, "%4lluK", 
1146                         ((unsigned long long) content->size) / 1024ULL);
1147             }
1148             else if (content->size < (10 * 1024 * 1024)) {
1149                 float f = content->size;
1150                 f /= (1024 * 1024);
1151                 sprintf(sizebuf, "%1.2fM", f);
1152             }
1153             else if (content->size < (1024 * 1024 * 1024)) {
1154                 sprintf(sizebuf, "%4lluM", 
1155                         ((unsigned long long) content->size) / 
1156                         (1024ULL * 1024ULL));
1157             }
1158             else {
1159                 float f = (content->size / 1024);
1160                 f /= (1024 * 1024);
1161                 sprintf(sizebuf, "%1.2fG", f);
1162             }
1163             printf("%-50s  %s  %s", content->key, timebuf, sizebuf);
1164             if (data->allDetails) {
1165                 printf("  %-34s  %-64s  %-12s",
1166                        content->eTag, 
1167                        content->ownerId ? content->ownerId : "",
1168                        content->ownerDisplayName ? 
1169                        content->ownerDisplayName : "");
1170             }
1171             printf("\n");
1172         }
1173     }
1174
1175     data->keyCount += contentsCount;
1176
1177     for (i = 0; i < commonPrefixesCount; i++) {
1178         printf("\nCommon Prefix: %s\n", commonPrefixes[i]);
1179     }
1180
1181     return S3StatusOK;
1182 }
1183
1184
1185 static void list_bucket(const char *bucketName, const char *prefix,
1186                         const char *marker, const char *delimiter,
1187                         int maxkeys, int allDetails)
1188 {
1189     S3_init();
1190     
1191     S3BucketContext bucketContext =
1192     {
1193         bucketName,
1194         protocolG,
1195         uriStyleG,
1196         accessKeyIdG,
1197         secretAccessKeyG
1198     };
1199
1200     S3ListBucketHandler listBucketHandler =
1201     {
1202         { &responsePropertiesCallback, &responseCompleteCallback },
1203         &listBucketCallback
1204     };
1205
1206     list_bucket_callback_data data;
1207
1208     snprintf(data.nextMarker, sizeof(data.nextMarker), "%s", marker);
1209     data.keyCount = 0;
1210     data.allDetails = allDetails;
1211
1212     do {
1213         data.isTruncated = 0;
1214         do {
1215             S3_list_bucket(&bucketContext, prefix, data.nextMarker,
1216                            delimiter, maxkeys, 0, &listBucketHandler, &data);
1217         } while (S3_status_is_retryable(statusG) && should_retry());
1218         if (statusG != S3StatusOK) {
1219             break;
1220         }
1221         marker = data.nextMarker;
1222     } while (data.isTruncated && (!maxkeys || (data.keyCount < maxkeys)));
1223
1224     if (statusG == S3StatusOK) {
1225         if (!data.keyCount) {
1226             printListBucketHeader(allDetails);
1227         }
1228     }
1229     else {
1230         printError();
1231     }
1232
1233     S3_deinitialize();
1234 }
1235
1236
1237 static void list(int argc, char **argv, int optindex)
1238 {
1239     if (optindex == argc) {
1240         list_service(0);
1241         return;
1242     }
1243
1244     const char *bucketName = 0;
1245
1246     const char *prefix = 0, *marker = 0, *delimiter = 0;
1247     int maxkeys = 0, allDetails = 0;
1248     while (optindex < argc) {
1249         char *param = argv[optindex++];
1250         if (!strncmp(param, PREFIX_PREFIX, PREFIX_PREFIX_LEN)) {
1251             prefix = &(param[PREFIX_PREFIX_LEN]);
1252         }
1253         else if (!strncmp(param, MARKER_PREFIX, MARKER_PREFIX_LEN)) {
1254             marker = &(param[MARKER_PREFIX_LEN]);
1255         }
1256         else if (!strncmp(param, DELIMITER_PREFIX, DELIMITER_PREFIX_LEN)) {
1257             delimiter = &(param[DELIMITER_PREFIX_LEN]);
1258         }
1259         else if (!strncmp(param, MAXKEYS_PREFIX, MAXKEYS_PREFIX_LEN)) {
1260             maxkeys = convertInt(&(param[MAXKEYS_PREFIX_LEN]), "maxkeys");
1261         }
1262         else if (!strncmp(param, ALL_DETAILS_PREFIX,
1263                           ALL_DETAILS_PREFIX_LEN)) {
1264             const char *ad = &(param[ALL_DETAILS_PREFIX_LEN]);
1265             if (!strcmp(ad, "true") || !strcmp(ad, "TRUE") || 
1266                 !strcmp(ad, "yes") || !strcmp(ad, "YES") ||
1267                 !strcmp(ad, "1")) {
1268                 allDetails = 1;
1269             }
1270         }
1271         else if (!bucketName) {
1272             bucketName = param;
1273         }
1274         else {
1275             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
1276             usageExit(stderr);
1277         }
1278     }
1279
1280     if (bucketName) {
1281         list_bucket(bucketName, prefix, marker, delimiter, maxkeys, 
1282                     allDetails);
1283     }
1284     else {
1285         list_service(allDetails);
1286     }
1287 }
1288
1289     
1290
1291 // delete object -------------------------------------------------------------
1292
1293 static void delete_object(int argc, char **argv, int optindex)
1294 {
1295     (void) argc;
1296
1297     // Split bucket/key
1298     char *slash = argv[optindex];
1299
1300     // We know there is a slash in there, put_object is only called if so
1301     while (*slash && (*slash != '/')) {
1302         slash++;
1303     }
1304     *slash++ = 0;
1305
1306     const char *bucketName = argv[optindex++];
1307     const char *key = slash;
1308
1309     S3_init();
1310     
1311     S3BucketContext bucketContext =
1312     {
1313         bucketName,
1314         protocolG,
1315         uriStyleG,
1316         accessKeyIdG,
1317         secretAccessKeyG
1318     };
1319
1320     S3ResponseHandler responseHandler =
1321     { 
1322         0,
1323         &responseCompleteCallback
1324     };
1325
1326     do {
1327         S3_delete_object(&bucketContext, key, 0, &responseHandler, 0);
1328     } while (S3_status_is_retryable(statusG) && should_retry());
1329
1330     if ((statusG != S3StatusOK) &&
1331         (statusG != S3StatusErrorPreconditionFailed)) {
1332         printError();
1333     }
1334
1335     S3_deinitialize();
1336 }
1337
1338
1339 // put object ----------------------------------------------------------------
1340
1341 typedef struct put_object_callback_data
1342 {
1343     FILE *infile;
1344     growbuffer *gb;
1345     uint64_t contentLength, originalContentLength;
1346     int noStatus;
1347 } put_object_callback_data;
1348
1349
1350 static int putObjectDataCallback(int bufferSize, char *buffer,
1351                                  void *callbackData)
1352 {
1353     put_object_callback_data *data = 
1354         (put_object_callback_data *) callbackData;
1355     
1356     int ret = 0;
1357
1358     if (data->contentLength) {
1359         int toRead = ((data->contentLength > (unsigned) bufferSize) ?
1360                       (unsigned) bufferSize : data->contentLength);
1361         if (data->gb) {
1362             growbuffer_read(&(data->gb), toRead, &ret, buffer);
1363         }
1364         else if (data->infile) {
1365             ret = fread(buffer, 1, toRead, data->infile);
1366         }
1367     }
1368
1369     data->contentLength -= ret;
1370
1371     if (data->contentLength && !data->noStatus) {
1372         // Avoid a weird bug in MingW, which won't print the second integer
1373         // value properly when it's in the same call, so print separately
1374         printf("%llu bytes remaining ", 
1375                (unsigned long long) data->contentLength);
1376         printf("(%d%% complete) ...\n",
1377                (int) (((data->originalContentLength - 
1378                         data->contentLength) * 100) /
1379                       data->originalContentLength));
1380     }
1381
1382     return ret;
1383 }
1384
1385
1386 static void put_object(int argc, char **argv, int optindex)
1387 {
1388     if (optindex == argc) {
1389         fprintf(stderr, "\nERROR: Missing parameter: bucket/key\n");
1390         usageExit(stderr);
1391     }
1392
1393     // Split bucket/key
1394     char *slash = argv[optindex];
1395     while (*slash && (*slash != '/')) {
1396         slash++;
1397     }
1398     if (!*slash || !*(slash + 1)) {
1399         fprintf(stderr, "\nERROR: Invalid bucket/key name: %s\n",
1400                 argv[optindex]);
1401         usageExit(stderr);
1402     }
1403     *slash++ = 0;
1404
1405     const char *bucketName = argv[optindex++];
1406     const char *key = slash;
1407
1408     const char *filename = 0;
1409     uint64_t contentLength = 0;
1410     const char *cacheControl = 0, *contentType = 0, *md5 = 0;
1411     const char *contentDispositionFilename = 0, *contentEncoding = 0;
1412     int64_t expires = -1;
1413     S3CannedAcl cannedAcl = S3CannedAclPrivate;
1414     int metaPropertiesCount = 0;
1415     S3NameValue metaProperties[S3_MAX_METADATA_COUNT];
1416     int noStatus = 0;
1417
1418     while (optindex < argc) {
1419         char *param = argv[optindex++];
1420         if (!strncmp(param, FILENAME_PREFIX, FILENAME_PREFIX_LEN)) {
1421             filename = &(param[FILENAME_PREFIX_LEN]);
1422         }
1423         else if (!strncmp(param, CONTENT_LENGTH_PREFIX, 
1424                           CONTENT_LENGTH_PREFIX_LEN)) {
1425             contentLength = convertInt(&(param[CONTENT_LENGTH_PREFIX_LEN]),
1426                                        "contentLength");
1427             if (contentLength > (5LL * 1024 * 1024 * 1024)) {
1428                 fprintf(stderr, "\nERROR: contentLength must be no greater "
1429                         "than 5 GB\n");
1430                 usageExit(stderr);
1431             }
1432         }
1433         else if (!strncmp(param, CACHE_CONTROL_PREFIX, 
1434                           CACHE_CONTROL_PREFIX_LEN)) {
1435             cacheControl = &(param[CACHE_CONTROL_PREFIX_LEN]);
1436         }
1437         else if (!strncmp(param, CONTENT_TYPE_PREFIX, 
1438                           CONTENT_TYPE_PREFIX_LEN)) {
1439             contentType = &(param[CONTENT_TYPE_PREFIX_LEN]);
1440         }
1441         else if (!strncmp(param, MD5_PREFIX, MD5_PREFIX_LEN)) {
1442             md5 = &(param[MD5_PREFIX_LEN]);
1443         }
1444         else if (!strncmp(param, CONTENT_DISPOSITION_FILENAME_PREFIX, 
1445                           CONTENT_DISPOSITION_FILENAME_PREFIX_LEN)) {
1446             contentDispositionFilename = 
1447                 &(param[CONTENT_DISPOSITION_FILENAME_PREFIX_LEN]);
1448         }
1449         else if (!strncmp(param, CONTENT_ENCODING_PREFIX, 
1450                           CONTENT_ENCODING_PREFIX_LEN)) {
1451             contentEncoding = &(param[CONTENT_ENCODING_PREFIX_LEN]);
1452         }
1453         else if (!strncmp(param, EXPIRES_PREFIX, EXPIRES_PREFIX_LEN)) {
1454             expires = parseIso8601Time(&(param[EXPIRES_PREFIX_LEN]));
1455             if (expires < 0) {
1456                 fprintf(stderr, "\nERROR: Invalid expires time "
1457                         "value; ISO 8601 time format required\n");
1458                 usageExit(stderr);
1459             }
1460         }
1461         else if (!strncmp(param, X_AMZ_META_PREFIX, X_AMZ_META_PREFIX_LEN)) {
1462             if (metaPropertiesCount == S3_MAX_METADATA_COUNT) {
1463                 fprintf(stderr, "\nERROR: Too many x-amz-meta- properties, "
1464                         "limit %lu: %s\n", 
1465                         (unsigned long) S3_MAX_METADATA_COUNT, param);
1466                 usageExit(stderr);
1467             }
1468             char *name = &(param[X_AMZ_META_PREFIX_LEN]);
1469             char *value = name;
1470             while (*value && (*value != '=')) {
1471                 value++;
1472             }
1473             if (!*value || !*(value + 1)) {
1474                 fprintf(stderr, "\nERROR: Invalid parameter: %s\n", param);
1475                 usageExit(stderr);
1476             }
1477             *value++ = 0;
1478             metaProperties[metaPropertiesCount].name = name;
1479             metaProperties[metaPropertiesCount++].value = value;
1480         }
1481         else if (!strncmp(param, CANNED_ACL_PREFIX, CANNED_ACL_PREFIX_LEN)) {
1482             char *val = &(param[CANNED_ACL_PREFIX_LEN]);
1483             if (!strcmp(val, "private")) {
1484                 cannedAcl = S3CannedAclPrivate;
1485             }
1486             else if (!strcmp(val, "public-read")) {
1487                 cannedAcl = S3CannedAclPublicRead;
1488             }
1489             else if (!strcmp(val, "public-read-write")) {
1490                 cannedAcl = S3CannedAclPublicReadWrite;
1491             }
1492             else if (!strcmp(val, "authenticated-read")) {
1493                 cannedAcl = S3CannedAclAuthenticatedRead;
1494             }
1495             else {
1496                 fprintf(stderr, "\nERROR: Unknown canned ACL: %s\n", val);
1497                 usageExit(stderr);
1498             }
1499         }
1500         else if (!strncmp(param, NO_STATUS_PREFIX, NO_STATUS_PREFIX_LEN)) {
1501             const char *ns = &(param[NO_STATUS_PREFIX_LEN]);
1502             if (!strcmp(ns, "true") || !strcmp(ns, "TRUE") || 
1503                 !strcmp(ns, "yes") || !strcmp(ns, "YES") ||
1504                 !strcmp(ns, "1")) {
1505                 noStatus = 1;
1506             }
1507         }
1508         else {
1509             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
1510             usageExit(stderr);
1511         }
1512     }
1513
1514     put_object_callback_data data;
1515
1516     data.infile = 0;
1517     data.gb = 0;
1518     data.noStatus = noStatus;
1519
1520     if (filename) {
1521         if (!contentLength) {
1522             struct stat statbuf;
1523             // Stat the file to get its length
1524             if (stat(filename, &statbuf) == -1) {
1525                 fprintf(stderr, "\nERROR: Failed to stat file %s: ",
1526                         filename);
1527                 perror(0);
1528                 exit(-1);
1529             }
1530             contentLength = statbuf.st_size;
1531         }
1532         // Open the file
1533         if (!(data.infile = fopen(filename, "r" FOPEN_EXTRA_FLAGS))) {
1534             fprintf(stderr, "\nERROR: Failed to open input file %s: ",
1535                     filename);
1536             perror(0);
1537             exit(-1);
1538         }
1539     }
1540     else {
1541         // Read from stdin.  If contentLength is not provided, we have
1542         // to read it all in to get contentLength.
1543         if (!contentLength) {
1544             // Read all if stdin to get the data
1545             char buffer[64 * 1024];
1546             while (1) {
1547                 int amtRead = fread(buffer, 1, sizeof(buffer), stdin);
1548                 if (amtRead == 0) {
1549                     break;
1550                 }
1551                 if (!growbuffer_append(&(data.gb), buffer, amtRead)) {
1552                     fprintf(stderr, "\nERROR: Out of memory while reading "
1553                             "stdin\n");
1554                     exit(-1);
1555                 }
1556                 contentLength += amtRead;
1557                 if (amtRead < (int) sizeof(buffer)) {
1558                     break;
1559                 }
1560             }
1561         }
1562         else {
1563             data.infile = stdin;
1564         }
1565     }
1566
1567     data.contentLength = data.originalContentLength = contentLength;
1568
1569     S3_init();
1570     
1571     S3BucketContext bucketContext =
1572     {
1573         bucketName,
1574         protocolG,
1575         uriStyleG,
1576         accessKeyIdG,
1577         secretAccessKeyG
1578     };
1579
1580     S3PutProperties putProperties =
1581     {
1582         contentType,
1583         md5,
1584         cacheControl,
1585         contentDispositionFilename,
1586         contentEncoding,
1587         expires,
1588         cannedAcl,
1589         metaPropertiesCount,
1590         metaProperties
1591     };
1592
1593     S3PutObjectHandler putObjectHandler =
1594     {
1595         { &responsePropertiesCallback, &responseCompleteCallback },
1596         &putObjectDataCallback
1597     };
1598
1599     do {
1600         S3_put_object(&bucketContext, key, contentLength, &putProperties, 0,
1601                       &putObjectHandler, &data);
1602     } while (S3_status_is_retryable(statusG) && should_retry());
1603
1604     if (data.infile) {
1605         fclose(data.infile);
1606     }
1607     else if (data.gb) {
1608         growbuffer_destroy(data.gb);
1609     }
1610
1611     if (statusG != S3StatusOK) {
1612         printError();
1613     }
1614     else if (data.contentLength) {
1615         fprintf(stderr, "\nERROR: Failed to read remaining %llu bytes from "
1616                 "input\n", (unsigned long long) data.contentLength);
1617     }
1618
1619     S3_deinitialize();
1620 }
1621
1622
1623 // copy object ---------------------------------------------------------------
1624
1625 static void copy_object(int argc, char **argv, int optindex)
1626 {
1627     if (optindex == argc) {
1628         fprintf(stderr, "\nERROR: Missing parameter: source bucket/key\n");
1629         usageExit(stderr);
1630     }
1631
1632     // Split bucket/key
1633     char *slash = argv[optindex];
1634     while (*slash && (*slash != '/')) {
1635         slash++;
1636     }
1637     if (!*slash || !*(slash + 1)) {
1638         fprintf(stderr, "\nERROR: Invalid source bucket/key name: %s\n",
1639                 argv[optindex]);
1640         usageExit(stderr);
1641     }
1642     *slash++ = 0;
1643
1644     const char *sourceBucketName = argv[optindex++];
1645     const char *sourceKey = slash;
1646
1647     if (optindex == argc) {
1648         fprintf(stderr, "\nERROR: Missing parameter: "
1649                 "destination bucket/key\n");
1650         usageExit(stderr);
1651     }
1652
1653     // Split bucket/key
1654     slash = argv[optindex];
1655     while (*slash && (*slash != '/')) {
1656         slash++;
1657     }
1658     if (!*slash || !*(slash + 1)) {
1659         fprintf(stderr, "\nERROR: Invalid destination bucket/key name: %s\n",
1660                 argv[optindex]);
1661         usageExit(stderr);
1662     }
1663     *slash++ = 0;
1664
1665     const char *destinationBucketName = argv[optindex++];
1666     const char *destinationKey = slash;
1667
1668     const char *cacheControl = 0, *contentType = 0;
1669     const char *contentDispositionFilename = 0, *contentEncoding = 0;
1670     int64_t expires = -1;
1671     S3CannedAcl cannedAcl = S3CannedAclPrivate;
1672     int metaPropertiesCount = 0;
1673     S3NameValue metaProperties[S3_MAX_METADATA_COUNT];
1674     int anyPropertiesSet = 0;
1675
1676     while (optindex < argc) {
1677         char *param = argv[optindex++];
1678         if (!strncmp(param, CACHE_CONTROL_PREFIX, 
1679                           CACHE_CONTROL_PREFIX_LEN)) {
1680             cacheControl = &(param[CACHE_CONTROL_PREFIX_LEN]);
1681             anyPropertiesSet = 1;
1682         }
1683         else if (!strncmp(param, CONTENT_TYPE_PREFIX, 
1684                           CONTENT_TYPE_PREFIX_LEN)) {
1685             contentType = &(param[CONTENT_TYPE_PREFIX_LEN]);
1686             anyPropertiesSet = 1;
1687         }
1688         else if (!strncmp(param, CONTENT_DISPOSITION_FILENAME_PREFIX, 
1689                           CONTENT_DISPOSITION_FILENAME_PREFIX_LEN)) {
1690             contentDispositionFilename = 
1691                 &(param[CONTENT_DISPOSITION_FILENAME_PREFIX_LEN]);
1692             anyPropertiesSet = 1;
1693         }
1694         else if (!strncmp(param, CONTENT_ENCODING_PREFIX, 
1695                           CONTENT_ENCODING_PREFIX_LEN)) {
1696             contentEncoding = &(param[CONTENT_ENCODING_PREFIX_LEN]);
1697             anyPropertiesSet = 1;
1698         }
1699         else if (!strncmp(param, EXPIRES_PREFIX, EXPIRES_PREFIX_LEN)) {
1700             expires = parseIso8601Time(&(param[EXPIRES_PREFIX_LEN]));
1701             if (expires < 0) {
1702                 fprintf(stderr, "\nERROR: Invalid expires time "
1703                         "value; ISO 8601 time format required\n");
1704                 usageExit(stderr);
1705             }
1706             anyPropertiesSet = 1;
1707         }
1708         else if (!strncmp(param, X_AMZ_META_PREFIX, X_AMZ_META_PREFIX_LEN)) {
1709             if (metaPropertiesCount == S3_MAX_METADATA_COUNT) {
1710                 fprintf(stderr, "\nERROR: Too many x-amz-meta- properties, "
1711                         "limit %lu: %s\n", 
1712                         (unsigned long) S3_MAX_METADATA_COUNT, param);
1713                 usageExit(stderr);
1714             }
1715             char *name = &(param[X_AMZ_META_PREFIX_LEN]);
1716             char *value = name;
1717             while (*value && (*value != '=')) {
1718                 value++;
1719             }
1720             if (!*value || !*(value + 1)) {
1721                 fprintf(stderr, "\nERROR: Invalid parameter: %s\n", param);
1722                 usageExit(stderr);
1723             }
1724             *value++ = 0;
1725             metaProperties[metaPropertiesCount].name = name;
1726             metaProperties[metaPropertiesCount++].value = value;
1727             anyPropertiesSet = 1;
1728         }
1729         else if (!strncmp(param, CANNED_ACL_PREFIX, CANNED_ACL_PREFIX_LEN)) {
1730             char *val = &(param[CANNED_ACL_PREFIX_LEN]);
1731             if (!strcmp(val, "private")) {
1732                 cannedAcl = S3CannedAclPrivate;
1733             }
1734             else if (!strcmp(val, "public-read")) {
1735                 cannedAcl = S3CannedAclPublicRead;
1736             }
1737             else if (!strcmp(val, "public-read-write")) {
1738                 cannedAcl = S3CannedAclPublicReadWrite;
1739             }
1740             else if (!strcmp(val, "authenticated-read")) {
1741                 cannedAcl = S3CannedAclAuthenticatedRead;
1742             }
1743             else {
1744                 fprintf(stderr, "\nERROR: Unknown canned ACL: %s\n", val);
1745                 usageExit(stderr);
1746             }
1747             anyPropertiesSet = 1;
1748         }
1749         else {
1750             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
1751             usageExit(stderr);
1752         }
1753     }
1754
1755     S3_init();
1756     
1757     S3BucketContext bucketContext =
1758     {
1759         sourceBucketName,
1760         protocolG,
1761         uriStyleG,
1762         accessKeyIdG,
1763         secretAccessKeyG
1764     };
1765
1766     S3PutProperties putProperties =
1767     {
1768         contentType,
1769         0,
1770         cacheControl,
1771         contentDispositionFilename,
1772         contentEncoding,
1773         expires,
1774         cannedAcl,
1775         metaPropertiesCount,
1776         metaProperties
1777     };
1778
1779     S3ResponseHandler responseHandler =
1780     { 
1781         &responsePropertiesCallback,
1782         &responseCompleteCallback
1783     };
1784
1785     int64_t lastModified;
1786     char eTag[256];
1787
1788     do {
1789         S3_copy_object(&bucketContext, sourceKey, destinationBucketName,
1790                        destinationKey, anyPropertiesSet ? &putProperties : 0,
1791                        &lastModified, sizeof(eTag), eTag, 0,
1792                        &responseHandler, 0);
1793     } while (S3_status_is_retryable(statusG) && should_retry());
1794
1795     if (statusG == S3StatusOK) {
1796         if (lastModified >= 0) {
1797             char timebuf[256];
1798             time_t t = (time_t) lastModified;
1799             strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%SZ",
1800                      gmtime(&t));
1801             printf("Last-Modified: %s\n", timebuf);
1802         }
1803         if (eTag[0]) {
1804             printf("ETag: %s\n", eTag);
1805         }
1806     }
1807     else {
1808         printError();
1809     }
1810
1811     S3_deinitialize();
1812 }
1813
1814
1815 // get object ----------------------------------------------------------------
1816
1817 static S3Status getObjectDataCallback(int bufferSize, const char *buffer,
1818                                       void *callbackData)
1819 {
1820     FILE *outfile = (FILE *) callbackData;
1821
1822     size_t wrote = fwrite(buffer, 1, bufferSize, outfile);
1823     
1824     return ((wrote < (size_t) bufferSize) ? 
1825             S3StatusAbortedByCallback : S3StatusOK);
1826 }
1827
1828
1829 static void get_object(int argc, char **argv, int optindex)
1830 {
1831     if (optindex == argc) {
1832         fprintf(stderr, "\nERROR: Missing parameter: bucket/key\n");
1833         usageExit(stderr);
1834     }
1835
1836     // Split bucket/key
1837     char *slash = argv[optindex];
1838     while (*slash && (*slash != '/')) {
1839         slash++;
1840     }
1841     if (!*slash || !*(slash + 1)) {
1842         fprintf(stderr, "\nERROR: Invalid bucket/key name: %s\n",
1843                 argv[optindex]);
1844         usageExit(stderr);
1845     }
1846     *slash++ = 0;
1847
1848     const char *bucketName = argv[optindex++];
1849     const char *key = slash;
1850
1851     const char *filename = 0;
1852     int64_t ifModifiedSince = -1, ifNotModifiedSince = -1;
1853     const char *ifMatch = 0, *ifNotMatch = 0;
1854     uint64_t startByte = 0, byteCount = 0;
1855
1856     while (optindex < argc) {
1857         char *param = argv[optindex++];
1858         if (!strncmp(param, FILENAME_PREFIX, FILENAME_PREFIX_LEN)) {
1859             filename = &(param[FILENAME_PREFIX_LEN]);
1860         }
1861         else if (!strncmp(param, IF_MODIFIED_SINCE_PREFIX, 
1862                      IF_MODIFIED_SINCE_PREFIX_LEN)) {
1863             // Parse ifModifiedSince
1864             ifModifiedSince = parseIso8601Time
1865                 (&(param[IF_MODIFIED_SINCE_PREFIX_LEN]));
1866             if (ifModifiedSince < 0) {
1867                 fprintf(stderr, "\nERROR: Invalid ifModifiedSince time "
1868                         "value; ISO 8601 time format required\n");
1869                 usageExit(stderr);
1870             }
1871         }
1872         else if (!strncmp(param, IF_NOT_MODIFIED_SINCE_PREFIX, 
1873                           IF_NOT_MODIFIED_SINCE_PREFIX_LEN)) {
1874             // Parse ifModifiedSince
1875             ifNotModifiedSince = parseIso8601Time
1876                 (&(param[IF_NOT_MODIFIED_SINCE_PREFIX_LEN]));
1877             if (ifNotModifiedSince < 0) {
1878                 fprintf(stderr, "\nERROR: Invalid ifNotModifiedSince time "
1879                         "value; ISO 8601 time format required\n");
1880                 usageExit(stderr);
1881             }
1882         }
1883         else if (!strncmp(param, IF_MATCH_PREFIX, IF_MATCH_PREFIX_LEN)) {
1884             ifMatch = &(param[IF_MATCH_PREFIX_LEN]);
1885         }
1886         else if (!strncmp(param, IF_NOT_MATCH_PREFIX,
1887                           IF_NOT_MATCH_PREFIX_LEN)) {
1888             ifNotMatch = &(param[IF_NOT_MATCH_PREFIX_LEN]);
1889         }
1890         else if (!strncmp(param, START_BYTE_PREFIX, START_BYTE_PREFIX_LEN)) {
1891             startByte = convertInt
1892                 (&(param[START_BYTE_PREFIX_LEN]), "startByte");
1893         }
1894         else if (!strncmp(param, BYTE_COUNT_PREFIX, BYTE_COUNT_PREFIX_LEN)) {
1895             byteCount = convertInt
1896                 (&(param[BYTE_COUNT_PREFIX_LEN]), "byteCount");
1897         }
1898         else {
1899             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
1900             usageExit(stderr);
1901         }
1902     }
1903
1904     FILE *outfile = 0;
1905
1906     if (filename) {
1907         // Stat the file, and if it doesn't exist, open it in w mode
1908         struct stat buf;
1909         if (stat(filename, &buf) == -1) {
1910             outfile = fopen(filename, "w" FOPEN_EXTRA_FLAGS);
1911         }
1912         else {
1913             // Open in r+ so that we don't truncate the file, just in case
1914             // there is an error and we write no bytes, we leave the file
1915             // unmodified
1916             outfile = fopen(filename, "r+" FOPEN_EXTRA_FLAGS);
1917         }
1918         
1919         if (!outfile) {
1920             fprintf(stderr, "\nERROR: Failed to open output file %s: ",
1921                     filename);
1922             perror(0);
1923             exit(-1);
1924         }
1925     }
1926     else if (showResponsePropertiesG) {
1927         fprintf(stderr, "\nERROR: get -s requires a filename parameter\n");
1928         usageExit(stderr);
1929     }
1930     else {
1931         outfile = stdout;
1932     }
1933
1934     S3_init();
1935     
1936     S3BucketContext bucketContext =
1937     {
1938         bucketName,
1939         protocolG,
1940         uriStyleG,
1941         accessKeyIdG,
1942         secretAccessKeyG
1943     };
1944
1945     S3GetConditions getConditions =
1946     {
1947         ifModifiedSince,
1948         ifNotModifiedSince,
1949         ifMatch,
1950         ifNotMatch
1951     };
1952
1953     S3GetObjectHandler getObjectHandler =
1954     {
1955         { &responsePropertiesCallback, &responseCompleteCallback },
1956         &getObjectDataCallback
1957     };
1958
1959     do {
1960         S3_get_object(&bucketContext, key, &getConditions, startByte,
1961                       byteCount, 0, &getObjectHandler, outfile);
1962     } while (S3_status_is_retryable(statusG) && should_retry());
1963
1964     if (statusG != S3StatusOK) {
1965         printError();
1966     }
1967
1968     fclose(outfile);
1969
1970     S3_deinitialize();
1971 }
1972
1973
1974 // head object ---------------------------------------------------------------
1975
1976 static void head_object(int argc, char **argv, int optindex)
1977 {
1978     if (optindex == argc) {
1979         fprintf(stderr, "\nERROR: Missing parameter: bucket/key\n");
1980         usageExit(stderr);
1981     }
1982     
1983     // Head implies showing response properties
1984     showResponsePropertiesG = 1;
1985
1986     // Split bucket/key
1987     char *slash = argv[optindex];
1988
1989     while (*slash && (*slash != '/')) {
1990         slash++;
1991     }
1992     if (!*slash || !*(slash + 1)) {
1993         fprintf(stderr, "\nERROR: Invalid bucket/key name: %s\n",
1994                 argv[optindex]);
1995         usageExit(stderr);
1996     }
1997     *slash++ = 0;
1998
1999     const char *bucketName = argv[optindex++];
2000     const char *key = slash;
2001
2002     if (optindex != argc) {
2003         fprintf(stderr, "\nERROR: Extraneous parameter: %s\n", argv[optindex]);
2004         usageExit(stderr);
2005     }
2006
2007     S3_init();
2008     
2009     S3BucketContext bucketContext =
2010     {
2011         bucketName,
2012         protocolG,
2013         uriStyleG,
2014         accessKeyIdG,
2015         secretAccessKeyG
2016     };
2017
2018     S3ResponseHandler responseHandler =
2019     { 
2020         &responsePropertiesCallback,
2021         &responseCompleteCallback
2022     };
2023
2024     do {
2025         S3_head_object(&bucketContext, key, 0, &responseHandler, 0);
2026     } while (S3_status_is_retryable(statusG) && should_retry());
2027
2028     if ((statusG != S3StatusOK) &&
2029         (statusG != S3StatusErrorPreconditionFailed)) {
2030         printError();
2031     }
2032
2033     S3_deinitialize();
2034 }
2035
2036
2037 // generate query string ------------------------------------------------------
2038
2039 static void generate_query_string(int argc, char **argv, int optindex)
2040 {
2041     if (optindex == argc) {
2042         fprintf(stderr, "\nERROR: Missing parameter: bucket[/key]\n");
2043         usageExit(stderr);
2044     }
2045
2046     const char *bucketName = argv[optindex];
2047     const char *key = 0;
2048
2049     // Split bucket/key
2050     char *slash = argv[optindex++];
2051     while (*slash && (*slash != '/')) {
2052         slash++;
2053     }
2054     if (*slash) {
2055         *slash++ = 0;
2056         key = slash;
2057     }
2058     else {
2059         key = 0;
2060     }
2061
2062     int64_t expires = -1;
2063
2064     const char *resource = 0;
2065
2066     while (optindex < argc) {
2067         char *param = argv[optindex++];
2068         if (!strncmp(param, EXPIRES_PREFIX, EXPIRES_PREFIX_LEN)) {
2069             expires = parseIso8601Time(&(param[EXPIRES_PREFIX_LEN]));
2070             if (expires < 0) {
2071                 fprintf(stderr, "\nERROR: Invalid expires time "
2072                         "value; ISO 8601 time format required\n");
2073                 usageExit(stderr);
2074             }
2075         }
2076         else if (!strncmp(param, RESOURCE_PREFIX, RESOURCE_PREFIX_LEN)) {
2077             resource = &(param[RESOURCE_PREFIX_LEN]);
2078         }
2079         else {
2080             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
2081             usageExit(stderr);
2082         }
2083     }
2084
2085     S3_init();
2086     
2087     S3BucketContext bucketContext =
2088     {
2089         bucketName,
2090         protocolG,
2091         uriStyleG,
2092         accessKeyIdG,
2093         secretAccessKeyG
2094     };
2095
2096     char buffer[S3_MAX_AUTHENTICATED_QUERY_STRING_SIZE];
2097
2098     S3Status status = S3_generate_authenticated_query_string
2099         (buffer, &bucketContext, key, expires, resource);
2100     
2101     if (status != S3StatusOK) {
2102         printf("Failed to generate authenticated query string: %s\n",
2103                S3_get_status_name(status));
2104     }
2105     else {
2106         printf("%s\n", buffer);
2107     }
2108
2109     S3_deinitialize();
2110 }
2111
2112
2113 // get acl -------------------------------------------------------------------
2114
2115 void get_acl(int argc, char **argv, int optindex)
2116 {
2117     if (optindex == argc) {
2118         fprintf(stderr, "\nERROR: Missing parameter: bucket[/key]\n");
2119         usageExit(stderr);
2120     }
2121
2122     const char *bucketName = argv[optindex];
2123     const char *key = 0;
2124
2125     // Split bucket/key
2126     char *slash = argv[optindex++];
2127     while (*slash && (*slash != '/')) {
2128         slash++;
2129     }
2130     if (*slash) {
2131         *slash++ = 0;
2132         key = slash;
2133     }
2134     else {
2135         key = 0;
2136     }
2137
2138     const char *filename = 0;
2139
2140     while (optindex < argc) {
2141         char *param = argv[optindex++];
2142         if (!strncmp(param, FILENAME_PREFIX, FILENAME_PREFIX_LEN)) {
2143             filename = &(param[FILENAME_PREFIX_LEN]);
2144         }
2145         else {
2146             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
2147             usageExit(stderr);
2148         }
2149     }
2150
2151     FILE *outfile = 0;
2152
2153     if (filename) {
2154         // Stat the file, and if it doesn't exist, open it in w mode
2155         struct stat buf;
2156         if (stat(filename, &buf) == -1) {
2157             outfile = fopen(filename, "w" FOPEN_EXTRA_FLAGS);
2158         }
2159         else {
2160             // Open in r+ so that we don't truncate the file, just in case
2161             // there is an error and we write no bytes, we leave the file
2162             // unmodified
2163             outfile = fopen(filename, "r+" FOPEN_EXTRA_FLAGS);
2164         }
2165         
2166         if (!outfile) {
2167             fprintf(stderr, "\nERROR: Failed to open output file %s: ",
2168                     filename);
2169             perror(0);
2170             exit(-1);
2171         }
2172     }
2173     else if (showResponsePropertiesG) {
2174         fprintf(stderr, "\nERROR: getacl -s requires a filename parameter\n");
2175         usageExit(stderr);
2176     }
2177     else {
2178         outfile = stdout;
2179     }
2180
2181     int aclGrantCount;
2182     S3AclGrant aclGrants[S3_MAX_ACL_GRANT_COUNT];
2183     char ownerId[S3_MAX_GRANTEE_USER_ID_SIZE];
2184     char ownerDisplayName[S3_MAX_GRANTEE_DISPLAY_NAME_SIZE];
2185
2186     S3_init();
2187
2188     S3BucketContext bucketContext =
2189     {
2190         bucketName,
2191         protocolG,
2192         uriStyleG,
2193         accessKeyIdG,
2194         secretAccessKeyG
2195     };
2196
2197     S3ResponseHandler responseHandler =
2198     {
2199         &responsePropertiesCallback,
2200         &responseCompleteCallback
2201     };
2202
2203     do {
2204         S3_get_acl(&bucketContext, key, ownerId, ownerDisplayName, 
2205                    &aclGrantCount, aclGrants, 0, &responseHandler, 0);
2206     } while (S3_status_is_retryable(statusG) && should_retry());
2207
2208     if (statusG == S3StatusOK) {
2209         fprintf(outfile, "OwnerID %s %s\n", ownerId, ownerDisplayName);
2210         fprintf(outfile, "%-6s  %-90s  %-12s\n", " Type", 
2211                 "                                   User Identifier",
2212                 " Permission");
2213         fprintf(outfile, "------  "
2214                 "------------------------------------------------------------"
2215                 "------------------------------  ------------\n");
2216         int i;
2217         for (i = 0; i < aclGrantCount; i++) {
2218             S3AclGrant *grant = &(aclGrants[i]);
2219             const char *type;
2220             char composedId[S3_MAX_GRANTEE_USER_ID_SIZE + 
2221                             S3_MAX_GRANTEE_DISPLAY_NAME_SIZE + 16];
2222             const char *id;
2223
2224             switch (grant->granteeType) {
2225             case S3GranteeTypeAmazonCustomerByEmail:
2226                 type = "Email";
2227                 id = grant->grantee.amazonCustomerByEmail.emailAddress;
2228                 break;
2229             case S3GranteeTypeCanonicalUser:
2230                 type = "UserID";
2231                 snprintf(composedId, sizeof(composedId),
2232                          "%s (%s)", grant->grantee.canonicalUser.id,
2233                          grant->grantee.canonicalUser.displayName);
2234                 id = composedId;
2235                 break;
2236             case S3GranteeTypeAllAwsUsers:
2237                 type = "Group";
2238                 id = "Authenticated AWS Users";
2239                 break;
2240             case S3GranteeTypeAllUsers:
2241                 type = "Group";
2242                 id = "All Users";
2243                 break;
2244             default:
2245                 type = "Group";
2246                 id = "Log Delivery";
2247                 break;
2248             }
2249             const char *perm;
2250             switch (grant->permission) {
2251             case S3PermissionRead:
2252                 perm = "READ";
2253                 break;
2254             case S3PermissionWrite:
2255                 perm = "WRITE";
2256                 break;
2257             case S3PermissionReadACP:
2258                 perm = "READ_ACP";
2259                 break;
2260             case S3PermissionWriteACP:
2261                 perm = "WRITE_ACP";
2262                 break;
2263             default:
2264                 perm = "FULL_CONTROL";
2265                 break;
2266             }
2267             fprintf(outfile, "%-6s  %-90s  %-12s\n", type, id, perm);
2268         }
2269     }
2270     else {
2271         printError();
2272     }
2273
2274     fclose(outfile);
2275
2276     S3_deinitialize();
2277 }
2278
2279
2280 // set acl -------------------------------------------------------------------
2281
2282 void set_acl(int argc, char **argv, int optindex)
2283 {
2284     if (optindex == argc) {
2285         fprintf(stderr, "\nERROR: Missing parameter: bucket[/key]\n");
2286         usageExit(stderr);
2287     }
2288
2289     const char *bucketName = argv[optindex];
2290     const char *key = 0;
2291
2292     // Split bucket/key
2293     char *slash = argv[optindex++];
2294     while (*slash && (*slash != '/')) {
2295         slash++;
2296     }
2297     if (*slash) {
2298         *slash++ = 0;
2299         key = slash;
2300     }
2301     else {
2302         key = 0;
2303     }
2304
2305     const char *filename = 0;
2306
2307     while (optindex < argc) {
2308         char *param = argv[optindex++];
2309         if (!strncmp(param, FILENAME_PREFIX, FILENAME_PREFIX_LEN)) {
2310             filename = &(param[FILENAME_PREFIX_LEN]);
2311         }
2312         else {
2313             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
2314             usageExit(stderr);
2315         }
2316     }
2317
2318     FILE *infile;
2319
2320     if (filename) {
2321         if (!(infile = fopen(filename, "r" FOPEN_EXTRA_FLAGS))) {
2322             fprintf(stderr, "\nERROR: Failed to open input file %s: ",
2323                     filename);
2324             perror(0);
2325             exit(-1);
2326         }
2327     }
2328     else {
2329         infile = stdin;
2330     }
2331
2332     // Read in the complete ACL
2333     char aclBuf[65536];
2334     aclBuf[fread(aclBuf, 1, sizeof(aclBuf), infile)] = 0;
2335     char ownerId[S3_MAX_GRANTEE_USER_ID_SIZE];
2336     char ownerDisplayName[S3_MAX_GRANTEE_DISPLAY_NAME_SIZE];
2337     
2338     // Parse it
2339     int aclGrantCount;
2340     S3AclGrant aclGrants[S3_MAX_ACL_GRANT_COUNT];
2341     if (!convert_simple_acl(aclBuf, ownerId, ownerDisplayName,
2342                             &aclGrantCount, aclGrants)) {
2343         fprintf(stderr, "\nERROR: Failed to parse ACLs\n");
2344         fclose(infile);
2345         exit(-1);
2346     }
2347
2348     S3_init();
2349
2350     S3BucketContext bucketContext =
2351     {
2352         bucketName,
2353         protocolG,
2354         uriStyleG,
2355         accessKeyIdG,
2356         secretAccessKeyG
2357     };
2358
2359     S3ResponseHandler responseHandler =
2360     {
2361         &responsePropertiesCallback,
2362         &responseCompleteCallback
2363     };
2364
2365     do {
2366         S3_set_acl(&bucketContext, key, ownerId, ownerDisplayName,
2367                    aclGrantCount, aclGrants, 0, &responseHandler, 0);
2368     } while (S3_status_is_retryable(statusG) && should_retry());
2369     
2370     if (statusG != S3StatusOK) {
2371         printError();
2372     }
2373
2374     fclose(infile);
2375
2376     S3_deinitialize();
2377 }
2378
2379
2380 // get logging ----------------------------------------------------------------
2381
2382 void get_logging(int argc, char **argv, int optindex)
2383 {
2384     if (optindex == argc) {
2385         fprintf(stderr, "\nERROR: Missing parameter: bucket\n");
2386         usageExit(stderr);
2387     }
2388
2389     const char *bucketName = argv[optindex++];
2390     const char *filename = 0;
2391
2392     while (optindex < argc) {
2393         char *param = argv[optindex++];
2394         if (!strncmp(param, FILENAME_PREFIX, FILENAME_PREFIX_LEN)) {
2395             filename = &(param[FILENAME_PREFIX_LEN]);
2396         }
2397         else {
2398             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
2399             usageExit(stderr);
2400         }
2401     }
2402
2403     FILE *outfile = 0;
2404
2405     if (filename) {
2406         // Stat the file, and if it doesn't exist, open it in w mode
2407         struct stat buf;
2408         if (stat(filename, &buf) == -1) {
2409             outfile = fopen(filename, "w" FOPEN_EXTRA_FLAGS);
2410         }
2411         else {
2412             // Open in r+ so that we don't truncate the file, just in case
2413             // there is an error and we write no bytes, we leave the file
2414             // unmodified
2415             outfile = fopen(filename, "r+" FOPEN_EXTRA_FLAGS);
2416         }
2417         
2418         if (!outfile) {
2419             fprintf(stderr, "\nERROR: Failed to open output file %s: ",
2420                     filename);
2421             perror(0);
2422             exit(-1);
2423         }
2424     }
2425     else if (showResponsePropertiesG) {
2426         fprintf(stderr, "\nERROR: getlogging -s requires a filename "
2427                 "parameter\n");
2428         usageExit(stderr);
2429     }
2430     else {
2431         outfile = stdout;
2432     }
2433
2434     int aclGrantCount;
2435     S3AclGrant aclGrants[S3_MAX_ACL_GRANT_COUNT];
2436     char targetBucket[S3_MAX_BUCKET_NAME_SIZE];
2437     char targetPrefix[S3_MAX_KEY_SIZE];
2438
2439     S3_init();
2440
2441     S3BucketContext bucketContext =
2442     {
2443         bucketName,
2444         protocolG,
2445         uriStyleG,
2446         accessKeyIdG,
2447         secretAccessKeyG
2448     };
2449
2450     S3ResponseHandler responseHandler =
2451     {
2452         &responsePropertiesCallback,
2453         &responseCompleteCallback
2454     };
2455
2456     do {
2457         S3_get_server_access_logging(&bucketContext, targetBucket, targetPrefix,
2458                                      &aclGrantCount, aclGrants, 0, 
2459                                      &responseHandler, 0);
2460     } while (S3_status_is_retryable(statusG) && should_retry());
2461
2462     if (statusG == S3StatusOK) {
2463         if (targetBucket[0]) {
2464             printf("Target Bucket: %s\n", targetBucket);
2465             if (targetPrefix[0]) {
2466                 printf("Target Prefix: %s\n", targetPrefix);
2467             }
2468             fprintf(outfile, "%-6s  %-90s  %-12s\n", " Type", 
2469                     "                                   User Identifier",
2470                     " Permission");
2471             fprintf(outfile, "------  "
2472                     "---------------------------------------------------------"
2473                     "---------------------------------  ------------\n");
2474             int i;
2475             for (i = 0; i < aclGrantCount; i++) {
2476                 S3AclGrant *grant = &(aclGrants[i]);
2477                 const char *type;
2478                 char composedId[S3_MAX_GRANTEE_USER_ID_SIZE + 
2479                                 S3_MAX_GRANTEE_DISPLAY_NAME_SIZE + 16];
2480                 const char *id;
2481                 
2482                 switch (grant->granteeType) {
2483                 case S3GranteeTypeAmazonCustomerByEmail:
2484                     type = "Email";
2485                     id = grant->grantee.amazonCustomerByEmail.emailAddress;
2486                     break;
2487                 case S3GranteeTypeCanonicalUser:
2488                     type = "UserID";
2489                     snprintf(composedId, sizeof(composedId),
2490                              "%s (%s)", grant->grantee.canonicalUser.id,
2491                              grant->grantee.canonicalUser.displayName);
2492                     id = composedId;
2493                     break;
2494                 case S3GranteeTypeAllAwsUsers:
2495                     type = "Group";
2496                     id = "Authenticated AWS Users";
2497                     break;
2498                 default:
2499                     type = "Group";
2500                     id = "All Users";
2501                     break;
2502                 }
2503                 const char *perm;
2504                 switch (grant->permission) {
2505                 case S3PermissionRead:
2506                     perm = "READ";
2507                     break;
2508                 case S3PermissionWrite:
2509                     perm = "WRITE";
2510                     break;
2511                 case S3PermissionReadACP:
2512                     perm = "READ_ACP";
2513                     break;
2514                 case S3PermissionWriteACP:
2515                     perm = "WRITE_ACP";
2516                     break;
2517                 default:
2518                     perm = "FULL_CONTROL";
2519                     break;
2520                 }
2521                 fprintf(outfile, "%-6s  %-90s  %-12s\n", type, id, perm);
2522             }
2523         }
2524         else {
2525             printf("Service logging is not enabled for this bucket.\n");
2526         }
2527     }
2528     else {
2529         printError();
2530     }
2531
2532     fclose(outfile);
2533
2534     S3_deinitialize();
2535 }
2536
2537
2538 // set logging ----------------------------------------------------------------
2539
2540 void set_logging(int argc, char **argv, int optindex)
2541 {
2542     if (optindex == argc) {
2543         fprintf(stderr, "\nERROR: Missing parameter: bucket\n");
2544         usageExit(stderr);
2545     }
2546
2547     const char *bucketName = argv[optindex++];
2548
2549     const char *targetBucket = 0, *targetPrefix = 0, *filename = 0;
2550
2551     while (optindex < argc) {
2552         char *param = argv[optindex++];
2553         if (!strncmp(param, TARGET_BUCKET_PREFIX, TARGET_BUCKET_PREFIX_LEN)) {
2554             targetBucket = &(param[TARGET_BUCKET_PREFIX_LEN]);
2555         }
2556         else if (!strncmp(param, TARGET_PREFIX_PREFIX, 
2557                           TARGET_PREFIX_PREFIX_LEN)) {
2558             targetPrefix = &(param[TARGET_PREFIX_PREFIX_LEN]);
2559         }
2560         else if (!strncmp(param, FILENAME_PREFIX, FILENAME_PREFIX_LEN)) {
2561             filename = &(param[FILENAME_PREFIX_LEN]);
2562         }
2563         else {
2564             fprintf(stderr, "\nERROR: Unknown param: %s\n", param);
2565             usageExit(stderr);
2566         }
2567     }
2568
2569     int aclGrantCount = 0;
2570     S3AclGrant aclGrants[S3_MAX_ACL_GRANT_COUNT];
2571
2572     if (targetBucket) {
2573         FILE *infile;
2574         
2575         if (filename) {
2576             if (!(infile = fopen(filename, "r" FOPEN_EXTRA_FLAGS))) {
2577                 fprintf(stderr, "\nERROR: Failed to open input file %s: ",
2578                         filename);
2579                 perror(0);
2580                 exit(-1);
2581             }
2582         }
2583         else {
2584             infile = stdin;
2585         }
2586
2587         // Read in the complete ACL
2588         char aclBuf[65536];
2589         aclBuf[fread(aclBuf, 1, sizeof(aclBuf), infile)] = 0;
2590         char ownerId[S3_MAX_GRANTEE_USER_ID_SIZE];
2591         char ownerDisplayName[S3_MAX_GRANTEE_DISPLAY_NAME_SIZE];
2592         
2593         // Parse it
2594         if (!convert_simple_acl(aclBuf, ownerId, ownerDisplayName,
2595                                 &aclGrantCount, aclGrants)) {
2596             fprintf(stderr, "\nERROR: Failed to parse ACLs\n");
2597             fclose(infile);
2598             exit(-1);
2599         }
2600
2601         fclose(infile);
2602     }
2603
2604     S3_init();
2605
2606     S3BucketContext bucketContext =
2607     {
2608         bucketName,
2609         protocolG,
2610         uriStyleG,
2611         accessKeyIdG,
2612         secretAccessKeyG
2613     };
2614
2615     S3ResponseHandler responseHandler =
2616     {
2617         &responsePropertiesCallback,
2618         &responseCompleteCallback
2619     };
2620
2621     do {
2622         S3_set_server_access_logging(&bucketContext, targetBucket, 
2623                                      targetPrefix, aclGrantCount, aclGrants, 
2624                                      0, &responseHandler, 0);
2625     } while (S3_status_is_retryable(statusG) && should_retry());
2626     
2627     if (statusG != S3StatusOK) {
2628         printError();
2629     }
2630
2631     S3_deinitialize();
2632 }
2633
2634
2635 // main ----------------------------------------------------------------------
2636
2637 int main(int argc, char **argv)
2638 {
2639     // Parse args
2640     while (1) {
2641         int idx = 0;
2642         int c = getopt_long(argc, argv, "fhusr:", longOptionsG, &idx);
2643
2644         if (c == -1) {
2645             // End of options
2646             break;
2647         }
2648
2649         switch (c) {
2650         case 'f':
2651             forceG = 1;
2652             break;
2653         case 'h':
2654             uriStyleG = S3UriStyleVirtualHost;
2655             break;
2656         case 'u':
2657             protocolG = S3ProtocolHTTP;
2658             break;
2659         case 's':
2660             showResponsePropertiesG = 1;
2661             break;
2662         case 'r': {
2663             const char *v = optarg;
2664             while (*v) {
2665                 retriesG *= 10;
2666                 retriesG += *v - '0';
2667                 v++;
2668             }
2669             break;
2670         }
2671         default:
2672             fprintf(stderr, "\nERROR: Unknown option: -%c\n", c);
2673             // Usage exit
2674             usageExit(stderr);
2675         }
2676     }
2677
2678     // The first non-option argument gives the operation to perform
2679     if (optind == argc) {
2680         fprintf(stderr, "\n\nERROR: Missing argument: command\n\n");
2681         usageExit(stderr);
2682     }
2683
2684     const char *command = argv[optind++];
2685     
2686     if (!strcmp(command, "help")) {
2687         fprintf(stdout, "\ns3 is a program for performing single requests "
2688                 "to Amazon S3.\n");
2689         usageExit(stdout);
2690     }
2691
2692     accessKeyIdG = getenv("S3_ACCESS_KEY_ID");
2693     if (!accessKeyIdG) {
2694         fprintf(stderr, "Missing environment variable: S3_ACCESS_KEY_ID\n");
2695         return -1;
2696     }
2697     secretAccessKeyG = getenv("S3_SECRET_ACCESS_KEY");
2698     if (!secretAccessKeyG) {
2699         fprintf(stderr, 
2700                 "Missing environment variable: S3_SECRET_ACCESS_KEY\n");
2701         return -1;
2702     }
2703
2704     if (!strcmp(command, "list")) {
2705         list(argc, argv, optind);
2706     }
2707     else if (!strcmp(command, "test")) {
2708         test_bucket(argc, argv, optind);
2709     }
2710     else if (!strcmp(command, "create")) {
2711         create_bucket(argc, argv, optind);
2712     }
2713     else if (!strcmp(command, "delete")) {
2714         if (optind == argc) {
2715             fprintf(stderr, 
2716                     "\nERROR: Missing parameter: bucket or bucket/key\n");
2717             usageExit(stderr);
2718         }
2719         char *val = argv[optind];
2720         int hasSlash = 0;
2721         while (*val) {
2722             if (*val++ == '/') {
2723                 hasSlash = 1;
2724                 break;
2725             }
2726         }
2727         if (hasSlash) {
2728             delete_object(argc, argv, optind);
2729         }
2730         else {
2731             delete_bucket(argc, argv, optind);
2732         }
2733     }
2734     else if (!strcmp(command, "put")) {
2735         put_object(argc, argv, optind);
2736     }
2737     else if (!strcmp(command, "copy")) {
2738         copy_object(argc, argv, optind);
2739     }
2740     else if (!strcmp(command, "get")) {
2741         get_object(argc, argv, optind);
2742     }
2743     else if (!strcmp(command, "head")) {
2744         head_object(argc, argv, optind);
2745     }
2746     else if (!strcmp(command, "gqs")) {
2747         generate_query_string(argc, argv, optind);
2748     }
2749     else if (!strcmp(command, "getacl")) {
2750         get_acl(argc, argv, optind);
2751     }
2752     else if (!strcmp(command, "setacl")) {
2753         set_acl(argc, argv, optind);
2754     }
2755     else if (!strcmp(command, "getlogging")) {
2756         get_logging(argc, argv, optind);
2757     }
2758     else if (!strcmp(command, "setlogging")) {
2759         set_logging(argc, argv, optind);
2760     }
2761     else {
2762         fprintf(stderr, "Unknown command: %s\n", command);
2763         return -1;
2764     }
2765
2766     return 0;
2767 }