2 * HTTP GET/PUT IO engine
4 * IO engine to perform HTTP(S) GET/PUT requests via libcurl-easy.
6 * Copyright (C) 2018 SUSE LLC
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..
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.
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.
25 #include <curl/curl.h>
26 #include <openssl/hmac.h>
27 #include <openssl/sha.h>
28 #include <openssl/md5.h>
30 #include "../optgroup.h"
33 * Silence OpenSSL 3.0 deprecated function warnings
35 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
44 FIO_HTTPS_INSECURE = 2,
60 char *s3_sse_customer_key;
61 char *s3_sse_customer_algorithm;
62 char *s3_storage_class;
63 char *swift_auth_token;
68 struct http_curl_stream {
74 static struct fio_option options[] = {
79 .help = "Enable https",
80 .off1 = offsetof(struct http_options, https),
84 .oval = FIO_HTTPS_OFF,
89 .help = "Enable HTTPS",
92 .oval = FIO_HTTPS_INSECURE,
93 .help = "Enable HTTPS, disable peer verification",
96 .category = FIO_OPT_C_ENGINE,
97 .group = FIO_OPT_G_HTTP,
101 .lname = "http_host",
102 .type = FIO_OPT_STR_STORE,
103 .help = "Hostname (S3 bucket)",
104 .off1 = offsetof(struct http_options, host),
106 .category = FIO_OPT_C_ENGINE,
107 .group = FIO_OPT_G_HTTP,
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,
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,
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),
134 .category = FIO_OPT_C_ENGINE,
135 .group = FIO_OPT_G_HTTP,
138 .name = "http_s3_keyid",
139 .lname = "S3 key id",
140 .type = FIO_OPT_STR_STORE,
142 .off1 = offsetof(struct http_options, s3_keyid),
144 .category = FIO_OPT_C_ENGINE,
145 .group = FIO_OPT_G_HTTP,
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),
154 .category = FIO_OPT_C_ENGINE,
155 .group = FIO_OPT_G_HTTP,
158 .name = "http_s3_region",
159 .lname = "S3 region",
160 .type = FIO_OPT_STR_STORE,
162 .off1 = offsetof(struct http_options, s3_region),
164 .category = FIO_OPT_C_ENGINE,
165 .group = FIO_OPT_G_HTTP,
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),
174 .category = FIO_OPT_C_ENGINE,
175 .group = FIO_OPT_G_HTTP,
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),
184 .category = FIO_OPT_C_ENGINE,
185 .group = FIO_OPT_G_HTTP,
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),
194 .category = FIO_OPT_C_ENGINE,
195 .group = FIO_OPT_G_HTTP,
199 .lname = "Request mode to use",
201 .help = "Whether to use WebDAV, Swift, or S3",
202 .off1 = offsetof(struct http_options, mode),
206 .oval = FIO_HTTP_WEBDAV,
207 .help = "WebDAV server",
211 .help = "S3 storage backend",
214 .oval = FIO_HTTP_SWIFT,
215 .help = "OpenStack Swift storage",
218 .category = FIO_OPT_C_ENGINE,
219 .group = FIO_OPT_G_HTTP,
222 .name = "http_verbose",
223 .lname = "HTTP verbosity level",
225 .help = "increase http engine verbosity",
226 .off1 = offsetof(struct http_options, verbose),
228 .category = FIO_OPT_C_ENGINE,
229 .group = FIO_OPT_G_HTTP,
236 static char *_aws_uriencode(const char *uri)
238 size_t bufsize = 1024;
239 char *r = malloc(bufsize);
242 const char *hex = "0123456789ABCDEF";
245 log_err("malloc failed\n");
250 for (i = 0; (c = uri[i]); i++) {
252 log_err("encoding the URL failed\n");
257 if ( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
258 || (c >= '0' && c <= '9') || c == '_' || c == '-'
259 || c == '~' || c == '.' || c == '/')
263 r[n++] = hex[(c >> 4 ) & 0xF];
264 r[n++] = hex[c & 0xF];
271 static char *_conv_hex(const unsigned char *p, size_t len)
275 const char *hex = "0123456789abcdef";
276 r = malloc(len * 2 + 1);
278 for (i = 0; i < len; i++) {
279 r[n++] = hex[(p[i] >> 4 ) & 0xF];
280 r[n++] = hex[p[i] & 0xF];
287 static char *_gen_hex_sha256(const char *p, size_t len)
289 unsigned char hash[SHA256_DIGEST_LENGTH];
291 SHA256((unsigned char*)p, len, hash);
292 return _conv_hex(hash, SHA256_DIGEST_LENGTH);
295 static char *_gen_hex_md5(const char *p, size_t len)
297 unsigned char hash[MD5_DIGEST_LENGTH];
299 MD5((unsigned char*)p, len, hash);
300 return _conv_hex(hash, MD5_DIGEST_LENGTH);
303 static char *_conv_base64_encode(const unsigned char *p, size_t len)
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', '+', '/'
318 size_t out_len = 4 * ((len + 2) / 3);
319 ret = r = malloc(out_len + 1);
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];
329 *r++ = sEncodingTable[(p[i] >> 2) & 0x3F];
330 if (i == (len - 1)) {
331 *r++ = sEncodingTable[((p[i] & 0x3) << 4)];
334 *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)];
335 *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2)];
344 static char *_gen_base64_md5(const unsigned char *p, size_t len)
346 unsigned char hash[MD5_DIGEST_LENGTH];
347 MD5((unsigned char*)p, len, hash);
348 return _conv_base64_encode(hash, MD5_DIGEST_LENGTH);
351 static void _hmac(unsigned char *md, void *key, int key_len, char *data) {
352 #ifndef CONFIG_HAVE_OPAQUE_HMAC_CTX
356 unsigned int hmac_len;
358 #ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX
359 ctx = HMAC_CTX_new();
362 /* work-around crash in certain versions of libssl */
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);
368 #ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX
371 HMAC_CTX_cleanup(ctx);
375 static int _curl_trace(CURL *handle, curl_infotype type,
376 char *data, size_t size,
380 (void)handle; /* prevent compiler warning */
385 fprintf(stderr, "== Info: %s", data);
388 case CURLINFO_SSL_DATA_OUT:
389 case CURLINFO_SSL_DATA_IN:
392 case CURLINFO_HEADER_OUT:
393 text = "=> Send header";
395 case CURLINFO_DATA_OUT:
396 text = "=> Send data";
398 case CURLINFO_HEADER_IN:
399 text = "<= Recv header";
401 case CURLINFO_DATA_IN:
402 text = "<= Recv data";
406 log_info("%s: %s", text, data);
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
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)
423 char *uri_encoded = NULL;
426 char *signature = NULL;
427 const char *service = "s3";
428 const char *aws = "aws4_request";
429 unsigned char md[SHA256_DIGEST_LENGTH];
430 unsigned char sse_key[33] = {0};
431 char *sse_key_base64 = NULL;
432 char *sse_key_md5_base64 = NULL;
434 time_t t = time(NULL);
435 struct tm *gtm = gmtime(&t);
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);
441 if (o->s3_sse_customer_key != NULL)
442 strncpy((char*)sse_key, o->s3_sse_customer_key, sizeof(sse_key) - 1);
444 if (op == DDIR_WRITE) {
445 dsha = _gen_hex_sha256(buf, len);
446 sprintf(method, "PUT");
448 /* DDIR_READ && DDIR_TRIM supply an empty body */
450 sprintf(method, "GET");
452 sprintf(method, "DELETE");
453 dsha = _gen_hex_sha256("", 0);
456 /* Create the canonical request first */
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),
465 "x-amz-content-sha256:%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"
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"
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);
483 snprintf(creq, sizeof(creq),
488 "x-amz-content-sha256:%s\n"
490 "x-amz-storage-class:%s\n"
492 "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n"
495 , uri_encoded, o->host, dsha, date_iso, o->s3_storage_class, dsha);
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",
500 date_iso, date_short, o->s3_region, service, aws, csha);
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);
509 signature = _conv_hex(md, SHA256_DIGEST_LENGTH);
511 /* Suppress automatic Accept: header */
512 slist = curl_slist_append(slist, "Accept:");
514 snprintf(s, sizeof(s), "x-amz-content-sha256: %s", dsha);
515 slist = curl_slist_append(slist, s);
517 snprintf(s, sizeof(s), "x-amz-date: %s", date_iso);
518 slist = curl_slist_append(slist, s);
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);
529 snprintf(s, sizeof(s), "x-amz-storage-class: %s", o->s3_storage_class);
530 slist = curl_slist_append(slist, s);
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,"
540 o->s3_keyid, date_short, o->s3_region, signature);
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);
546 slist = curl_slist_append(slist, s);
548 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
554 if (sse_key_base64 != NULL) {
555 free(sse_key_base64);
556 free(sse_key_md5_base64);
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)
566 if (op == DDIR_WRITE) {
567 dsha = _gen_hex_md5(buf, len);
569 /* Suppress automatic Accept: header */
570 slist = curl_slist_append(slist, "Accept:");
572 snprintf(s, sizeof(s), "etag: %s", dsha);
573 slist = curl_slist_append(slist, s);
575 snprintf(s, sizeof(s), "x-auth-token: %s", o->swift_auth_token);
576 slist = curl_slist_append(slist, s);
578 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
583 static void fio_http_cleanup(struct thread_data *td)
585 struct http_data *http = td->io_ops_data;
588 curl_easy_cleanup(http->curl);
593 static size_t _http_read(void *ptr, size_t size, size_t nmemb, void *stream)
595 struct http_curl_stream *state = stream;
596 size_t len = size * nmemb;
597 /* We're retrieving; nothing is supposed to be read locally */
600 if (len+state->pos > state->max)
601 len = state->max - state->pos;
602 memcpy(ptr, &state->buf[state->pos], len);
607 static size_t _http_write(void *ptr, size_t size, size_t nmemb, void *stream)
609 struct http_curl_stream *state = stream;
610 /* We're just discarding the returned body after a PUT */
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);
622 static int _http_seek(void *stream, curl_off_t offset, int origin)
624 struct http_curl_stream *state = stream;
625 if (offset < state->max && origin == SEEK_SET) {
627 return CURL_SEEKFUNC_OK;
629 return CURL_SEEKFUNC_FAIL;
632 static enum fio_q_status fio_http_queue(struct thread_data *td,
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;
645 fio_ro_check(td, io_u);
646 memset(&_curl_stream, 0, sizeof(_curl_stream));
647 snprintf(object, sizeof(object), "%s_%llu_%llu", td->files[0]->file_name,
648 io_u->offset, io_u->xfer_buflen);
649 if (o->https == FIO_HTTPS_OFF)
650 snprintf(url, sizeof(url), "http://%s%s", o->host, object);
652 snprintf(url, sizeof(url), "https://%s%s", o->host, object);
653 curl_easy_setopt(http->curl, CURLOPT_URL, url);
654 _curl_stream.buf = io_u->xfer_buf;
655 _curl_stream.max = io_u->xfer_buflen;
656 curl_easy_setopt(http->curl, CURLOPT_SEEKDATA, &_curl_stream);
657 curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)io_u->xfer_buflen);
659 if (o->mode == FIO_HTTP_S3)
660 _add_aws_auth_header(http->curl, slist, o, io_u->ddir, object,
661 io_u->xfer_buf, io_u->xfer_buflen);
662 else if (o->mode == FIO_HTTP_SWIFT)
663 _add_swift_header(http->curl, slist, o, io_u->ddir, object,
664 io_u->xfer_buf, io_u->xfer_buflen);
666 if (io_u->ddir == DDIR_WRITE) {
667 curl_easy_setopt(http->curl, CURLOPT_READDATA, &_curl_stream);
668 curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, NULL);
669 curl_easy_setopt(http->curl, CURLOPT_UPLOAD, 1L);
670 res = curl_easy_perform(http->curl);
671 if (res == CURLE_OK) {
672 curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
673 if (status == 100 || (status >= 200 && status <= 204))
675 log_err("DDIR_WRITE failed with HTTP status code %ld\n", status);
678 } else if (io_u->ddir == DDIR_READ) {
679 curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL);
680 curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, &_curl_stream);
681 curl_easy_setopt(http->curl, CURLOPT_HTTPGET, 1L);
682 res = curl_easy_perform(http->curl);
683 if (res == CURLE_OK) {
684 curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
687 else if (status == 404) {
688 /* Object doesn't exist. Pretend we read
690 memset(io_u->xfer_buf, 0, io_u->xfer_buflen);
693 log_err("DDIR_READ failed with HTTP status code %ld\n", status);
696 } else if (io_u->ddir == DDIR_TRIM) {
697 curl_easy_setopt(http->curl, CURLOPT_HTTPGET, 1L);
698 curl_easy_setopt(http->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
699 curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0);
700 curl_easy_setopt(http->curl, CURLOPT_READDATA, NULL);
701 curl_easy_setopt(http->curl, CURLOPT_WRITEDATA, NULL);
702 res = curl_easy_perform(http->curl);
703 if (res == CURLE_OK) {
704 curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
705 if (status == 200 || status == 202 || status == 204 || status == 404)
707 log_err("DDIR_TRIM failed with HTTP status code %ld\n", status);
712 log_err("WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!\n");
716 td_verror(td, io_u->error, "transfer");
718 curl_slist_free_all(slist);
719 return FIO_Q_COMPLETED;
722 static struct io_u *fio_http_event(struct thread_data *td, int event)
724 /* sync IO engine - never any outstanding events */
728 int fio_http_getevents(struct thread_data *td, unsigned int min,
729 unsigned int max, const struct timespec *t)
731 /* sync IO engine - never any outstanding events */
735 static int fio_http_setup(struct thread_data *td)
737 struct http_data *http = NULL;
738 struct http_options *o = td->eo;
740 /* allocate engine specific structure to deal with libhttp. */
741 http = calloc(1, sizeof(*http));
743 log_err("calloc failed.\n");
747 http->curl = curl_easy_init();
749 curl_easy_setopt(http->curl, CURLOPT_VERBOSE, 1L);
751 curl_easy_setopt(http->curl, CURLOPT_DEBUGFUNCTION, &_curl_trace);
752 curl_easy_setopt(http->curl, CURLOPT_NOPROGRESS, 1L);
753 curl_easy_setopt(http->curl, CURLOPT_FOLLOWLOCATION, 1L);
754 curl_easy_setopt(http->curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
755 if (o->https == FIO_HTTPS_INSECURE) {
756 curl_easy_setopt(http->curl, CURLOPT_SSL_VERIFYPEER, 0L);
757 curl_easy_setopt(http->curl, CURLOPT_SSL_VERIFYHOST, 0L);
759 curl_easy_setopt(http->curl, CURLOPT_READFUNCTION, _http_read);
760 curl_easy_setopt(http->curl, CURLOPT_WRITEFUNCTION, _http_write);
761 curl_easy_setopt(http->curl, CURLOPT_SEEKFUNCTION, &_http_seek);
762 if (o->user && o->pass) {
763 curl_easy_setopt(http->curl, CURLOPT_USERNAME, o->user);
764 curl_easy_setopt(http->curl, CURLOPT_PASSWORD, o->pass);
765 curl_easy_setopt(http->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
768 td->io_ops_data = http;
770 /* Force single process mode. */
771 td->o.use_thread = 1;
775 fio_http_cleanup(td);
779 static int fio_http_open(struct thread_data *td, struct fio_file *f)
783 static int fio_http_invalidate(struct thread_data *td, struct fio_file *f)
788 FIO_STATIC struct ioengine_ops ioengine = {
790 .version = FIO_IOOPS_VERSION,
791 .flags = FIO_DISKLESSIO | FIO_SYNCIO,
792 .setup = fio_http_setup,
793 .queue = fio_http_queue,
794 .getevents = fio_http_getevents,
795 .event = fio_http_event,
796 .cleanup = fio_http_cleanup,
797 .open_file = fio_http_open,
798 .invalidate = fio_http_invalidate,
800 .option_struct_size = sizeof(struct http_options),
803 static void fio_init fio_http_register(void)
805 register_ioengine(&ioengine);
808 static void fio_exit fio_http_unregister(void)
810 unregister_ioengine(&ioengine);