Commit | Line | Data |
---|---|---|
c2f6a13d LMB |
1 | /* |
2 | * HTTP GET/PUT IO engine | |
3 | * | |
4 | * IO engine to perform HTTP(S) GET/PUT requests via libcurl-easy. | |
5 | * | |
6 | * Copyright (C) 2018 SUSE LLC | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License, | |
10 | * version 2 as published by the Free Software Foundation.. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public | |
18 | * License along with this program; if not, write to the Free | |
19 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
20 | * Boston, MA 02110-1301, USA. | |
21 | */ | |
22 | ||
23 | #include <pthread.h> | |
24 | #include <time.h> | |
25 | #include <curl/curl.h> | |
26 | #include <openssl/hmac.h> | |
27 | #include <openssl/sha.h> | |
09fd2966 | 28 | #include <openssl/md5.h> |
c2f6a13d LMB |
29 | #include "fio.h" |
30 | #include "../optgroup.h" | |
31 | ||
9c1c1a8d JA |
32 | /* |
33 | * Silence OpenSSL 3.0 deprecated function warnings | |
34 | */ | |
35 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |
c2f6a13d | 36 | |
09fd2966 LMB |
37 | enum { |
38 | FIO_HTTP_WEBDAV = 0, | |
39 | FIO_HTTP_S3 = 1, | |
40 | FIO_HTTP_SWIFT = 2, | |
41 | ||
42 | FIO_HTTPS_OFF = 0, | |
43 | FIO_HTTPS_ON = 1, | |
44 | FIO_HTTPS_INSECURE = 2, | |
45 | }; | |
46 | ||
c2f6a13d LMB |
47 | struct http_data { |
48 | CURL *curl; | |
49 | }; | |
50 | ||
51 | struct http_options { | |
52 | void *pad; | |
09fd2966 | 53 | unsigned int https; |
c2f6a13d LMB |
54 | char *host; |
55 | char *user; | |
56 | char *pass; | |
57 | char *s3_key; | |
58 | char *s3_keyid; | |
59 | char *s3_region; | |
91af79c4 FH |
60 | char *s3_sse_customer_key; |
61 | char *s3_sse_customer_algorithm; | |
f2d79184 | 62 | char *s3_storage_class; |
09fd2966 | 63 | char *swift_auth_token; |
c2f6a13d | 64 | int verbose; |
09fd2966 | 65 | unsigned int mode; |
c2f6a13d LMB |
66 | }; |
67 | ||
68 | struct http_curl_stream { | |
69 | char *buf; | |
70 | size_t pos; | |
71 | size_t max; | |
72 | }; | |
73 | ||
74 | static struct fio_option options[] = { | |
75 | { | |
76 | .name = "https", | |
77 | .lname = "https", | |
09fd2966 | 78 | .type = FIO_OPT_STR, |
c2f6a13d LMB |
79 | .help = "Enable https", |
80 | .off1 = offsetof(struct http_options, https), | |
09fd2966 LMB |
81 | .def = "off", |
82 | .posval = { | |
83 | { .ival = "off", | |
84 | .oval = FIO_HTTPS_OFF, | |
85 | .help = "No HTTPS", | |
86 | }, | |
87 | { .ival = "on", | |
88 | .oval = FIO_HTTPS_ON, | |
89 | .help = "Enable HTTPS", | |
90 | }, | |
91 | { .ival = "insecure", | |
92 | .oval = FIO_HTTPS_INSECURE, | |
93 | .help = "Enable HTTPS, disable peer verification", | |
94 | }, | |
95 | }, | |
c2f6a13d LMB |
96 | .category = FIO_OPT_C_ENGINE, |
97 | .group = FIO_OPT_G_HTTP, | |
98 | }, | |
99 | { | |
100 | .name = "http_host", | |
101 | .lname = "http_host", | |
102 | .type = FIO_OPT_STR_STORE, | |
103 | .help = "Hostname (S3 bucket)", | |
104 | .off1 = offsetof(struct http_options, host), | |
105 | .def = "localhost", | |
106 | .category = FIO_OPT_C_ENGINE, | |
107 | .group = FIO_OPT_G_HTTP, | |
108 | }, | |
109 | { | |
110 | .name = "http_user", | |
111 | .lname = "http_user", | |
112 | .type = FIO_OPT_STR_STORE, | |
113 | .help = "HTTP user name", | |
114 | .off1 = offsetof(struct http_options, user), | |
115 | .category = FIO_OPT_C_ENGINE, | |
116 | .group = FIO_OPT_G_HTTP, | |
117 | }, | |
118 | { | |
119 | .name = "http_pass", | |
120 | .lname = "http_pass", | |
121 | .type = FIO_OPT_STR_STORE, | |
122 | .help = "HTTP password", | |
123 | .off1 = offsetof(struct http_options, pass), | |
124 | .category = FIO_OPT_C_ENGINE, | |
125 | .group = FIO_OPT_G_HTTP, | |
126 | }, | |
127 | { | |
128 | .name = "http_s3_key", | |
129 | .lname = "S3 secret key", | |
130 | .type = FIO_OPT_STR_STORE, | |
131 | .help = "S3 secret key", | |
132 | .off1 = offsetof(struct http_options, s3_key), | |
133 | .def = "", | |
134 | .category = FIO_OPT_C_ENGINE, | |
135 | .group = FIO_OPT_G_HTTP, | |
136 | }, | |
137 | { | |
138 | .name = "http_s3_keyid", | |
139 | .lname = "S3 key id", | |
140 | .type = FIO_OPT_STR_STORE, | |
141 | .help = "S3 key id", | |
142 | .off1 = offsetof(struct http_options, s3_keyid), | |
143 | .def = "", | |
144 | .category = FIO_OPT_C_ENGINE, | |
145 | .group = FIO_OPT_G_HTTP, | |
146 | }, | |
09fd2966 LMB |
147 | { |
148 | .name = "http_swift_auth_token", | |
149 | .lname = "Swift auth token", | |
150 | .type = FIO_OPT_STR_STORE, | |
151 | .help = "OpenStack Swift auth token", | |
152 | .off1 = offsetof(struct http_options, swift_auth_token), | |
153 | .def = "", | |
154 | .category = FIO_OPT_C_ENGINE, | |
155 | .group = FIO_OPT_G_HTTP, | |
156 | }, | |
c2f6a13d LMB |
157 | { |
158 | .name = "http_s3_region", | |
159 | .lname = "S3 region", | |
160 | .type = FIO_OPT_STR_STORE, | |
161 | .help = "S3 region", | |
162 | .off1 = offsetof(struct http_options, s3_region), | |
163 | .def = "us-east-1", | |
164 | .category = FIO_OPT_C_ENGINE, | |
165 | .group = FIO_OPT_G_HTTP, | |
166 | }, | |
91af79c4 FH |
167 | { |
168 | .name = "http_s3_sse_customer_key", | |
169 | .lname = "SSE Customer Key", | |
170 | .type = FIO_OPT_STR_STORE, | |
171 | .help = "S3 SSE Customer Key", | |
172 | .off1 = offsetof(struct http_options, s3_sse_customer_key), | |
173 | .def = "", | |
174 | .category = FIO_OPT_C_ENGINE, | |
175 | .group = FIO_OPT_G_HTTP, | |
176 | }, | |
177 | { | |
178 | .name = "http_s3_sse_customer_algorithm", | |
179 | .lname = "SSE Customer Algorithm", | |
180 | .type = FIO_OPT_STR_STORE, | |
181 | .help = "S3 SSE Customer Algorithm", | |
182 | .off1 = offsetof(struct http_options, s3_sse_customer_algorithm), | |
183 | .def = "AES256", | |
184 | .category = FIO_OPT_C_ENGINE, | |
185 | .group = FIO_OPT_G_HTTP, | |
186 | }, | |
f2d79184 FH |
187 | { |
188 | .name = "http_s3_storage_class", | |
189 | .lname = "S3 Storage class", | |
190 | .type = FIO_OPT_STR_STORE, | |
191 | .help = "S3 Storage Class", | |
192 | .off1 = offsetof(struct http_options, s3_storage_class), | |
193 | .def = "STANDARD", | |
194 | .category = FIO_OPT_C_ENGINE, | |
195 | .group = FIO_OPT_G_HTTP, | |
196 | }, | |
c2f6a13d | 197 | { |
09fd2966 LMB |
198 | .name = "http_mode", |
199 | .lname = "Request mode to use", | |
200 | .type = FIO_OPT_STR, | |
201 | .help = "Whether to use WebDAV, Swift, or S3", | |
202 | .off1 = offsetof(struct http_options, mode), | |
203 | .def = "webdav", | |
204 | .posval = { | |
205 | { .ival = "webdav", | |
206 | .oval = FIO_HTTP_WEBDAV, | |
207 | .help = "WebDAV server", | |
208 | }, | |
209 | { .ival = "s3", | |
210 | .oval = FIO_HTTP_S3, | |
211 | .help = "S3 storage backend", | |
212 | }, | |
213 | { .ival = "swift", | |
214 | .oval = FIO_HTTP_SWIFT, | |
215 | .help = "OpenStack Swift storage", | |
216 | }, | |
217 | }, | |
c2f6a13d LMB |
218 | .category = FIO_OPT_C_ENGINE, |
219 | .group = FIO_OPT_G_HTTP, | |
220 | }, | |
221 | { | |
222 | .name = "http_verbose", | |
09fd2966 | 223 | .lname = "HTTP verbosity level", |
c2f6a13d LMB |
224 | .type = FIO_OPT_INT, |
225 | .help = "increase http engine verbosity", | |
226 | .off1 = offsetof(struct http_options, verbose), | |
227 | .def = "0", | |
228 | .category = FIO_OPT_C_ENGINE, | |
229 | .group = FIO_OPT_G_HTTP, | |
230 | }, | |
231 | { | |
232 | .name = NULL, | |
233 | }, | |
234 | }; | |
235 | ||
236 | static char *_aws_uriencode(const char *uri) | |
237 | { | |
238 | size_t bufsize = 1024; | |
239 | char *r = malloc(bufsize); | |
240 | char c; | |
241 | int i, n; | |
242 | const char *hex = "0123456789ABCDEF"; | |
243 | ||
244 | if (!r) { | |
245 | log_err("malloc failed\n"); | |
246 | return NULL; | |
247 | } | |
248 | ||
249 | n = 0; | |
250 | for (i = 0; (c = uri[i]); i++) { | |
251 | if (n > bufsize-5) { | |
252 | log_err("encoding the URL failed\n"); | |
9e66b060 | 253 | free(r); |
c2f6a13d LMB |
254 | return NULL; |
255 | } | |
256 | ||
257 | if ( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') | |
258 | || (c >= '0' && c <= '9') || c == '_' || c == '-' | |
259 | || c == '~' || c == '.' || c == '/') | |
260 | r[n++] = c; | |
261 | else { | |
262 | r[n++] = '%'; | |
263 | r[n++] = hex[(c >> 4 ) & 0xF]; | |
264 | r[n++] = hex[c & 0xF]; | |
265 | } | |
266 | } | |
267 | r[n++] = 0; | |
268 | return r; | |
269 | } | |
270 | ||
271 | static char *_conv_hex(const unsigned char *p, size_t len) | |
272 | { | |
273 | char *r; | |
274 | int i,n; | |
275 | const char *hex = "0123456789abcdef"; | |
276 | r = malloc(len * 2 + 1); | |
277 | n = 0; | |
278 | for (i = 0; i < len; i++) { | |
279 | r[n++] = hex[(p[i] >> 4 ) & 0xF]; | |
280 | r[n++] = hex[p[i] & 0xF]; | |
281 | } | |
282 | r[n] = 0; | |
283 | ||
284 | return r; | |
285 | } | |
286 | ||
287 | static char *_gen_hex_sha256(const char *p, size_t len) | |
288 | { | |
289 | unsigned char hash[SHA256_DIGEST_LENGTH]; | |
290 | ||
291 | SHA256((unsigned char*)p, len, hash); | |
292 | return _conv_hex(hash, SHA256_DIGEST_LENGTH); | |
293 | } | |
294 | ||
09fd2966 LMB |
295 | static char *_gen_hex_md5(const char *p, size_t len) |
296 | { | |
297 | unsigned char hash[MD5_DIGEST_LENGTH]; | |
298 | ||
299 | MD5((unsigned char*)p, len, hash); | |
300 | return _conv_hex(hash, MD5_DIGEST_LENGTH); | |
301 | } | |
302 | ||
91af79c4 FH |
303 | static char *_conv_base64_encode(const unsigned char *p, size_t len) |
304 | { | |
305 | char *r, *ret; | |
306 | int i; | |
307 | static const char sEncodingTable[] = { | |
308 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', | |
309 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', | |
310 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', | |
311 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', | |
312 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', | |
313 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', | |
314 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', | |
315 | '4', '5', '6', '7', '8', '9', '+', '/' | |
316 | }; | |
317 | ||
318 | size_t out_len = 4 * ((len + 2) / 3); | |
319 | ret = r = malloc(out_len + 1); | |
320 | ||
321 | for (i = 0; i < len - 2; i += 3) { | |
322 | *r++ = sEncodingTable[(p[i] >> 2) & 0x3F]; | |
323 | *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)]; | |
324 | *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2) | ((int) (p[i + 2] & 0xC0) >> 6)]; | |
325 | *r++ = sEncodingTable[p[i + 2] & 0x3F]; | |
326 | } | |
327 | ||
328 | if (i < len) { | |
329 | *r++ = sEncodingTable[(p[i] >> 2) & 0x3F]; | |
330 | if (i == (len - 1)) { | |
331 | *r++ = sEncodingTable[((p[i] & 0x3) << 4)]; | |
332 | *r++ = '='; | |
333 | } else { | |
334 | *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)]; | |
335 | *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2)]; | |
336 | } | |
337 | *r++ = '='; | |
338 | } | |
339 | ||
340 | ret[out_len]=0; | |
341 | return ret; | |
342 | } | |
343 | ||
344 | static char *_gen_base64_md5(const unsigned char *p, size_t len) | |
345 | { | |
346 | unsigned char hash[MD5_DIGEST_LENGTH]; | |
347 | MD5((unsigned char*)p, len, hash); | |
348 | return _conv_base64_encode(hash, MD5_DIGEST_LENGTH); | |
349 | } | |
350 | ||
c2f6a13d | 351 | static void _hmac(unsigned char *md, void *key, int key_len, char *data) { |
b61a5f46 DD |
352 | #ifndef CONFIG_HAVE_OPAQUE_HMAC_CTX |
353 | HMAC_CTX _ctx; | |
354 | #endif | |
c2f6a13d LMB |
355 | HMAC_CTX *ctx; |
356 | unsigned int hmac_len; | |
357 | ||
b61a5f46 | 358 | #ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX |
c2f6a13d | 359 | ctx = HMAC_CTX_new(); |
b61a5f46 DD |
360 | #else |
361 | ctx = &_ctx; | |
e8aaa776 JA |
362 | /* work-around crash in certain versions of libssl */ |
363 | HMAC_CTX_init(ctx); | |
b61a5f46 | 364 | #endif |
c2f6a13d LMB |
365 | HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), NULL); |
366 | HMAC_Update(ctx, (unsigned char*)data, strlen(data)); | |
367 | HMAC_Final(ctx, md, &hmac_len); | |
b61a5f46 | 368 | #ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX |
c2f6a13d | 369 | HMAC_CTX_free(ctx); |
b61a5f46 DD |
370 | #else |
371 | HMAC_CTX_cleanup(ctx); | |
372 | #endif | |
c2f6a13d LMB |
373 | } |
374 | ||
375 | static int _curl_trace(CURL *handle, curl_infotype type, | |
376 | char *data, size_t size, | |
377 | void *userp) | |
378 | { | |
379 | const char *text; | |
380 | (void)handle; /* prevent compiler warning */ | |
381 | (void)userp; | |
382 | ||
383 | switch (type) { | |
384 | case CURLINFO_TEXT: | |
24010223 | 385 | fprintf(stderr, "== Info: %s", data); |
87933e32 | 386 | fio_fallthrough; |
c2f6a13d LMB |
387 | default: |
388 | case CURLINFO_SSL_DATA_OUT: | |
389 | case CURLINFO_SSL_DATA_IN: | |
390 | return 0; | |
391 | ||
392 | case CURLINFO_HEADER_OUT: | |
393 | text = "=> Send header"; | |
394 | break; | |
395 | case CURLINFO_DATA_OUT: | |
396 | text = "=> Send data"; | |
397 | break; | |
398 | case CURLINFO_HEADER_IN: | |
399 | text = "<= Recv header"; | |
400 | break; | |
401 | case CURLINFO_DATA_IN: | |
402 | text = "<= Recv data"; | |
403 | break; | |
404 | } | |
405 | ||
406 | log_info("%s: %s", text, data); | |
407 | return 0; | |
408 | } | |
409 | ||
410 | /* https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html | |
411 | * https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro | |
412 | */ | |
413 | static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct http_options *o, | |
414 | int op, const char *uri, char *buf, size_t len) | |
415 | { | |
416 | char date_short[16]; | |
417 | char date_iso[32]; | |
418 | char method[8]; | |
419 | char dkey[128]; | |
f2d79184 FH |
420 | char creq[4096]; |
421 | char sts[512]; | |
c2f6a13d LMB |
422 | char s[512]; |
423 | char *uri_encoded = NULL; | |
424 | char *dsha = NULL; | |
425 | char *csha = NULL; | |
426 | char *signature = NULL; | |
427 | const char *service = "s3"; | |
428 | const char *aws = "aws4_request"; | |
429 | unsigned char md[SHA256_DIGEST_LENGTH]; | |
91af79c4 FH |
430 | unsigned char sse_key[33] = {0}; |
431 | char *sse_key_base64 = NULL; | |
432 | char *sse_key_md5_base64 = NULL; | |
c2f6a13d LMB |
433 | |
434 | time_t t = time(NULL); | |
435 | struct tm *gtm = gmtime(&t); | |
436 | ||
437 | strftime (date_short, sizeof(date_short), "%Y%m%d", gtm); | |
438 | strftime (date_iso, sizeof(date_iso), "%Y%m%dT%H%M%SZ", gtm); | |
439 | uri_encoded = _aws_uriencode(uri); | |
440 | ||
91af79c4 FH |
441 | if (o->s3_sse_customer_key != NULL) |
442 | strncpy((char*)sse_key, o->s3_sse_customer_key, sizeof(sse_key) - 1); | |
443 | ||
c2f6a13d LMB |
444 | if (op == DDIR_WRITE) { |
445 | dsha = _gen_hex_sha256(buf, len); | |
446 | sprintf(method, "PUT"); | |
447 | } else { | |
448 | /* DDIR_READ && DDIR_TRIM supply an empty body */ | |
449 | if (op == DDIR_READ) | |
450 | sprintf(method, "GET"); | |
451 | else | |
452 | sprintf(method, "DELETE"); | |
453 | dsha = _gen_hex_sha256("", 0); | |
454 | } | |
455 | ||
456 | /* Create the canonical request first */ | |
91af79c4 FH |
457 | if (sse_key[0] != '\0') { |
458 | sse_key_base64 = _conv_base64_encode(sse_key, sizeof(sse_key) - 1); | |
459 | sse_key_md5_base64 = _gen_base64_md5(sse_key, sizeof(sse_key) - 1); | |
460 | snprintf(creq, sizeof(creq), | |
461 | "%s\n" | |
462 | "%s\n" | |
463 | "\n" | |
464 | "host:%s\n" | |
465 | "x-amz-content-sha256:%s\n" | |
466 | "x-amz-date:%s\n" | |
467 | "x-amz-server-side-encryption-customer-algorithm:%s\n" | |
468 | "x-amz-server-side-encryption-customer-key:%s\n" | |
469 | "x-amz-server-side-encryption-customer-key-md5:%s\n" | |
470 | "x-amz-storage-class:%s\n" | |
471 | "\n" | |
472 | "host;x-amz-content-sha256;x-amz-date;" | |
473 | "x-amz-server-side-encryption-customer-algorithm;" | |
474 | "x-amz-server-side-encryption-customer-key;" | |
475 | "x-amz-server-side-encryption-customer-key-md5;" | |
476 | "x-amz-storage-class\n" | |
477 | "%s" | |
478 | , method | |
479 | , uri_encoded, o->host, dsha, date_iso | |
480 | , o->s3_sse_customer_algorithm, sse_key_base64 | |
481 | , sse_key_md5_base64, o->s3_storage_class, dsha); | |
482 | } else { | |
483 | snprintf(creq, sizeof(creq), | |
484 | "%s\n" | |
485 | "%s\n" | |
486 | "\n" | |
487 | "host:%s\n" | |
488 | "x-amz-content-sha256:%s\n" | |
489 | "x-amz-date:%s\n" | |
490 | "x-amz-storage-class:%s\n" | |
491 | "\n" | |
492 | "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n" | |
493 | "%s" | |
494 | , method | |
495 | , uri_encoded, o->host, dsha, date_iso, o->s3_storage_class, dsha); | |
496 | } | |
c2f6a13d LMB |
497 | |
498 | csha = _gen_hex_sha256(creq, strlen(creq)); | |
499 | snprintf(sts, sizeof(sts), "AWS4-HMAC-SHA256\n%s\n%s/%s/%s/%s\n%s", | |
91af79c4 | 500 | date_iso, date_short, o->s3_region, service, aws, csha); |
c2f6a13d LMB |
501 | |
502 | snprintf((char *)dkey, sizeof(dkey), "AWS4%s", o->s3_key); | |
503 | _hmac(md, dkey, strlen(dkey), date_short); | |
504 | _hmac(md, md, SHA256_DIGEST_LENGTH, o->s3_region); | |
505 | _hmac(md, md, SHA256_DIGEST_LENGTH, (char*) service); | |
506 | _hmac(md, md, SHA256_DIGEST_LENGTH, (char*) aws); | |
507 | _hmac(md, md, SHA256_DIGEST_LENGTH, sts); | |
508 | ||
509 | signature = _conv_hex(md, SHA256_DIGEST_LENGTH); | |
510 | ||
fc002f14 | 511 | /* Suppress automatic Accept: header */ |
c2f6a13d LMB |
512 | slist = curl_slist_append(slist, "Accept:"); |
513 | ||
514 | snprintf(s, sizeof(s), "x-amz-content-sha256: %s", dsha); | |
515 | slist = curl_slist_append(slist, s); | |
516 | ||
517 | snprintf(s, sizeof(s), "x-amz-date: %s", date_iso); | |
518 | slist = curl_slist_append(slist, s); | |
91af79c4 FH |
519 | |
520 | if (sse_key[0] != '\0') { | |
521 | snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-algorithm: %s", o->s3_sse_customer_algorithm); | |
522 | slist = curl_slist_append(slist, s); | |
523 | snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-key: %s", sse_key_base64); | |
524 | slist = curl_slist_append(slist, s); | |
525 | snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-key-md5: %s", sse_key_md5_base64); | |
526 | slist = curl_slist_append(slist, s); | |
527 | } | |
528 | ||
f2d79184 FH |
529 | snprintf(s, sizeof(s), "x-amz-storage-class: %s", o->s3_storage_class); |
530 | slist = curl_slist_append(slist, s); | |
91af79c4 FH |
531 | |
532 | if (sse_key[0] != '\0') { | |
533 | snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request," | |
534 | "SignedHeaders=host;x-amz-content-sha256;" | |
535 | "x-amz-date;x-amz-server-side-encryption-customer-algorithm;" | |
536 | "x-amz-server-side-encryption-customer-key;" | |
537 | "x-amz-server-side-encryption-customer-key-md5;" | |
538 | "x-amz-storage-class," | |
539 | "Signature=%s", | |
540 | o->s3_keyid, date_short, o->s3_region, signature); | |
541 | } else { | |
542 | snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request," | |
543 | "SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-storage-class,Signature=%s", | |
544 | o->s3_keyid, date_short, o->s3_region, signature); | |
545 | } | |
c2f6a13d LMB |
546 | slist = curl_slist_append(slist, s); |
547 | ||
548 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); | |
549 | ||
550 | free(uri_encoded); | |
551 | free(csha); | |
552 | free(dsha); | |
553 | free(signature); | |
91af79c4 FH |
554 | if (sse_key_base64 != NULL) { |
555 | free(sse_key_base64); | |
556 | free(sse_key_md5_base64); | |
557 | } | |
c2f6a13d LMB |
558 | } |
559 | ||
09fd2966 LMB |
560 | static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_options *o, |
561 | int op, const char *uri, char *buf, size_t len) | |
562 | { | |
563 | char *dsha = NULL; | |
564 | char s[512]; | |
565 | ||
566 | if (op == DDIR_WRITE) { | |
567 | dsha = _gen_hex_md5(buf, len); | |
568 | } | |
fc002f14 | 569 | /* Suppress automatic Accept: header */ |
09fd2966 LMB |
570 | slist = curl_slist_append(slist, "Accept:"); |
571 | ||
572 | snprintf(s, sizeof(s), "etag: %s", dsha); | |
573 | slist = curl_slist_append(slist, s); | |
574 | ||
575 | snprintf(s, sizeof(s), "x-auth-token: %s", o->swift_auth_token); | |
576 | slist = curl_slist_append(slist, s); | |
577 | ||
578 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); | |
579 | ||
580 | free(dsha); | |
581 | } | |
582 | ||
c2f6a13d LMB |
583 | static void fio_http_cleanup(struct thread_data *td) |
584 | { | |
585 | struct http_data *http = td->io_ops_data; | |
586 | ||
587 | if (http) { | |
588 | curl_easy_cleanup(http->curl); | |
589 | free(http); | |
590 | } | |
591 | } | |
592 | ||
593 | static size_t _http_read(void *ptr, size_t size, size_t nmemb, void *stream) | |
594 | { | |
595 | struct http_curl_stream *state = stream; | |
596 | size_t len = size * nmemb; | |
597 | /* We're retrieving; nothing is supposed to be read locally */ | |
598 | if (!stream) | |
599 | return 0; | |
600 | if (len+state->pos > state->max) | |
601 | len = state->max - state->pos; | |
602 | memcpy(ptr, &state->buf[state->pos], len); | |
603 | state->pos += len; | |
604 | return len; | |
605 | } | |
606 | ||
607 | static size_t _http_write(void *ptr, size_t size, size_t nmemb, void *stream) | |
608 | { | |
609 | struct http_curl_stream *state = stream; | |
610 | /* We're just discarding the returned body after a PUT */ | |
611 | if (!stream) | |
612 | return nmemb; | |
613 | if (size != 1) | |
614 | return CURLE_WRITE_ERROR; | |
615 | if (nmemb + state->pos > state->max) | |
616 | return CURLE_WRITE_ERROR; | |
617 | memcpy(&state->buf[state->pos], ptr, nmemb); | |
618 | state->pos += nmemb; | |
619 | return nmemb; | |
620 | } | |
621 | ||
622 | static int _http_seek(void *stream, curl_off_t offset, int origin) | |
623 | { | |
624 | struct http_curl_stream *state = stream; | |
625 | if (offset < state->max && origin == SEEK_SET) { | |
626 | state->pos = offset; | |
627 | return CURL_SEEKFUNC_OK; | |
628 | } else | |
629 | return CURL_SEEKFUNC_FAIL; | |
630 | } | |
631 | ||
632 | static enum fio_q_status fio_http_queue(struct thread_data *td, | |
633 | struct io_u *io_u) | |
634 | { | |
635 | struct http_data *http = td->io_ops_data; | |
636 | struct http_options *o = td->eo; | |
637 | struct http_curl_stream _curl_stream; | |
638 | struct curl_slist *slist = NULL; | |
639 | char object[512]; | |
640 | char url[1024]; | |
641 | long status; | |
642 | CURLcode res; | |
c2f6a13d LMB |
643 | |
644 | fio_ro_check(td, io_u); | |
645 | memset(&_curl_stream, 0, sizeof(_curl_stream)); | |
09fd2966 LMB |
646 | snprintf(object, sizeof(object), "%s_%llu_%llu", td->files[0]->file_name, |
647 | io_u->offset, io_u->xfer_buflen); | |
648 | if (o->https == FIO_HTTPS_OFF) | |
649 | snprintf(url, sizeof(url), "http://%s%s", o->host, object); | |
650 | else | |
651 | snprintf(url, sizeof(url), "https://%s%s", o->host, object); | |
c2f6a13d LMB |
652 | curl_easy_setopt(http->curl, CURLOPT_URL, url); |
653 | _curl_stream.buf = io_u->xfer_buf; | |
654 | _curl_stream.max = io_u->xfer_buflen; | |
655 | curl_easy_setopt(http->curl, CURLOPT_SEEKDATA, &_curl_stream); | |
656 | curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)io_u->xfer_buflen); | |
657 | ||
09fd2966 | 658 | if (o->mode == FIO_HTTP_S3) |
c2f6a13d LMB |
659 | _add_aws_auth_header(http->curl, slist, o, io_u->ddir, object, |
660 | io_u->xfer_buf, io_u->xfer_buflen); | |
09fd2966 LMB |
661 | else if (o->mode == FIO_HTTP_SWIFT) |
662 | _add_swift_header(http->curl, slist, o, io_u->ddir, object, | |
663 | io_u->xfer_buf, io_u->xfer_buflen); | |
c2f6a13d LMB |
664 | |
665 | if (io_u->ddir == DDIR_WRITE) { | |
666 | curl_easy_setopt(http->curl, CURLOPT_READDATA, &_curl_stream); | |
667 | curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, NULL); | |
668 | curl_easy_setopt(http->curl, CURLOPT_UPLOAD, 1L); | |
669 | res = curl_easy_perform(http->curl); | |
670 | if (res == CURLE_OK) { | |
671 | curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status); | |
672 | if (status == 100 || (status >= 200 && status <= 204)) | |
673 | goto out; | |
674 | log_err("DDIR_WRITE failed with HTTP status code %ld\n", status); | |
c2f6a13d | 675 | } |
4a4c21bf | 676 | goto err; |
c2f6a13d LMB |
677 | } else if (io_u->ddir == DDIR_READ) { |
678 | curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL); | |
679 | curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, &_curl_stream); | |
680 | curl_easy_setopt(http->curl, CURLOPT_HTTPGET, 1L); | |
681 | res = curl_easy_perform(http->curl); | |
682 | if (res == CURLE_OK) { | |
683 | curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status); | |
684 | if (status == 200) | |
685 | goto out; | |
686 | else if (status == 404) { | |
687 | /* Object doesn't exist. Pretend we read | |
688 | * zeroes */ | |
689 | memset(io_u->xfer_buf, 0, io_u->xfer_buflen); | |
690 | goto out; | |
691 | } | |
692 | log_err("DDIR_READ failed with HTTP status code %ld\n", status); | |
693 | } | |
694 | goto err; | |
695 | } else if (io_u->ddir == DDIR_TRIM) { | |
696 | curl_easy_setopt(http->curl, CURLOPT_HTTPGET, 1L); | |
697 | curl_easy_setopt(http->curl, CURLOPT_CUSTOMREQUEST, "DELETE"); | |
bd915a6b | 698 | curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0); |
c2f6a13d LMB |
699 | curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL); |
700 | curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, NULL); | |
701 | res = curl_easy_perform(http->curl); | |
702 | if (res == CURLE_OK) { | |
703 | curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status); | |
704 | if (status == 200 || status == 202 || status == 204 || status == 404) | |
705 | goto out; | |
706 | log_err("DDIR_TRIM failed with HTTP status code %ld\n", status); | |
707 | } | |
708 | goto err; | |
709 | } | |
710 | ||
711 | log_err("WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!\n"); | |
712 | ||
713 | err: | |
c77fe685 | 714 | io_u->error = EIO; |
c2f6a13d LMB |
715 | td_verror(td, io_u->error, "transfer"); |
716 | out: | |
717 | curl_slist_free_all(slist); | |
718 | return FIO_Q_COMPLETED; | |
719 | } | |
720 | ||
721 | static struct io_u *fio_http_event(struct thread_data *td, int event) | |
722 | { | |
723 | /* sync IO engine - never any outstanding events */ | |
724 | return NULL; | |
725 | } | |
726 | ||
727 | int fio_http_getevents(struct thread_data *td, unsigned int min, | |
728 | unsigned int max, const struct timespec *t) | |
729 | { | |
730 | /* sync IO engine - never any outstanding events */ | |
731 | return 0; | |
732 | } | |
733 | ||
734 | static int fio_http_setup(struct thread_data *td) | |
735 | { | |
736 | struct http_data *http = NULL; | |
737 | struct http_options *o = td->eo; | |
c65f3435 | 738 | |
c2f6a13d LMB |
739 | /* allocate engine specific structure to deal with libhttp. */ |
740 | http = calloc(1, sizeof(*http)); | |
741 | if (!http) { | |
742 | log_err("calloc failed.\n"); | |
743 | goto cleanup; | |
744 | } | |
745 | ||
746 | http->curl = curl_easy_init(); | |
747 | if (o->verbose) | |
748 | curl_easy_setopt(http->curl, CURLOPT_VERBOSE, 1L); | |
749 | if (o->verbose > 1) | |
750 | curl_easy_setopt(http->curl, CURLOPT_DEBUGFUNCTION, &_curl_trace); | |
751 | curl_easy_setopt(http->curl, CURLOPT_NOPROGRESS, 1L); | |
752 | curl_easy_setopt(http->curl, CURLOPT_FOLLOWLOCATION, 1L); | |
753 | curl_easy_setopt(http->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); | |
09fd2966 LMB |
754 | if (o->https == FIO_HTTPS_INSECURE) { |
755 | curl_easy_setopt(http->curl, CURLOPT_SSL_VERIFYPEER, 0L); | |
756 | curl_easy_setopt(http->curl, CURLOPT_SSL_VERIFYHOST, 0L); | |
757 | } | |
c2f6a13d LMB |
758 | curl_easy_setopt(http->curl, CURLOPT_READFUNCTION, _http_read); |
759 | curl_easy_setopt(http->curl, CURLOPT_WRITEFUNCTION, _http_write); | |
bd915a6b | 760 | curl_easy_setopt(http->curl, CURLOPT_SEEKFUNCTION, &_http_seek); |
c2f6a13d LMB |
761 | if (o->user && o->pass) { |
762 | curl_easy_setopt(http->curl, CURLOPT_USERNAME, o->user); | |
763 | curl_easy_setopt(http->curl, CURLOPT_PASSWORD, o->pass); | |
764 | curl_easy_setopt(http->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); | |
765 | } | |
766 | ||
767 | td->io_ops_data = http; | |
768 | ||
769 | /* Force single process mode. */ | |
770 | td->o.use_thread = 1; | |
771 | ||
772 | return 0; | |
773 | cleanup: | |
774 | fio_http_cleanup(td); | |
c65f3435 | 775 | return 1; |
c2f6a13d LMB |
776 | } |
777 | ||
778 | static int fio_http_open(struct thread_data *td, struct fio_file *f) | |
779 | { | |
780 | return 0; | |
781 | } | |
782 | static int fio_http_invalidate(struct thread_data *td, struct fio_file *f) | |
783 | { | |
784 | return 0; | |
785 | } | |
786 | ||
5a8a6a03 | 787 | FIO_STATIC struct ioengine_ops ioengine = { |
c2f6a13d LMB |
788 | .name = "http", |
789 | .version = FIO_IOOPS_VERSION, | |
f32a30d4 | 790 | .flags = FIO_DISKLESSIO | FIO_SYNCIO, |
c2f6a13d LMB |
791 | .setup = fio_http_setup, |
792 | .queue = fio_http_queue, | |
793 | .getevents = fio_http_getevents, | |
794 | .event = fio_http_event, | |
795 | .cleanup = fio_http_cleanup, | |
796 | .open_file = fio_http_open, | |
797 | .invalidate = fio_http_invalidate, | |
798 | .options = options, | |
799 | .option_struct_size = sizeof(struct http_options), | |
800 | }; | |
801 | ||
802 | static void fio_init fio_http_register(void) | |
803 | { | |
804 | register_ioengine(&ioengine); | |
805 | } | |
806 | ||
807 | static void fio_exit fio_http_unregister(void) | |
808 | { | |
809 | unregister_ioengine(&ioengine); | |
810 | } |