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");
256 if ( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
257 || (c >= '0' && c <= '9') || c == '_' || c == '-'
258 || c == '~' || c == '.' || c == '/')
262 r[n++] = hex[(c >> 4 ) & 0xF];
263 r[n++] = hex[c & 0xF];
270 static char *_conv_hex(const unsigned char *p, size_t len)
274 const char *hex = "0123456789abcdef";
275 r = malloc(len * 2 + 1);
277 for (i = 0; i < len; i++) {
278 r[n++] = hex[(p[i] >> 4 ) & 0xF];
279 r[n++] = hex[p[i] & 0xF];
286 static char *_gen_hex_sha256(const char *p, size_t len)
288 unsigned char hash[SHA256_DIGEST_LENGTH];
290 SHA256((unsigned char*)p, len, hash);
291 return _conv_hex(hash, SHA256_DIGEST_LENGTH);
294 static char *_gen_hex_md5(const char *p, size_t len)
296 unsigned char hash[MD5_DIGEST_LENGTH];
298 MD5((unsigned char*)p, len, hash);
299 return _conv_hex(hash, MD5_DIGEST_LENGTH);
302 static char *_conv_base64_encode(const unsigned char *p, size_t len)
306 static const char sEncodingTable[] = {
307 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
308 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
309 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
310 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
311 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
312 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
313 'w', 'x', 'y', 'z', '0', '1', '2', '3',
314 '4', '5', '6', '7', '8', '9', '+', '/'
317 size_t out_len = 4 * ((len + 2) / 3);
318 ret = r = malloc(out_len + 1);
320 for (i = 0; i < len - 2; i += 3) {
321 *r++ = sEncodingTable[(p[i] >> 2) & 0x3F];
322 *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)];
323 *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2) | ((int) (p[i + 2] & 0xC0) >> 6)];
324 *r++ = sEncodingTable[p[i + 2] & 0x3F];
328 *r++ = sEncodingTable[(p[i] >> 2) & 0x3F];
329 if (i == (len - 1)) {
330 *r++ = sEncodingTable[((p[i] & 0x3) << 4)];
333 *r++ = sEncodingTable[((p[i] & 0x3) << 4) | ((int) (p[i + 1] & 0xF0) >> 4)];
334 *r++ = sEncodingTable[((p[i + 1] & 0xF) << 2)];
343 static char *_gen_base64_md5(const unsigned char *p, size_t len)
345 unsigned char hash[MD5_DIGEST_LENGTH];
346 MD5((unsigned char*)p, len, hash);
347 return _conv_base64_encode(hash, MD5_DIGEST_LENGTH);
350 static void _hmac(unsigned char *md, void *key, int key_len, char *data) {
351 #ifndef CONFIG_HAVE_OPAQUE_HMAC_CTX
355 unsigned int hmac_len;
357 #ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX
358 ctx = HMAC_CTX_new();
361 /* work-around crash in certain versions of libssl */
364 HMAC_Init_ex(ctx, key, key_len, EVP_sha256(), NULL);
365 HMAC_Update(ctx, (unsigned char*)data, strlen(data));
366 HMAC_Final(ctx, md, &hmac_len);
367 #ifdef CONFIG_HAVE_OPAQUE_HMAC_CTX
370 HMAC_CTX_cleanup(ctx);
374 static int _curl_trace(CURL *handle, curl_infotype type,
375 char *data, size_t size,
379 (void)handle; /* prevent compiler warning */
384 fprintf(stderr, "== Info: %s", data);
387 case CURLINFO_SSL_DATA_OUT:
388 case CURLINFO_SSL_DATA_IN:
391 case CURLINFO_HEADER_OUT:
392 text = "=> Send header";
394 case CURLINFO_DATA_OUT:
395 text = "=> Send data";
397 case CURLINFO_HEADER_IN:
398 text = "<= Recv header";
400 case CURLINFO_DATA_IN:
401 text = "<= Recv data";
405 log_info("%s: %s", text, data);
409 /* https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
410 * https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro
412 static void _add_aws_auth_header(CURL *curl, struct curl_slist *slist, struct http_options *o,
413 int op, const char *uri, char *buf, size_t len)
422 char *uri_encoded = NULL;
425 char *signature = NULL;
426 const char *service = "s3";
427 const char *aws = "aws4_request";
428 unsigned char md[SHA256_DIGEST_LENGTH];
429 unsigned char sse_key[33] = {0};
430 char *sse_key_base64 = NULL;
431 char *sse_key_md5_base64 = NULL;
433 time_t t = time(NULL);
434 struct tm *gtm = gmtime(&t);
436 strftime (date_short, sizeof(date_short), "%Y%m%d", gtm);
437 strftime (date_iso, sizeof(date_iso), "%Y%m%dT%H%M%SZ", gtm);
438 uri_encoded = _aws_uriencode(uri);
440 if (o->s3_sse_customer_key != NULL)
441 strncpy((char*)sse_key, o->s3_sse_customer_key, sizeof(sse_key) - 1);
443 if (op == DDIR_WRITE) {
444 dsha = _gen_hex_sha256(buf, len);
445 sprintf(method, "PUT");
447 /* DDIR_READ && DDIR_TRIM supply an empty body */
449 sprintf(method, "GET");
451 sprintf(method, "DELETE");
452 dsha = _gen_hex_sha256("", 0);
455 /* Create the canonical request first */
456 if (sse_key[0] != '\0') {
457 sse_key_base64 = _conv_base64_encode(sse_key, sizeof(sse_key) - 1);
458 sse_key_md5_base64 = _gen_base64_md5(sse_key, sizeof(sse_key) - 1);
459 snprintf(creq, sizeof(creq),
464 "x-amz-content-sha256:%s\n"
466 "x-amz-server-side-encryption-customer-algorithm:%s\n"
467 "x-amz-server-side-encryption-customer-key:%s\n"
468 "x-amz-server-side-encryption-customer-key-md5:%s\n"
469 "x-amz-storage-class:%s\n"
471 "host;x-amz-content-sha256;x-amz-date;"
472 "x-amz-server-side-encryption-customer-algorithm;"
473 "x-amz-server-side-encryption-customer-key;"
474 "x-amz-server-side-encryption-customer-key-md5;"
475 "x-amz-storage-class\n"
478 , uri_encoded, o->host, dsha, date_iso
479 , o->s3_sse_customer_algorithm, sse_key_base64
480 , sse_key_md5_base64, o->s3_storage_class, dsha);
482 snprintf(creq, sizeof(creq),
487 "x-amz-content-sha256:%s\n"
489 "x-amz-storage-class:%s\n"
491 "host;x-amz-content-sha256;x-amz-date;x-amz-storage-class\n"
494 , uri_encoded, o->host, dsha, date_iso, o->s3_storage_class, dsha);
497 csha = _gen_hex_sha256(creq, strlen(creq));
498 snprintf(sts, sizeof(sts), "AWS4-HMAC-SHA256\n%s\n%s/%s/%s/%s\n%s",
499 date_iso, date_short, o->s3_region, service, aws, csha);
501 snprintf((char *)dkey, sizeof(dkey), "AWS4%s", o->s3_key);
502 _hmac(md, dkey, strlen(dkey), date_short);
503 _hmac(md, md, SHA256_DIGEST_LENGTH, o->s3_region);
504 _hmac(md, md, SHA256_DIGEST_LENGTH, (char*) service);
505 _hmac(md, md, SHA256_DIGEST_LENGTH, (char*) aws);
506 _hmac(md, md, SHA256_DIGEST_LENGTH, sts);
508 signature = _conv_hex(md, SHA256_DIGEST_LENGTH);
510 /* Suppress automatic Accept: header */
511 slist = curl_slist_append(slist, "Accept:");
513 snprintf(s, sizeof(s), "x-amz-content-sha256: %s", dsha);
514 slist = curl_slist_append(slist, s);
516 snprintf(s, sizeof(s), "x-amz-date: %s", date_iso);
517 slist = curl_slist_append(slist, s);
519 if (sse_key[0] != '\0') {
520 snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-algorithm: %s", o->s3_sse_customer_algorithm);
521 slist = curl_slist_append(slist, s);
522 snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-key: %s", sse_key_base64);
523 slist = curl_slist_append(slist, s);
524 snprintf(s, sizeof(s), "x-amz-server-side-encryption-customer-key-md5: %s", sse_key_md5_base64);
525 slist = curl_slist_append(slist, s);
528 snprintf(s, sizeof(s), "x-amz-storage-class: %s", o->s3_storage_class);
529 slist = curl_slist_append(slist, s);
531 if (sse_key[0] != '\0') {
532 snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request,"
533 "SignedHeaders=host;x-amz-content-sha256;"
534 "x-amz-date;x-amz-server-side-encryption-customer-algorithm;"
535 "x-amz-server-side-encryption-customer-key;"
536 "x-amz-server-side-encryption-customer-key-md5;"
537 "x-amz-storage-class,"
539 o->s3_keyid, date_short, o->s3_region, signature);
541 snprintf(s, sizeof(s), "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/s3/aws4_request,"
542 "SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-storage-class,Signature=%s",
543 o->s3_keyid, date_short, o->s3_region, signature);
545 slist = curl_slist_append(slist, s);
547 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
553 if (sse_key_base64 != NULL) {
554 free(sse_key_base64);
555 free(sse_key_md5_base64);
559 static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_options *o,
560 int op, const char *uri, char *buf, size_t len)
565 if (op == DDIR_WRITE) {
566 dsha = _gen_hex_md5(buf, len);
568 /* Suppress automatic Accept: header */
569 slist = curl_slist_append(slist, "Accept:");
571 snprintf(s, sizeof(s), "etag: %s", dsha);
572 slist = curl_slist_append(slist, s);
574 snprintf(s, sizeof(s), "x-auth-token: %s", o->swift_auth_token);
575 slist = curl_slist_append(slist, s);
577 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
582 static void fio_http_cleanup(struct thread_data *td)
584 struct http_data *http = td->io_ops_data;
587 curl_easy_cleanup(http->curl);
592 static size_t _http_read(void *ptr, size_t size, size_t nmemb, void *stream)
594 struct http_curl_stream *state = stream;
595 size_t len = size * nmemb;
596 /* We're retrieving; nothing is supposed to be read locally */
599 if (len+state->pos > state->max)
600 len = state->max - state->pos;
601 memcpy(ptr, &state->buf[state->pos], len);
606 static size_t _http_write(void *ptr, size_t size, size_t nmemb, void *stream)
608 struct http_curl_stream *state = stream;
609 /* We're just discarding the returned body after a PUT */
613 return CURLE_WRITE_ERROR;
614 if (nmemb + state->pos > state->max)
615 return CURLE_WRITE_ERROR;
616 memcpy(&state->buf[state->pos], ptr, nmemb);
621 static int _http_seek(void *stream, curl_off_t offset, int origin)
623 struct http_curl_stream *state = stream;
624 if (offset < state->max && origin == SEEK_SET) {
626 return CURL_SEEKFUNC_OK;
628 return CURL_SEEKFUNC_FAIL;
631 static enum fio_q_status fio_http_queue(struct thread_data *td,
634 struct http_data *http = td->io_ops_data;
635 struct http_options *o = td->eo;
636 struct http_curl_stream _curl_stream;
637 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);