Merge branch 'wip-http-swift' of https://github.com/l-mb/fio
[fio.git] / engines / http.c
CommitLineData
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
32
09fd2966
LMB
33enum {
34 FIO_HTTP_WEBDAV = 0,
35 FIO_HTTP_S3 = 1,
36 FIO_HTTP_SWIFT = 2,
37
38 FIO_HTTPS_OFF = 0,
39 FIO_HTTPS_ON = 1,
40 FIO_HTTPS_INSECURE = 2,
41};
42
c2f6a13d
LMB
43struct http_data {
44 CURL *curl;
45};
46
47struct http_options {
48 void *pad;
09fd2966 49 unsigned int https;
c2f6a13d
LMB
50 char *host;
51 char *user;
52 char *pass;
53 char *s3_key;
54 char *s3_keyid;
55 char *s3_region;
09fd2966 56 char *swift_auth_token;
c2f6a13d 57 int verbose;
09fd2966 58 unsigned int mode;
c2f6a13d
LMB
59};
60
61struct http_curl_stream {
62 char *buf;
63 size_t pos;
64 size_t max;
65};
66
67static struct fio_option options[] = {
68 {
69 .name = "https",
70 .lname = "https",
09fd2966 71 .type = FIO_OPT_STR,
c2f6a13d
LMB
72 .help = "Enable https",
73 .off1 = offsetof(struct http_options, https),
09fd2966
LMB
74 .def = "off",
75 .posval = {
76 { .ival = "off",
77 .oval = FIO_HTTPS_OFF,
78 .help = "No HTTPS",
79 },
80 { .ival = "on",
81 .oval = FIO_HTTPS_ON,
82 .help = "Enable HTTPS",
83 },
84 { .ival = "insecure",
85 .oval = FIO_HTTPS_INSECURE,
86 .help = "Enable HTTPS, disable peer verification",
87 },
88 },
c2f6a13d
LMB
89 .category = FIO_OPT_C_ENGINE,
90 .group = FIO_OPT_G_HTTP,
91 },
92 {
93 .name = "http_host",
94 .lname = "http_host",
95 .type = FIO_OPT_STR_STORE,
96 .help = "Hostname (S3 bucket)",
97 .off1 = offsetof(struct http_options, host),
98 .def = "localhost",
99 .category = FIO_OPT_C_ENGINE,
100 .group = FIO_OPT_G_HTTP,
101 },
102 {
103 .name = "http_user",
104 .lname = "http_user",
105 .type = FIO_OPT_STR_STORE,
106 .help = "HTTP user name",
107 .off1 = offsetof(struct http_options, user),
108 .category = FIO_OPT_C_ENGINE,
109 .group = FIO_OPT_G_HTTP,
110 },
111 {
112 .name = "http_pass",
113 .lname = "http_pass",
114 .type = FIO_OPT_STR_STORE,
115 .help = "HTTP password",
116 .off1 = offsetof(struct http_options, pass),
117 .category = FIO_OPT_C_ENGINE,
118 .group = FIO_OPT_G_HTTP,
119 },
120 {
121 .name = "http_s3_key",
122 .lname = "S3 secret key",
123 .type = FIO_OPT_STR_STORE,
124 .help = "S3 secret key",
125 .off1 = offsetof(struct http_options, s3_key),
126 .def = "",
127 .category = FIO_OPT_C_ENGINE,
128 .group = FIO_OPT_G_HTTP,
129 },
130 {
131 .name = "http_s3_keyid",
132 .lname = "S3 key id",
133 .type = FIO_OPT_STR_STORE,
134 .help = "S3 key id",
135 .off1 = offsetof(struct http_options, s3_keyid),
136 .def = "",
137 .category = FIO_OPT_C_ENGINE,
138 .group = FIO_OPT_G_HTTP,
139 },
09fd2966
LMB
140 {
141 .name = "http_swift_auth_token",
142 .lname = "Swift auth token",
143 .type = FIO_OPT_STR_STORE,
144 .help = "OpenStack Swift auth token",
145 .off1 = offsetof(struct http_options, swift_auth_token),
146 .def = "",
147 .category = FIO_OPT_C_ENGINE,
148 .group = FIO_OPT_G_HTTP,
149 },
c2f6a13d
LMB
150 {
151 .name = "http_s3_region",
152 .lname = "S3 region",
153 .type = FIO_OPT_STR_STORE,
154 .help = "S3 region",
155 .off1 = offsetof(struct http_options, s3_region),
156 .def = "us-east-1",
157 .category = FIO_OPT_C_ENGINE,
158 .group = FIO_OPT_G_HTTP,
159 },
160 {
09fd2966
LMB
161 .name = "http_mode",
162 .lname = "Request mode to use",
163 .type = FIO_OPT_STR,
164 .help = "Whether to use WebDAV, Swift, or S3",
165 .off1 = offsetof(struct http_options, mode),
166 .def = "webdav",
167 .posval = {
168 { .ival = "webdav",
169 .oval = FIO_HTTP_WEBDAV,
170 .help = "WebDAV server",
171 },
172 { .ival = "s3",
173 .oval = FIO_HTTP_S3,
174 .help = "S3 storage backend",
175 },
176 { .ival = "swift",
177 .oval = FIO_HTTP_SWIFT,
178 .help = "OpenStack Swift storage",
179 },
180 },
c2f6a13d
LMB
181 .category = FIO_OPT_C_ENGINE,
182 .group = FIO_OPT_G_HTTP,
183 },
184 {
185 .name = "http_verbose",
09fd2966 186 .lname = "HTTP verbosity level",
c2f6a13d
LMB
187 .type = FIO_OPT_INT,
188 .help = "increase http engine verbosity",
189 .off1 = offsetof(struct http_options, verbose),
190 .def = "0",
191 .category = FIO_OPT_C_ENGINE,
192 .group = FIO_OPT_G_HTTP,
193 },
194 {
195 .name = NULL,
196 },
197};
198
199static char *_aws_uriencode(const char *uri)
200{
201 size_t bufsize = 1024;
202 char *r = malloc(bufsize);
203 char c;
204 int i, n;
205 const char *hex = "0123456789ABCDEF";
206
207 if (!r) {
208 log_err("malloc failed\n");
209 return NULL;
210 }
211
212 n = 0;
213 for (i = 0; (c = uri[i]); i++) {
214 if (n > bufsize-5) {
215 log_err("encoding the URL failed\n");
216 return NULL;
217 }
218
219 if ( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
220 || (c >= '0' && c <= '9') || c == '_' || c == '-'
221 || c == '~' || c == '.' || c == '/')
222 r[n++] = c;
223 else {
224 r[n++] = '%';
225 r[n++] = hex[(c >> 4 ) & 0xF];
226 r[n++] = hex[c & 0xF];
227 }
228 }
229 r[n++] = 0;
230 return r;
231}
232
233static char *_conv_hex(const unsigned char *p, size_t len)
234{
235 char *r;
236 int i,n;
237 const char *hex = "0123456789abcdef";
238 r = malloc(len * 2 + 1);
239 n = 0;
240 for (i = 0; i < len; i++) {
241 r[n++] = hex[(p[i] >> 4 ) & 0xF];
242 r[n++] = hex[p[i] & 0xF];
243 }
244 r[n] = 0;
245
246 return r;
247}
248
249static char *_gen_hex_sha256(const char *p, size_t len)
250{
251 unsigned char hash[SHA256_DIGEST_LENGTH];
252
253 SHA256((unsigned char*)p, len, hash);
254 return _conv_hex(hash, SHA256_DIGEST_LENGTH);
255}
256
09fd2966
LMB
257static char *_gen_hex_md5(const char *p, size_t len)
258{
259 unsigned char hash[MD5_DIGEST_LENGTH];
260
261 MD5((unsigned char*)p, len, hash);
262 return _conv_hex(hash, MD5_DIGEST_LENGTH);
263}
264
c2f6a13d 265static void _hmac(unsigned char *md, void *key, int key_len, char *data) {
b61a5f46
DD
266#ifndef CONFIG_HAVE_OPAQUE_HMAC_CTX
267 HMAC_CTX _ctx;
268#endif
c2f6a13d
LMB
269 HMAC_CTX *ctx;
270 unsigned int hmac_len;
271
b61a5f46 272#ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX
c2f6a13d 273 ctx = HMAC_CTX_new();
b61a5f46
DD
274#else
275 ctx = &_ctx;
276#endif
c2f6a13d
LMB
277 HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), NULL);
278 HMAC_Update(ctx, (unsigned char*)data, strlen(data));
279 HMAC_Final(ctx, md, &hmac_len);
b61a5f46 280#ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX
c2f6a13d 281 HMAC_CTX_free(ctx);
b61a5f46
DD
282#else
283 HMAC_CTX_cleanup(ctx);
284#endif
c2f6a13d
LMB
285}
286
287static int _curl_trace(CURL *handle, curl_infotype type,
288 char *data, size_t size,
289 void *userp)
290{
291 const char *text;
292 (void)handle; /* prevent compiler warning */
293 (void)userp;
294
295 switch (type) {
296 case CURLINFO_TEXT:
297 fprintf(stderr, "== Info: %s", data);
298 default:
299 case CURLINFO_SSL_DATA_OUT:
300 case CURLINFO_SSL_DATA_IN:
301 return 0;
302
303 case CURLINFO_HEADER_OUT:
304 text = "=> Send header";
305 break;
306 case CURLINFO_DATA_OUT:
307 text = "=> Send data";
308 break;
309 case CURLINFO_HEADER_IN:
310 text = "<= Recv header";
311 break;
312 case CURLINFO_DATA_IN:
313 text = "<= Recv data";
314 break;
315 }
316
317 log_info("%s: %s", text, data);
318 return 0;
319}
320
321/* https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
322 * https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro
323 */
324static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct http_options *o,
325 int op, const char *uri, char *buf, size_t len)
326{
327 char date_short[16];
328 char date_iso[32];
329 char method[8];
330 char dkey[128];
331 char creq[512];
332 char sts[256];
333 char s[512];
334 char *uri_encoded = NULL;
335 char *dsha = NULL;
336 char *csha = NULL;
337 char *signature = NULL;
338 const char *service = "s3";
339 const char *aws = "aws4_request";
340 unsigned char md[SHA256_DIGEST_LENGTH];
341
342 time_t t = time(NULL);
343 struct tm *gtm = gmtime(&t);
344
345 strftime (date_short, sizeof(date_short), "%Y%m%d", gtm);
346 strftime (date_iso, sizeof(date_iso), "%Y%m%dT%H%M%SZ", gtm);
347 uri_encoded = _aws_uriencode(uri);
348
349 if (op == DDIR_WRITE) {
350 dsha = _gen_hex_sha256(buf, len);
351 sprintf(method, "PUT");
352 } else {
353 /* DDIR_READ && DDIR_TRIM supply an empty body */
354 if (op == DDIR_READ)
355 sprintf(method, "GET");
356 else
357 sprintf(method, "DELETE");
358 dsha = _gen_hex_sha256("", 0);
359 }
360
361 /* Create the canonical request first */
362 snprintf(creq, sizeof(creq),
363 "%s\n"
364 "%s\n"
365 "\n"
366 "host:%s\n"
367 "x-amz-content-sha256:%s\n"
368 "x-amz-date:%s\n"
369 "\n"
370 "host;x-amz-content-sha256;x-amz-date\n"
371 "%s"
372 , method
373 , uri_encoded, o->host, dsha, date_iso, dsha);
374
375 csha = _gen_hex_sha256(creq, strlen(creq));
376 snprintf(sts, sizeof(sts), "AWS4-HMAC-SHA256\n%s\n%s/%s/%s/%s\n%s",
377 date_iso, date_short, o->s3_region, service, aws, csha);
378
379 snprintf((char *)dkey, sizeof(dkey), "AWS4%s", o->s3_key);
380 _hmac(md, dkey, strlen(dkey), date_short);
381 _hmac(md, md, SHA256_DIGEST_LENGTH, o->s3_region);
382 _hmac(md, md, SHA256_DIGEST_LENGTH, (char*) service);
383 _hmac(md, md, SHA256_DIGEST_LENGTH, (char*) aws);
384 _hmac(md, md, SHA256_DIGEST_LENGTH, sts);
385
386 signature = _conv_hex(md, SHA256_DIGEST_LENGTH);
387
388 /* Surpress automatic Accept: header */
389 slist = curl_slist_append(slist, "Accept:");
390
391 snprintf(s, sizeof(s), "x-amz-content-sha256: %s", dsha);
392 slist = curl_slist_append(slist, s);
393
394 snprintf(s, sizeof(s), "x-amz-date: %s", date_iso);
395 slist = curl_slist_append(slist, s);
396
397 snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request,"
398 "SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature=%s",
399 o->s3_keyid, date_short, o->s3_region, signature);
400 slist = curl_slist_append(slist, s);
401
402 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
403
404 free(uri_encoded);
405 free(csha);
406 free(dsha);
407 free(signature);
408}
409
09fd2966
LMB
410static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_options *o,
411 int op, const char *uri, char *buf, size_t len)
412{
413 char *dsha = NULL;
414 char s[512];
415
416 if (op == DDIR_WRITE) {
417 dsha = _gen_hex_md5(buf, len);
418 }
419 /* Surpress automatic Accept: header */
420 slist = curl_slist_append(slist, "Accept:");
421
422 snprintf(s, sizeof(s), "etag: %s", dsha);
423 slist = curl_slist_append(slist, s);
424
425 snprintf(s, sizeof(s), "x-auth-token: %s", o->swift_auth_token);
426 slist = curl_slist_append(slist, s);
427
428 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
429
430 free(dsha);
431}
432
c2f6a13d
LMB
433static void fio_http_cleanup(struct thread_data *td)
434{
435 struct http_data *http = td->io_ops_data;
436
437 if (http) {
438 curl_easy_cleanup(http->curl);
439 free(http);
440 }
441}
442
443static size_t _http_read(void *ptr, size_t size, size_t nmemb, void *stream)
444{
445 struct http_curl_stream *state = stream;
446 size_t len = size * nmemb;
447 /* We're retrieving; nothing is supposed to be read locally */
448 if (!stream)
449 return 0;
450 if (len+state->pos > state->max)
451 len = state->max - state->pos;
452 memcpy(ptr, &state->buf[state->pos], len);
453 state->pos += len;
454 return len;
455}
456
457static size_t _http_write(void *ptr, size_t size, size_t nmemb, void *stream)
458{
459 struct http_curl_stream *state = stream;
460 /* We're just discarding the returned body after a PUT */
461 if (!stream)
462 return nmemb;
463 if (size != 1)
464 return CURLE_WRITE_ERROR;
465 if (nmemb + state->pos > state->max)
466 return CURLE_WRITE_ERROR;
467 memcpy(&state->buf[state->pos], ptr, nmemb);
468 state->pos += nmemb;
469 return nmemb;
470}
471
472static int _http_seek(void *stream, curl_off_t offset, int origin)
473{
474 struct http_curl_stream *state = stream;
475 if (offset < state->max && origin == SEEK_SET) {
476 state->pos = offset;
477 return CURL_SEEKFUNC_OK;
478 } else
479 return CURL_SEEKFUNC_FAIL;
480}
481
482static enum fio_q_status fio_http_queue(struct thread_data *td,
483 struct io_u *io_u)
484{
485 struct http_data *http = td->io_ops_data;
486 struct http_options *o = td->eo;
487 struct http_curl_stream _curl_stream;
488 struct curl_slist *slist = NULL;
489 char object[512];
490 char url[1024];
491 long status;
492 CURLcode res;
493 int r = -1;
494
495 fio_ro_check(td, io_u);
496 memset(&_curl_stream, 0, sizeof(_curl_stream));
09fd2966
LMB
497 snprintf(object, sizeof(object), "%s_%llu_%llu", td->files[0]->file_name,
498 io_u->offset, io_u->xfer_buflen);
499 if (o->https == FIO_HTTPS_OFF)
500 snprintf(url, sizeof(url), "http://%s%s", o->host, object);
501 else
502 snprintf(url, sizeof(url), "https://%s%s", o->host, object);
c2f6a13d
LMB
503 curl_easy_setopt(http->curl, CURLOPT_URL, url);
504 _curl_stream.buf = io_u->xfer_buf;
505 _curl_stream.max = io_u->xfer_buflen;
506 curl_easy_setopt(http->curl, CURLOPT_SEEKDATA, &_curl_stream);
507 curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)io_u->xfer_buflen);
508
09fd2966 509 if (o->mode == FIO_HTTP_S3)
c2f6a13d
LMB
510 _add_aws_auth_header(http->curl, slist, o, io_u->ddir, object,
511 io_u->xfer_buf, io_u->xfer_buflen);
09fd2966
LMB
512 else if (o->mode == FIO_HTTP_SWIFT)
513 _add_swift_header(http->curl, slist, o, io_u->ddir, object,
514 io_u->xfer_buf, io_u->xfer_buflen);
c2f6a13d
LMB
515
516 if (io_u->ddir == DDIR_WRITE) {
517 curl_easy_setopt(http->curl, CURLOPT_READDATA, &_curl_stream);
518 curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, NULL);
519 curl_easy_setopt(http->curl, CURLOPT_UPLOAD, 1L);
520 res = curl_easy_perform(http->curl);
521 if (res == CURLE_OK) {
522 curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
523 if (status == 100 || (status >= 200 && status <= 204))
524 goto out;
525 log_err("DDIR_WRITE failed with HTTP status code %ld\n", status);
526 goto err;
527 }
528 } else if (io_u->ddir == DDIR_READ) {
529 curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL);
530 curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, &_curl_stream);
531 curl_easy_setopt(http->curl, CURLOPT_HTTPGET, 1L);
532 res = curl_easy_perform(http->curl);
533 if (res == CURLE_OK) {
534 curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
535 if (status == 200)
536 goto out;
537 else if (status == 404) {
538 /* Object doesn't exist. Pretend we read
539 * zeroes */
540 memset(io_u->xfer_buf, 0, io_u->xfer_buflen);
541 goto out;
542 }
543 log_err("DDIR_READ failed with HTTP status code %ld\n", status);
544 }
545 goto err;
546 } else if (io_u->ddir == DDIR_TRIM) {
547 curl_easy_setopt(http->curl, CURLOPT_HTTPGET, 1L);
548 curl_easy_setopt(http->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
549 curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, 0);
550 curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL);
551 curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, NULL);
552 res = curl_easy_perform(http->curl);
553 if (res == CURLE_OK) {
554 curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
555 if (status == 200 || status == 202 || status == 204 || status == 404)
556 goto out;
557 log_err("DDIR_TRIM failed with HTTP status code %ld\n", status);
558 }
559 goto err;
560 }
561
562 log_err("WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!\n");
563
564err:
565 io_u->error = r;
566 td_verror(td, io_u->error, "transfer");
567out:
568 curl_slist_free_all(slist);
569 return FIO_Q_COMPLETED;
570}
571
572static struct io_u *fio_http_event(struct thread_data *td, int event)
573{
574 /* sync IO engine - never any outstanding events */
575 return NULL;
576}
577
578int fio_http_getevents(struct thread_data *td, unsigned int min,
579 unsigned int max, const struct timespec *t)
580{
581 /* sync IO engine - never any outstanding events */
582 return 0;
583}
584
585static int fio_http_setup(struct thread_data *td)
586{
587 struct http_data *http = NULL;
588 struct http_options *o = td->eo;
c65f3435 589
c2f6a13d
LMB
590 /* allocate engine specific structure to deal with libhttp. */
591 http = calloc(1, sizeof(*http));
592 if (!http) {
593 log_err("calloc failed.\n");
594 goto cleanup;
595 }
596
597 http->curl = curl_easy_init();
598 if (o->verbose)
599 curl_easy_setopt(http->curl, CURLOPT_VERBOSE, 1L);
600 if (o->verbose > 1)
601 curl_easy_setopt(http->curl, CURLOPT_DEBUGFUNCTION, &_curl_trace);
602 curl_easy_setopt(http->curl, CURLOPT_NOPROGRESS, 1L);
603 curl_easy_setopt(http->curl, CURLOPT_FOLLOWLOCATION, 1L);
604 curl_easy_setopt(http->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
09fd2966
LMB
605 if (o->https == FIO_HTTPS_INSECURE) {
606 curl_easy_setopt(http->curl, CURLOPT_SSL_VERIFYPEER, 0L);
607 curl_easy_setopt(http->curl, CURLOPT_SSL_VERIFYHOST, 0L);
608 }
c2f6a13d
LMB
609 curl_easy_setopt(http->curl, CURLOPT_READFUNCTION, _http_read);
610 curl_easy_setopt(http->curl, CURLOPT_WRITEFUNCTION, _http_write);
611 curl_easy_setopt(http->curl, CURLOPT_SEEKFUNCTION, _http_seek);
612 if (o->user && o->pass) {
613 curl_easy_setopt(http->curl, CURLOPT_USERNAME, o->user);
614 curl_easy_setopt(http->curl, CURLOPT_PASSWORD, o->pass);
615 curl_easy_setopt(http->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
616 }
617
618 td->io_ops_data = http;
619
620 /* Force single process mode. */
621 td->o.use_thread = 1;
622
623 return 0;
624cleanup:
625 fio_http_cleanup(td);
c65f3435 626 return 1;
c2f6a13d
LMB
627}
628
629static int fio_http_open(struct thread_data *td, struct fio_file *f)
630{
631 return 0;
632}
633static int fio_http_invalidate(struct thread_data *td, struct fio_file *f)
634{
635 return 0;
636}
637
638static struct ioengine_ops ioengine = {
639 .name = "http",
640 .version = FIO_IOOPS_VERSION,
641 .flags = FIO_DISKLESSIO,
642 .setup = fio_http_setup,
643 .queue = fio_http_queue,
644 .getevents = fio_http_getevents,
645 .event = fio_http_event,
646 .cleanup = fio_http_cleanup,
647 .open_file = fio_http_open,
648 .invalidate = fio_http_invalidate,
649 .options = options,
650 .option_struct_size = sizeof(struct http_options),
651};
652
653static void fio_init fio_http_register(void)
654{
655 register_ioengine(&ioengine);
656}
657
658static void fio_exit fio_http_unregister(void)
659{
660 unregister_ioengine(&ioengine);
661}