diff --git a/Makefile b/Makefile index 8d8626928f..c2b2df45b4 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,10 @@ ifdef CONFIG_GFAPI CFLAGS += "-DGFAPI_USE_FADVISE" endif endif +ifdef CONFIG_CIFS + SOURCE += engines/cifs.c + SOURCE += engines/cifs_sync.c +endif ifeq ($(CONFIG_TARGET_OS), Linux) SOURCE += diskutil.c fifo.c blktrace.c cgroup.c trim.c engines/sg.c \ diff --git a/configure b/configure index 33d1327ebb..d450209525 100755 --- a/configure +++ b/configure @@ -163,6 +163,8 @@ for opt do ;; --enable-libhdfs) libhdfs="yes" ;; + --disable-cifs) disable_cifs="yes" + ;; --help) show_help="yes" ;; @@ -1270,6 +1272,35 @@ if test "$libhdfs" = "yes" ; then fi echo "HDFS engine $libhdfs" +########################################## +# Check if we have the right samba libraries for CIFS +# Need path to headers with SMBCLI_CFLAGS +cifs="no" +cat > $TMPC << EOF +#include +#include + +#include +#include + +int main(int argc, char **argv) +{ + struct smbcli_state *s = smbcli_state_init(NULL); + return 0; +} +EOF +SMBCLI_LIBS=$(pkg-config --libs netapi smbclient smbclient-raw dcerpc samba-hostconfig samba-credentials gensec) +SMBCLI_CFLAGS=$(pkg-config --cflags netapi smbclient smbclient-raw dcerpc samba-hostconfig samba-credentials gensec) +if test "$disable_cifs" != "yes" && compile_prog "$SMBCLI_CFLAGS" "$SMBCLI_LIBS" "cifs"; then + LIBS="$LIBS $SMBCLI_LIBS" + # TODO: Figure out how to make this work + LIBS="$LIBS -Wl,-rpath /usr/lib/x86_64-linux-gnu/samba /usr/lib/x86_64-linux-gnu/samba/liberrors.so.0 /usr/lib/x86_64-linux-gnu/samba/libcli-ldap.so.0" + CFLAGS="$CFLAGS $SMBCLI_CFLAGS" + cifs="yes" +fi + echo "Samba CIFS client $cifs" + + ############################################################################# if test "$wordsize" = "64" ; then @@ -1414,6 +1445,9 @@ fi if test "$libhdfs" = "yes" ; then output_sym "CONFIG_LIBHDFS" fi +if test "$cifs" = "yes"; then + output_sym "CONFIG_CIFS" +fi if test "$zlib" = "no" ; then echo "Consider installing zlib-dev (zlib-devel), some fio features depend on it." diff --git a/engines/cifs.c b/engines/cifs.c new file mode 100644 index 0000000000..67c23fac0c --- /dev/null +++ b/engines/cifs.c @@ -0,0 +1,339 @@ +/* + * cifs sync + * + */ +#include +#include +#include +#include +#include + +#include "../fio.h" + +#include "cifs.h" + +// This not part of any installed samba headers +extern struct resolve_context *lpcfg_resolve_context(struct loadparm_context *lp_ctx); + +static int extend_file(struct thread_data *td, struct fio_file *f); + +struct fio_option cifs_options[] = { + { + .name = "hostname", + .lname = "CIFS host", + .type = FIO_OPT_STR_STORE, + .help = "CIFS host", + .off1 = offsetof(struct cifs_options, host), + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_CIFS, + }, + { + .name = "username", + .lname = "Username of the cifs user", + .type = FIO_OPT_STR_STORE, + .help = "Username of the cifs user", + .off1 = offsetof(struct cifs_options, username), + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_CIFS, + }, + { + .name = "password", + .lname = "Passsword of the cifs user", + .type = FIO_OPT_STR_STORE, + .help = "Passsword of the cifs user", + .off1 = offsetof(struct cifs_options, password), + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_CIFS, + }, + { + .name = "share", + .lname = "Name of the cifs share", + .type = FIO_OPT_STR_STORE, + .help = "Name of the cifs share", + .off1 = offsetof(struct cifs_options, share), + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_CIFS, + }, + { + .name = NULL, + }, +}; + +int fio_cifs_init(struct thread_data *td) +{ + struct cifs_data *ld = talloc(NULL, struct cifs_data); + struct cifs_options *o = td->eo; + NTSTATUS status; + + memset(ld, 0, sizeof(*ld)); + + log_info("Connecting to share: \\\\%s\\%s\n", o->host, o->share); + + // This is only used in the async client + ld->ev = samba_tevent_context_init(ld); + + // Not sure what this does, but it doesn't connect without it + gensec_init(); + + ld->lp_ctx = loadparm_init_global(false); + lpcfg_load_default(ld->lp_ctx); + lpcfg_smbcli_options(ld->lp_ctx, &ld->opts); + lpcfg_smbcli_session_options(ld->lp_ctx, &ld->sopts); + + // Login crentils + ld->creds = cli_credentials_init(ld); + cli_credentials_set_anonymous(ld->creds); + + if (o->username) { + cli_credentials_parse_string( + ld->creds, + o->username, + CRED_SPECIFIED); + } + + if (o->password) { + cli_credentials_set_password( + ld->creds, + o->password, + CRED_SPECIFIED); + } + + // Fills in missing values in crenditals with defaults. + // Won't connect without it. + cli_credentials_guess(ld->creds, ld->lp_ctx); + + status = smbcli_full_connection( + ld, /* mem_ctx */ + &ld->cli, /* handle */ + o->host, /* host */ + lpcfg_smb_ports(ld->lp_ctx), /* port */ + o->share, /* sharename */ + NULL, /* dev type */ + lpcfg_socket_options(ld->lp_ctx), /* opts: socket */ + ld->creds, /* opts: cred */ + lpcfg_resolve_context(ld->lp_ctx), /* resolve ctx */ + ld->ev, /* event */ + &ld->opts, /* opts */ + &ld->sopts, /* opts: session */ + lpcfg_gensec_settings(ld, ld->lp_ctx) /* opts: grsec */ + ); + + if (!NT_STATUS_IS_OK(status)) { + log_err("smbcli_full_connection() failed: %s %s\n", + get_friendly_nt_error_msg(status), + nt_errstr(status)); + goto error; + } + + log_info("Connected to share: \\\\%s\\%s\n", o->host, o->share); + + td->io_ops->data = ld; + return 0; + +error: + TALLOC_FREE(ld); + return -EIO; +} + +void fio_cifs_cleanup(struct thread_data *td) +{ + struct cifs_data *ld = td->io_ops->data; + + if (!ld) return; + + smbcli_tdis(ld->cli); + TALLOC_FREE(ld); +} + +static int open_flags(struct thread_data *td) +{ + int flags = 0; + + if (td_write(td)) { + if (!read_only) + flags = O_RDWR; + } else if (td_read(td)) { + if (!read_only) + flags = O_RDWR; + else + flags = O_RDONLY; + } + + if (td->o.create_on_open) + flags |= O_CREAT; + + return flags; +} + +int fio_cifs_open_file(struct thread_data *td, struct fio_file *f) +{ + int flags = open_flags(td); + struct cifs_data *ld = td->io_ops->data; + struct cifs_options *o = td->eo; + + ld->fnum = smbcli_open(ld->cli->tree, f->file_name, flags, DENY_NONE); + + if (ld->fnum == -1) { + log_err("smbcli_open() failed\n"); + return ld->fnum; + } + + log_info("Opened file: \\\\%s\\%s\%s\n", o->host, o->share, + f->file_name); + + // Setup file (grow / shrink) if needed. + return extend_file(td, f); +} + +int fio_cifs_close_file(struct thread_data *td, struct fio_file *f) +{ + struct cifs_data *ld = td->io_ops->data; + struct cifs_options *o = td->eo; + NTSTATUS status = smbcli_close(ld->cli->tree, ld->fnum); + + if (!NT_STATUS_IS_OK(status)) { + log_err("smbcli_close() failed: %s %s\n", + get_friendly_nt_error_msg(status), + nt_errstr(status)); + return -EIO; + } + + log_info("Closed file: \\\\%s\\%s\%s\n", o->host, o->share, + f->file_name); + + return 0; +} + +int fio_cifs_unlink_file(struct thread_data *td, struct fio_file *f) +{ + struct cifs_data *ld = td->io_ops->data; + struct cifs_options *o = td->eo; + NTSTATUS status; + + status = smbcli_unlink(ld->cli->tree, f->file_name); + + if (!NT_STATUS_IS_OK(status)) { + log_err("smbcli_unlink() failed: %s %s\n", + get_friendly_nt_error_msg(status), + nt_errstr(status)); + return -EIO; + } + + log_info("Deleted file: \\\\%s\\%s\%s\n", o->host, o->share, + f->file_name); + + return 0; +} + +int fio_cifs_get_file_size(struct thread_data *td, struct fio_file *f) +{ + struct cifs_data *ld = td->io_ops->data; + struct cifs_options *o = td->eo; + size_t size; + NTSTATUS status; + + // This gets called before the engine is initlized + if (!ld) return 0; + + status = smbcli_getattrE(ld->cli->tree, ld->fnum, NULL, &size, NULL, + NULL, NULL); + + if (!NT_STATUS_IS_OK(status)) { + log_err("smbcli_getattrE() failed: %s %s\n", + get_friendly_nt_error_msg(status), + nt_errstr(status)); + return -EIO; + } + + log_info("stat: \\\\%s\\%s\%s\n", o->host, o->share, + f->file_name); + + f->real_file_size = size; + fio_file_set_size_known(f); + + return 0; +} + +// Since we're not using the normal generic_open_* fd, we can't count on fio +// to extend the file for us +static +int extend_file(struct thread_data *td, struct fio_file *f) +{ + size_t size = 0; + struct cifs_data *ld = td->io_ops->data; + int ret = 0; + NTSTATUS status; + char* tmp_data; + + // Find current size + status = smbcli_getattrE(ld->cli->tree, ld->fnum, NULL, &size, NULL, + NULL, NULL); + + if (!NT_STATUS_IS_OK(status)) { + log_err("smbcli_getattrE() failed: %s %s\n", + get_friendly_nt_error_msg(status), + nt_errstr(status)); + return -EIO; + } + + if (size == f->real_file_size) + return 0; + + // Too large, truncate + if (size < f->real_file_size) { + status = smbcli_ftruncate(ld->cli->tree, ld->fnum, + f->real_file_size); + + if (!NT_STATUS_IS_OK(status)) { + log_err("smbcli_ftruncate() failed: %s %s\n", + get_friendly_nt_error_msg(status), + nt_errstr(status)); + return -EIO; + } + + return 0; + } + + // File is too small + + // fill the file, copied from extend_file + if ((tmp_data = malloc(td->o.max_bs[DDIR_WRITE])) == NULL) + return -ENOMEM; + + for (size_t left = f->real_file_size - size; left & !td->terminate; ) { + uint16_t write_flags = 0; + unsigned bs = td->o.max_bs[DDIR_WRITE]; + ssize_t len; + + if (td->o.create_fsync) { + // Disable write caching (according to header) + write_flags |= 0x0001; + } + + if (bs > left) + bs = left; + + fill_io_buffer(td, tmp_data, bs, bs); + + len = smbcli_write(ld->cli->tree, ld->fnum, write_flags, + tmp_data, size, bs); + + if (len < 0) { + if (errno == ENOSPC && td->o.fill_device) { + log_info("fio: ENOSPC on laying out " + "file, stopping\n"); + break; + } + + td_verror(td, errno, "write"); + ret = -EIO; + break; + } + + size += len; + left -= len; + } + + free(tmp_data); + return ret; +} diff --git a/engines/cifs.h b/engines/cifs.h new file mode 100644 index 0000000000..7488d3ebfa --- /dev/null +++ b/engines/cifs.h @@ -0,0 +1,46 @@ +#include +#include + +#include +#include +#include +#include +#include + +struct cifs_options { + struct thread_data *td; + const char* host; + const char* username; + const char* password; + const char* share; +}; + +struct cifs_data { + /* settings */ + struct loadparm_context* lp_ctx; + struct cli_credentials* creds; + struct smbcli_options opts; + struct smbcli_session_options sopts; + + /* client */ + struct smbcli_state *cli; + + /* async */ + struct tevent_context *ev; + struct tevent_req *req; + + /* fd */ + int fnum; +}; + +extern struct fio_option cifs_options[]; + +extern int fio_cifs_init(struct thread_data *td); +extern void fio_cifs_cleanup(struct thread_data *td); + +extern int fio_cifs_open_file(struct thread_data *td, struct fio_file *f); +extern int fio_cifs_close_file(struct thread_data *td, struct fio_file *f); +extern int fio_cifs_unlink_file(struct thread_data *td, struct fio_file *f); +extern int fio_cifs_get_file_size(struct thread_data *td, struct fio_file *f); + + diff --git a/engines/cifs_sync.c b/engines/cifs_sync.c new file mode 100644 index 0000000000..49b66ff5c3 --- /dev/null +++ b/engines/cifs_sync.c @@ -0,0 +1,87 @@ +/* + * cifs sync + * + */ +#include +#include +#include +#include +#include + +#include "../fio.h" + +#include "cifs.h" + +#define LAST_POS(f) ((f)->engine_data) + +static int fio_io_end(struct thread_data *td, struct io_u *io_u, int ret) +{ + if (io_u->file && ret >= 0 && ddir_rw(io_u->ddir)) + LAST_POS(io_u->file) = io_u->offset + ret; + + if (ret != (int) io_u->xfer_buflen) { + if (ret >= 0) { + io_u->resid = io_u->xfer_buflen - ret; + io_u->error = 0; + return FIO_Q_COMPLETED; + } else + io_u->error = errno; + } + + if (io_u->error) + td_verror(td, io_u->error, "xfer"); + + return FIO_Q_COMPLETED; +} + +static int fio_cifs_queue(struct thread_data *td, struct io_u *io_u) +{ + struct cifs_data *ld = td->io_ops->data; + ssize_t ret; + + if (io_u->ddir == DDIR_READ) { + ret = smbcli_read(ld->cli->tree, ld->fnum, + io_u->xfer_buf, io_u->offset, io_u->xfer_buflen); + } else if (io_u->ddir == DDIR_WRITE) { + uint16_t write_flags = 0; + ret = smbcli_write(ld->cli->tree, ld->fnum, write_flags, + io_u->xfer_buf, io_u->offset, io_u->xfer_buflen); + } else { + log_err("unsupported operation.\n"); + return -EINVAL; + } + + if (ret < 0) { + log_info("CIFS IO error Op: %i, fnum: %i, offset: %llu, " + "len: %lu, ret: %lld", + io_u->ddir, ld->fnum, io_u->offset, + io_u->xfer_buflen, (long long int) ret); + } + + return fio_io_end(td, io_u, ret); +} + +static struct ioengine_ops ioengine = { + .name = "cifs_sync", + .version = FIO_IOOPS_VERSION, + .init = fio_cifs_init, + .cleanup = fio_cifs_cleanup, + .queue = fio_cifs_queue, + .open_file = fio_cifs_open_file, + .close_file = fio_cifs_close_file, + .unlink_file = fio_cifs_unlink_file, + .get_file_size = fio_cifs_get_file_size, + .options = cifs_options, + .option_struct_size = sizeof(struct cifs_options), + .flags = FIO_SYNCIO | FIO_DISKLESSIO, +}; + +static void fio_init fio_cifs_register(void) +{ + register_ioengine(&ioengine); +} + +static void fio_exit fio_cifs_unregister(void) +{ + unregister_ioengine(&ioengine); +} diff --git a/examples/cifs.fio b/examples/cifs.fio new file mode 100644 index 0000000000..f0c2d244fa --- /dev/null +++ b/examples/cifs.fio @@ -0,0 +1,21 @@ +# Test opening a file from multiple jobs. +# Originally authored by Castor Fu +[global] +ioengine=cifs_sync +hostname=localhost +username=fio_smb_user +password=fio_password +share=fio_share +thread=1 +create_on_open=1 + +time_based=1 +runtime=10m +ramp_time=1m + +[remote file] +rw=randread +size=1280m +bs=4k +random_distribution=zipf:0.25 +filename_format=fio_run.$filenum diff --git a/options.c b/options.c index bc07885d75..fc0355bfa9 100644 --- a/options.c +++ b/options.c @@ -1572,6 +1572,11 @@ struct fio_option fio_options[FIO_MAX_OPTS] = { { .ival = "libhdfs", .help = "Hadoop Distributed Filesystem (HDFS) engine" }, +#endif +#ifdef CONFIG_CIFS + { .ival = "cifs", + .help = "CIFS Samba client" + }, #endif { .ival = "external", .help = "Load external engine (append name)", diff --git a/options.h b/options.h index b2e2c0c8fe..85af612d75 100644 --- a/options.h +++ b/options.h @@ -99,6 +99,7 @@ enum opt_category_group { __FIO_OPT_G_LATPROF, __FIO_OPT_G_RBD, __FIO_OPT_G_GFAPI, + __FIO_OPT_G_CIFS, __FIO_OPT_G_NR, FIO_OPT_G_RATE = (1U << __FIO_OPT_G_RATE), @@ -130,6 +131,7 @@ enum opt_category_group { FIO_OPT_G_LATPROF = (1U << __FIO_OPT_G_LATPROF), FIO_OPT_G_RBD = (1U << __FIO_OPT_G_RBD), FIO_OPT_G_GFAPI = (1U << __FIO_OPT_G_GFAPI), + FIO_OPT_G_CIFS = (1U << __FIO_OPT_G_CIFS), FIO_OPT_G_INVALID = (1U << __FIO_OPT_G_NR), };