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;
644 fio_ro_check(td, io_u);
645 memset(&_curl_stream, 0, sizeof(_curl_stream));
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);
651 snprintf(url, sizeof(url), "https://%s%s", o->host, object);
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);
658 if (o->mode == FIO_HTTP_S3)
659 _add_aws_auth_header(http->curl, slist, o, io_u->ddir, object,
660 io_u->xfer_buf, io_u->xfer_buflen);
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);
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))
674 log_err("DDIR_WRITE failed with HTTP status code %ld\n", status);
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);
686 else if (status == 404) {
687 /* Object doesn't exist. Pretend we read
689 memset(io_u->xfer_buf, 0, io_u->xfer_buflen);
692 log_err("DDIR_READ failed with HTTP status code %ld\n", status);
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");
698 curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)0);
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)
706 log_err("DDIR_TRIM failed with HTTP status code %ld\n", status);
711 log_err("WARNING: Only DDIR_READ/DDIR_WRITE/DDIR_TRIM are supported!\n");
715 td_verror(td, io_u->error, "transfer");
717 curl_slist_free_all(slist);
718 return FIO_Q_COMPLETED;
721 static struct io_u *fio_http_event(struct thread_data *td, int event)
723 /* sync IO engine - never any outstanding events */
727 int fio_http_getevents(struct thread_data *td, unsigned int min,
728 unsigned int max, const struct timespec *t)
730 /* sync IO engine - never any outstanding events */
734 static int fio_http_setup(struct thread_data *td)
736 struct http_data *http = NULL;
737 struct http_options *o = td->eo;
739 /* allocate engine specific structure to deal with libhttp. */
740 http = calloc(1, sizeof(*http));
742 log_err("calloc failed.\n");
746 http->curl = curl_easy_init();
748 curl_easy_setopt(http->curl, CURLOPT_VERBOSE, 1L);
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);
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);
758 curl_easy_setopt(http->curl, CURLOPT_READFUNCTION, _http_read);
759 curl_easy_setopt(http->curl, CURLOPT_WRITEFUNCTION, _http_write);
760 curl_easy_setopt(http->curl, CURLOPT_SEEKFUNCTION, &_http_seek);
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);
767 td->io_ops_data = http;
769 /* Force single process mode. */
770 td->o.use_thread = 1;
774 fio_http_cleanup(td);
778 static int fio_http_open(struct thread_data *td, struct fio_file *f)
782 static int fio_http_invalidate(struct thread_data *td, struct fio_file *f)
787 FIO_STATIC struct ioengine_ops ioengine = {
789 .version = FIO_IOOPS_VERSION,
790 .flags = FIO_DISKLESSIO | FIO_SYNCIO,
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,
799 .option_struct_size = sizeof(struct http_options),
802 static void fio_init fio_http_register(void)
804 register_ioengine(&ioengine);
807 static void fio_exit fio_http_unregister(void)
809 unregister_ioengine(&ioengine);