summaryrefslogtreecommitdiffstats
path: root/examples/fuse
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--examples/fuse/README7
-rw-r--r--examples/fuse/clifuse.c1591
-rw-r--r--examples/fuse/clifuse.h27
-rw-r--r--examples/fuse/smb2mount.c160
-rw-r--r--examples/fuse/wscript14
-rw-r--r--examples/fuse/wscript_build7
6 files changed, 1806 insertions, 0 deletions
diff --git a/examples/fuse/README b/examples/fuse/README
new file mode 100644
index 0000000..a1f311d
--- /dev/null
+++ b/examples/fuse/README
@@ -0,0 +1,7 @@
+WARNING:
+
+This is experimental work-in-progress code, don't expect it to do
+anything sensible.
+
+Eventually this *might* turn into a fuse client filesystem. This is
+not a promise for anything.
diff --git a/examples/fuse/clifuse.c b/examples/fuse/clifuse.c
new file mode 100644
index 0000000..59af160
--- /dev/null
+++ b/examples/fuse/clifuse.c
@@ -0,0 +1,1591 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * fusermount smb2 client
+ * Copyright (C) Volker Lendecke 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define FUSE_USE_VERSION 26
+#define _FILE_OFFSET_BITS 64
+#include "fuse/fuse_lowlevel.h"
+
+#include "source3/include/includes.h"
+#include "client.h"
+#include "trans2.h"
+#include "libsmb/proto.h"
+#include "libsmb/clirap.h"
+#include "libsmb/cli_smb2_fnum.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "libcli/security/security.h"
+#include "clifuse.h"
+#include "lib/util/idtree.h"
+
+struct mount_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ bool done;
+
+ struct tevent_fd *fde;
+ struct tevent_signal *signal_ev;
+
+ struct fuse_chan *ch;
+ struct fuse_session *se;
+
+ size_t bufsize;
+ char *buf;
+
+ struct idr_context *ino_ctx;
+ TALLOC_CTX *ino_parent;
+};
+
+struct inode_state {
+ struct idr_context *ino_ctx;
+ fuse_ino_t ino;
+ char path[];
+};
+
+static int inode_state_destructor(struct inode_state *s);
+
+static struct inode_state *inode_state_init(TALLOC_CTX *mem_ctx,
+ struct idr_context *ino_ctx,
+ const char *path)
+{
+ struct inode_state *state;
+ size_t pathlen;
+ int ino;
+
+ pathlen = strlen(path);
+ state = talloc_size(
+ mem_ctx, offsetof(struct inode_state, path) + pathlen + 1);
+ if (state == NULL) {
+ return NULL;
+ }
+ talloc_set_name_const(state, "struct inode_state");
+
+ ino = idr_get_new_above(ino_ctx, state, 1, INT32_MAX);
+ if (ino == -1) {
+ TALLOC_FREE(state);
+ return NULL;
+ }
+
+ state->ino = ino;
+ state->ino_ctx = ino_ctx;
+ memcpy(state->path, path, pathlen + 1);
+
+ DBG_DEBUG("Creating ino %d for path %s\n", ino, path);
+
+ talloc_set_destructor(state, inode_state_destructor);
+
+ return state;
+}
+
+static struct inode_state *inode_state_new(struct mount_state *mstate,
+ const char *path)
+{
+ return inode_state_init(mstate->ino_parent, mstate->ino_ctx, path);
+}
+
+static int inode_state_destructor(struct inode_state *s)
+{
+ DBG_DEBUG("destroying inode %ju\n", (uintmax_t)s->ino);
+ idr_remove(s->ino_ctx, s->ino);
+ return 0;
+}
+
+struct ll_create_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ struct fuse_file_info fi;
+ char *path;
+};
+
+static void cli_ll_create_done(struct tevent_req *req);
+
+static void cli_ll_create(fuse_req_t freq, fuse_ino_t parent, const char *name,
+ mode_t mode, struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_create_state *state;
+ struct inode_state *istate;
+ struct tevent_req *req;
+
+ DBG_DEBUG("parent=%ju, name=%s, mode=%x\n", (uintmax_t)parent,
+ name, (unsigned)mode);
+
+ istate = idr_find(mstate->ino_ctx, parent);
+ if (istate == NULL) {
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_create_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+ state->fi = *fi;
+
+ state->path = talloc_asprintf(state, "%s%s%s", istate->path,
+ strlen(istate->path) ? "\\": "",
+ name);
+ if (state->path == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+
+ req = cli_smb2_create_fnum_send(
+ state,
+ mstate->ev,
+ mstate->cli, state->path,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_GENERIC_READ|FILE_GENERIC_WRITE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ FILE_NON_DIRECTORY_FILE,
+ NULL);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_create_done, state);
+}
+
+static void cli_ll_create_done(struct tevent_req *req)
+{
+ struct ll_create_state *state = tevent_req_callback_data(
+ req, struct ll_create_state);
+ struct fuse_entry_param e;
+ struct inode_state *ino;
+ uint16_t fnum;
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(req, &fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+
+ state->fi.fh = fnum;
+ state->fi.direct_io = 0;
+ state->fi.keep_cache = 0;
+
+ ino = inode_state_new(state->mstate, state->path);
+ if (ino == NULL) {
+ fuse_reply_err(state->freq, ENOMEM);
+ return;
+ }
+
+ e = (struct fuse_entry_param) {
+ .ino = ino->ino,
+ .generation = 1, /* FIXME */
+ .attr_timeout = 1.0,
+ .entry_timeout = 1.0
+ };
+
+ fuse_reply_create(state->freq, &e, &state->fi);
+
+ TALLOC_FREE(state);
+}
+
+struct cli_get_unixattr_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint64_t fid_persistent;
+ uint64_t fid_volatile;
+
+ struct timespec create_time;
+ struct timespec access_time;
+ struct timespec write_time;
+ struct timespec change_time;
+ uint32_t mode;
+ uint64_t ino;
+ uint64_t size;
+};
+
+static void cli_get_unixattr_opened(struct tevent_req *subreq);
+static void cli_get_unixattr_gotinfo(struct tevent_req *subreq);
+static void cli_get_unixattr_closed(struct tevent_req *subreq);
+
+
+static struct tevent_req *cli_get_unixattr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *path)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_get_unixattr_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_get_unixattr_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ subreq = smb2cli_create_send(
+ state, ev, cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, path, SMB2_OPLOCK_LEVEL_NONE,
+ SMB2_IMPERSONATION_IMPERSONATION,
+ SYNCHRONIZE_ACCESS|FILE_READ_ATTRIBUTES, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_get_unixattr_opened, req);
+
+ return req;
+}
+
+static void cli_get_unixattr_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_get_unixattr_state *state = tevent_req_data(
+ req, struct cli_get_unixattr_state);
+ struct cli_state *cli = state->cli;
+ NTSTATUS status;
+
+ status = smb2cli_create_recv(
+ subreq,
+ &state->fid_persistent,
+ &state->fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("smb2cli_create_recv returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ subreq = smb2cli_query_info_send(
+ state,
+ state->ev,
+ cli->conn,
+ 0,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 1, /* in_info_type */
+ FSCC_FILE_ALL_INFORMATION, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ state->fid_persistent,
+ state->fid_volatile);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_get_unixattr_gotinfo, req);
+}
+
+static void cli_get_unixattr_gotinfo(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_get_unixattr_state *state = tevent_req_data(
+ req, struct cli_get_unixattr_state);
+ struct cli_state *cli = state->cli;
+ NTSTATUS status;
+ DATA_BLOB outbuf;
+
+ status = smb2cli_query_info_recv(subreq, state, &outbuf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("smb2cli_query_info_recv returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ if (outbuf.length < 0x60) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ state->create_time = interpret_long_date(BVAL(outbuf.data, 0));
+ state->access_time = interpret_long_date(BVAL(outbuf.data, 0x8));
+ state->write_time = interpret_long_date(BVAL(outbuf.data, 0x10));
+ state->change_time = interpret_long_date(BVAL(outbuf.data, 0x18));
+ state->mode = IVAL(outbuf.data, 0x20);
+ state->size = BVAL(outbuf.data, 0x30);
+ state->ino = BVAL(outbuf.data, 0x40);
+
+ subreq = smb2cli_close_send(state, state->ev, cli->conn, 0,
+ cli->smb2.session, cli->smb2.tcon, 0,
+ state->fid_persistent,
+ state->fid_volatile);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_get_unixattr_closed, req);
+}
+
+static void cli_get_unixattr_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = smb2cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_get_unixattr_recv(struct tevent_req *req,
+ struct stat *st)
+{
+ struct cli_get_unixattr_state *state = tevent_req_data(
+ req, struct cli_get_unixattr_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (state->mode & FILE_ATTRIBUTE_DIRECTORY) {
+ st->st_mode = (S_IFDIR | 0555);
+ st->st_nlink = 2;
+ } else {
+ st->st_mode = (S_IFREG | 0444);
+ st->st_nlink = 1;
+ }
+
+ st->st_size = state->size;
+ st->st_uid = getuid();
+ st->st_gid = getgid();
+ st->st_ino = state->ino;
+ st->st_atime = convert_timespec_to_time_t(state->access_time);
+ st->st_ctime = convert_timespec_to_time_t(state->change_time);
+ st->st_mtime = convert_timespec_to_time_t(state->write_time);
+
+ return NT_STATUS_OK;
+}
+
+struct cli_smb2_listdir_state {
+ struct tevent_context *ev;
+ struct smbXcli_conn *conn;
+ uint32_t timeout_msec;
+ struct smbXcli_session *session;
+ struct smbXcli_tcon *tcon;
+ uint8_t level;
+ uint8_t flags;
+ uint32_t file_index;
+ uint64_t fid_persistent;
+ uint64_t fid_volatile;
+ const char *mask;
+ uint32_t outbuf_len;
+
+ uint16_t attribute;
+ const char *mntpoint;
+ const char *pathname;
+ NTSTATUS (*fn)(const char *mntpoint, struct file_info *f,
+ const char *mask, void *private_data);
+ void *private_data;
+ bool processed_file;
+};
+
+static void cli_smb2_listdir_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb2_listdir_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXcli_conn *conn,
+ uint32_t timeout_msec,
+ struct smbXcli_session *session,
+ struct smbXcli_tcon *tcon,
+ uint8_t level,
+ uint8_t flags,
+ uint32_t file_index,
+ uint64_t fid_persistent,
+ uint64_t fid_volatile,
+ const char *mask,
+ uint32_t outbuf_len,
+ uint16_t attribute,
+ const char *mntpoint,
+ const char *pathname,
+ NTSTATUS (*fn)(const char *mntpoint, struct file_info *f,
+ const char *mask, void *private_data),
+ void *private_data)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb2_listdir_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb2_listdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->conn = conn;
+ state->timeout_msec = timeout_msec;
+ state->session = session;
+ state->tcon = tcon;
+ state->level = level;
+ state->flags = flags;
+ state->file_index = file_index;
+ state->fid_persistent = fid_persistent;
+ state->fid_volatile = fid_volatile;
+ state->mask = mask;
+ state->outbuf_len = outbuf_len;
+ state->attribute = attribute;
+ state->mntpoint = mntpoint;
+ state->pathname = pathname;
+ state->fn = fn;
+ state->private_data = private_data;
+
+ subreq = smb2cli_query_directory_send(
+ state, state->ev, state->conn, state->timeout_msec,
+ state->session, state->tcon, state->level,
+ state->flags, state->file_index,
+ state->fid_persistent, state->fid_volatile,
+ state->mask, state->outbuf_len);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_listdir_done, req);
+ return req;
+}
+
+static NTSTATUS parse_finfo_id_both_directory_info(uint8_t *dir_data,
+ uint32_t dir_data_length,
+ struct file_info *finfo,
+ uint32_t *next_offset)
+{
+ size_t namelen = 0;
+ size_t slen = 0;
+ size_t ret = 0;
+
+ if (dir_data_length < 4) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ *next_offset = IVAL(dir_data, 0);
+
+ if (*next_offset > dir_data_length) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ if (*next_offset != 0) {
+ /* Ensure we only read what in this record. */
+ dir_data_length = *next_offset;
+ }
+
+ if (dir_data_length < 105) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ finfo->btime_ts = interpret_long_date(BVAL(dir_data, 8));
+ finfo->atime_ts = interpret_long_date(BVAL(dir_data, 16));
+ finfo->mtime_ts = interpret_long_date(BVAL(dir_data, 24));
+ finfo->ctime_ts = interpret_long_date(BVAL(dir_data, 32));
+ finfo->size = IVAL2_TO_SMB_BIG_UINT(dir_data + 40, 0);
+ finfo->attr = IVAL(dir_data + 56, 0);
+ namelen = IVAL(dir_data + 60,0);
+ if (namelen > (dir_data_length - 104)) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ slen = CVAL(dir_data + 68, 0);
+ if (slen > 24) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ ret = pull_string_talloc(finfo,
+ dir_data,
+ FLAGS2_UNICODE_STRINGS,
+ &finfo->short_name,
+ dir_data + 70,
+ slen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ ret = pull_string_talloc(finfo,
+ dir_data,
+ FLAGS2_UNICODE_STRINGS,
+ &finfo->name,
+ dir_data + 104,
+ namelen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ return NT_STATUS_OK;
+}
+
+static void cli_smb2_listdir_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_listdir_state *state = tevent_req_data(
+ req, struct cli_smb2_listdir_state);
+ uint8_t *data;
+ uint32_t data_len;
+ uint32_t next_offset = 0;
+ NTSTATUS status;
+
+ status = smb2cli_query_directory_recv(subreq, state, &data,
+ &data_len);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ do {
+ struct file_info *finfo;
+ bool ok;
+
+ finfo = talloc_zero(state, struct file_info);
+ if (tevent_req_nomem(finfo, req)) {
+ return;
+ }
+
+ status = parse_finfo_id_both_directory_info(
+ data, data_len, finfo, &next_offset);
+
+ DEBUG(10, ("%s: parse_finfo_id_both_directory_info returned "
+ "%s\n", __func__, nt_errstr(status)));
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ok = dir_check_ftype(finfo->attr, state->attribute);
+
+ DEBUG(10, ("%s: dir_check_ftype(%u,%u) returned %u\n",
+ __func__, (unsigned)finfo->attr,
+ (unsigned)state->attribute, (unsigned)ok));
+
+ if (ok) {
+ /*
+ * Only process if attributes match. On SMB1 server
+ * does this, so on SMB2 we need to emulate in the
+ * client.
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=10260
+ */
+ state->processed_file = true;
+
+ status = state->fn(state->mntpoint, finfo,
+ state->pathname,
+ state->private_data);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ TALLOC_FREE(finfo);
+
+ if (next_offset != 0) {
+ data += next_offset;
+ data_len -= next_offset;
+ }
+ } while (next_offset != 0);
+
+ subreq = smb2cli_query_directory_send(
+ state, state->ev, state->conn, state->timeout_msec,
+ state->session, state->tcon, state->level,
+ state->flags, state->file_index,
+ state->fid_persistent, state->fid_volatile,
+ state->mask, state->outbuf_len);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_listdir_done, req);
+}
+
+static NTSTATUS cli_smb2_listdir_recv(struct tevent_req *req)
+{
+ struct cli_smb2_listdir_state *state = tevent_req_data(
+ req, struct cli_smb2_listdir_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (!state->processed_file) {
+ /*
+ * In SMB1 findfirst returns NT_STATUS_NO_SUCH_FILE
+ * if no files match. Emulate this in the client.
+ */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct ll_lookup_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ char *path;
+};
+
+static void cli_ll_lookup_done(struct tevent_req *req);
+
+static void cli_ll_lookup(fuse_req_t freq, fuse_ino_t parent_ino,
+ const char *name)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_lookup_state *state;
+ struct tevent_req *req;
+ struct inode_state *parent;
+
+ DBG_DEBUG("parent_ino=%ju, name=%s\n", (uintmax_t)parent_ino, name);
+
+ parent = idr_find(mstate->ino_ctx, parent_ino);
+ if (parent == NULL) {
+ DBG_WARNING("could not find parent\n");
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_lookup_state);
+ if (state == NULL) {
+ DBG_WARNING("talloc failed\n");
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+
+ state->path = talloc_asprintf(state, "%s%s%s", parent->path,
+ strlen(parent->path) ? "\\": "",
+ name);
+ if (state->path == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+
+ req = cli_get_unixattr_send(state, mstate->ev, mstate->cli,
+ state->path);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_lookup_done, state);
+}
+
+static void cli_ll_lookup_done(struct tevent_req *req)
+{
+ struct ll_lookup_state *state = tevent_req_callback_data(
+ req, struct ll_lookup_state);
+ struct stat sbuf = {0};
+ struct fuse_entry_param e;
+ struct inode_state *ino;
+ NTSTATUS status;
+
+ status = cli_get_unixattr_recv(req, &sbuf);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+
+ ino = inode_state_new(state->mstate, state->path);
+ if (ino == NULL) {
+ fuse_reply_err(state->freq, ENOMEM);
+ return;
+ }
+
+ e = (struct fuse_entry_param) {
+ .ino = ino->ino,
+ .attr = sbuf,
+ .generation = 1, /* FIXME */
+ .attr_timeout = 1.0,
+ .entry_timeout = 1.0
+ };
+
+ fuse_reply_entry(state->freq, &e);
+ TALLOC_FREE(state);
+}
+
+static void
+cli_ll_forget(fuse_req_t freq, fuse_ino_t ino, unsigned long nlookup)
+{
+ struct mount_state *mstate =
+ talloc_get_type_abort(fuse_req_userdata(freq),
+ struct mount_state);
+ struct inode_state *istate = NULL;
+
+ DBG_DEBUG("ino=%ju, nlookup=%lu\n", (uintmax_t)ino, nlookup);
+
+ istate = idr_find(mstate->ino_ctx, ino);
+ if (istate == NULL) {
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+ TALLOC_FREE(istate);
+ fuse_reply_none(freq);
+}
+
+struct ll_getattr_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ struct fuse_file_info fi;
+};
+
+static void cli_ll_getattr_done(struct tevent_req *req);
+
+static void cli_ll_getattr(fuse_req_t freq, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_getattr_state *state;
+ struct inode_state *istate;
+ struct tevent_req *req;
+
+ DBG_DEBUG("ino=%ju\n", (uintmax_t)ino);
+
+ istate = idr_find(mstate->ino_ctx, ino);
+ if (istate == NULL) {
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_getattr_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+
+ req = cli_get_unixattr_send(state, mstate->ev, mstate->cli,
+ istate->path);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_getattr_done, state);
+}
+
+static void cli_ll_getattr_done(struct tevent_req *req)
+{
+ struct ll_getattr_state *state = tevent_req_callback_data(
+ req, struct ll_getattr_state);
+ struct stat st;
+ NTSTATUS status;
+ int ret;
+
+ status = cli_get_unixattr_recv(req, &st);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+
+ ret = fuse_reply_attr(state->freq, &st, 1);
+ if (ret != 0) {
+ DBG_NOTICE("fuse_reply_attr failed: %s\n",
+ strerror(-errno));
+ }
+}
+
+
+struct ll_open_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ struct fuse_file_info fi;
+};
+
+static void cli_ll_open_done(struct tevent_req *req);
+
+static void cli_ll_open(fuse_req_t freq, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_open_state *state;
+ struct inode_state *istate;
+ struct tevent_req *req;
+ uint32_t acc;
+
+ DBG_DEBUG("ino=%ju\n", (uintmax_t)ino);
+
+ istate = idr_find(mstate->ino_ctx, ino);
+ if (istate == NULL) {
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_open_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+ state->fi = *fi;
+
+ switch (fi->flags & O_ACCMODE) {
+ case O_RDONLY:
+ acc = FILE_GENERIC_READ;
+ break;
+ case O_WRONLY:
+ acc = FILE_GENERIC_WRITE;
+ break;
+ case O_RDWR:
+ acc = FILE_GENERIC_READ|FILE_GENERIC_WRITE;
+ break;
+ default:
+ fuse_reply_err(freq, EACCES);
+ return;
+ }
+
+ req = cli_smb2_create_fnum_send(
+ state,
+ mstate->ev,
+ mstate->cli,
+ istate->path,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ acc,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_NON_DIRECTORY_FILE,
+ NULL);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_open_done, state);
+}
+
+static void cli_ll_open_done(struct tevent_req *req)
+{
+ struct ll_open_state *state = tevent_req_callback_data(
+ req, struct ll_open_state);
+ uint16_t fnum;
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(req, &fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+
+ state->fi.fh = fnum;
+ state->fi.direct_io = 0;
+ state->fi.keep_cache = 0;
+
+ fuse_reply_open(state->freq, &state->fi);
+
+ TALLOC_FREE(state);
+}
+
+struct ll_release_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ fuse_ino_t ino;
+};
+
+static void cli_ll_release_done(struct tevent_req *req);
+
+static void cli_ll_release(fuse_req_t freq, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_release_state *state;
+ struct inode_state *istate;
+ struct tevent_req *req;
+ uint16_t fnum;
+
+ DBG_DEBUG("ino=%ju\n", (uintmax_t)ino);
+
+ istate = idr_find(mstate->ino_ctx, ino);
+ if (istate == NULL) {
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_release_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+ state->ino = ino;
+
+ fnum = fi->fh;
+
+ req = cli_smb2_close_fnum_send(state, mstate->ev, mstate->cli, fnum, 0);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_release_done, state);
+}
+
+static void cli_ll_release_done(struct tevent_req *req)
+{
+ struct ll_release_state *state = tevent_req_callback_data(
+ req, struct ll_release_state);
+ struct inode_state *istate;
+ NTSTATUS status;
+
+ status = cli_smb2_close_fnum_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+
+ istate = idr_find(state->mstate->ino_ctx, state->ino);
+ if (istate == NULL) {
+ DEBUG(1, ("%s: inode %ju vanished!\n", __func__,
+ (uintmax_t)state->ino));
+ }
+ TALLOC_FREE(istate);
+
+ fuse_reply_err(state->freq, 0);
+ TALLOC_FREE(state);
+}
+
+struct ll_read_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+};
+
+static void cli_ll_read_done(struct tevent_req *req);
+
+static void cli_ll_read(fuse_req_t freq, fuse_ino_t ino,
+ size_t size, off_t off,
+ struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_read_state *state;
+ struct tevent_req *req;
+ uint16_t fnum;
+
+ DBG_DEBUG("ino=%ju, size=%zu, off=%ju\n", (uintmax_t)ino,
+ size, (uintmax_t)off);
+
+ state = talloc(mstate, struct ll_read_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+
+ fnum = fi->fh;
+
+ req = cli_smb2_read_send(state, mstate->ev, mstate->cli,
+ fnum, off, size);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_read_done, state);
+}
+
+static void cli_ll_read_done(struct tevent_req *req)
+{
+ struct ll_read_state *state = tevent_req_callback_data(
+ req, struct ll_read_state);
+ ssize_t received;
+ uint8_t *rcvbuf;
+ NTSTATUS status;
+
+ status = cli_smb2_read_recv(req, &received, &rcvbuf);
+ /* no talloc_free here yet */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
+ received = 0;
+ rcvbuf = NULL;
+ status = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+ fuse_reply_buf(state->freq, (char *)rcvbuf, received);
+ TALLOC_FREE(state);
+}
+
+struct ll_write_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+};
+
+static void cli_ll_write_done(struct tevent_req *req);
+
+static void cli_ll_write(fuse_req_t freq, fuse_ino_t ino, const char *buf,
+ size_t size, off_t off, struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct ll_write_state *state;
+ struct tevent_req *req;
+ uint16_t fnum;
+
+ DBG_DEBUG("ino=%ju, size=%zu, off=%ju\n", (uintmax_t)ino,
+ size, (uintmax_t)off);
+
+ state = talloc(mstate, struct ll_write_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+
+ fnum = fi->fh;
+
+ req = cli_smb2_write_send(state, mstate->ev, mstate->cli, fnum, 0,
+ (const uint8_t *)buf, off, size);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_write_done, state);
+}
+
+static void cli_ll_write_done(struct tevent_req *req)
+{
+ struct ll_write_state *state = tevent_req_callback_data(
+ req, struct ll_write_state);
+ size_t written;
+ NTSTATUS status;
+
+ status = cli_smb2_write_recv(req, &written);
+ /* no talloc_free here yet */
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+ fuse_reply_write(state->freq, written);
+ TALLOC_FREE(state);
+}
+
+
+struct ll_dir_state {
+ uint64_t fid_persistent;
+ uint64_t fid_volatile;
+
+ struct file_info *finfos;
+ unsigned num_finfos, num_sent;
+};
+
+static bool ll_dir_state_add(struct ll_dir_state *dir_state,
+ const char *name)
+{
+ struct file_info *tmp, *finfo;
+
+ tmp = talloc_realloc(dir_state, dir_state->finfos,
+ struct file_info, dir_state->num_finfos+1);
+ if (tmp == NULL) {
+ return false;
+ }
+ dir_state->finfos = tmp;
+ finfo = &dir_state->finfos[dir_state->num_finfos];
+
+ ZERO_STRUCTP(finfo);
+
+ finfo->name = talloc_strdup(dir_state->finfos, name);
+ if (finfo->name == NULL) {
+ return false;
+ }
+ dir_state->num_finfos += 1;
+
+ return true;
+}
+
+struct ll_opendir_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ struct fuse_file_info fi;
+ struct ll_dir_state *dir_state;
+};
+
+static void cli_ll_opendir_done(struct tevent_req *req);
+
+static void cli_ll_opendir(fuse_req_t freq, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct cli_state *cli = mstate->cli;
+ struct ll_opendir_state *state;
+ struct inode_state *istate;
+ struct tevent_req *req;
+
+ DBG_DEBUG("ino=%ju\n", (uintmax_t)ino);
+
+ istate = idr_find(mstate->ino_ctx, ino);
+ if (istate == NULL) {
+ fuse_reply_err(freq, ENOENT);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_opendir_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+ state->fi = *fi;
+
+ state->dir_state = talloc_zero(state, struct ll_dir_state);
+ if (state->dir_state == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+
+ req = smb2cli_create_send(
+ state, mstate->ev, cli->conn, cli->timeout,
+ cli->smb2.session, cli->smb2.tcon, istate->path,
+ 0, SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_GENERIC_READ, FILE_ATTRIBUTE_DIRECTORY,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, FILE_DIRECTORY_FILE, NULL);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_opendir_done, state);
+}
+
+static void cli_ll_opendir_done(struct tevent_req *req)
+{
+ struct ll_opendir_state *state = tevent_req_callback_data(
+ req, struct ll_opendir_state);
+ NTSTATUS status;
+
+ status = smb2cli_create_recv(
+ req,
+ &state->dir_state->fid_persistent,
+ &state->dir_state->fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("%s: smbcli_create_recv returned %s\n", __func__,
+ nt_errstr(status)));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+
+ state->fi.fh = (uint64_t)talloc_move(state->mstate, &state->dir_state);
+ state->fi.direct_io = 0;
+ state->fi.keep_cache = 0;
+
+ fuse_reply_open(state->freq, &state->fi);
+
+ TALLOC_FREE(state);
+}
+
+struct ll_readdir_state {
+ fuse_req_t freq;
+ struct ll_dir_state *dir_state;
+};
+
+static void cli_ll_readdir_done(struct tevent_req *subreq);
+static NTSTATUS cli_ll_readdir_one(const char *mnt, struct file_info *finfo,
+ const char *path, void *private_data);
+static void cli_ll_readdir_reply_one(fuse_req_t freq,
+ struct ll_dir_state *dir_state);
+
+static void cli_ll_readdir(fuse_req_t freq, fuse_ino_t ino, size_t size,
+ off_t off, struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct cli_state *cli = mstate->cli;
+ struct ll_dir_state *dir_state;
+ struct ll_readdir_state *state;
+ struct tevent_req *req;
+
+ DBG_DEBUG("ino=%ju, size=%zu, off=%ju\n", (uintmax_t)ino, size,
+ (uintmax_t)off);
+
+ dir_state = talloc_get_type_abort((void *)fi->fh, struct ll_dir_state);
+
+ if (dir_state->finfos != NULL) {
+ DBG_DEBUG("finfos=%p\n", dir_state->finfos);
+ cli_ll_readdir_reply_one(freq, dir_state);
+ return;
+ }
+
+ if (!ll_dir_state_add(dir_state, ".") ||
+ !ll_dir_state_add(dir_state, "..")) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+
+ state = talloc(mstate, struct ll_readdir_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->freq = freq;
+ state->dir_state = dir_state;
+
+ req = cli_smb2_listdir_send(
+ state, mstate->ev, cli->conn, cli->timeout,
+ cli->smb2.session, cli->smb2.tcon,
+ SMB2_FIND_ID_BOTH_DIRECTORY_INFO, 0, 0,
+ dir_state->fid_persistent, dir_state->fid_volatile,
+ "*", 0xffff,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN,
+ NULL, NULL, cli_ll_readdir_one, dir_state);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_readdir_done, state);
+}
+
+static void cli_ll_readdir_reply_one(fuse_req_t freq,
+ struct ll_dir_state *dir_state)
+{
+ struct file_info *finfo;
+ char buf[1024];
+ struct stat sbuf = {};
+ size_t buflen;
+
+ if (dir_state->num_finfos == dir_state->num_sent) {
+ DEBUG(10, ("%s: Done\n", __func__));
+ fuse_reply_buf(freq, NULL, 0);
+ return;
+ }
+
+ sbuf.st_mode = S_IFREG | 0755;
+ sbuf.st_ino = random(); /* FIXME :-) */
+ finfo = &dir_state->finfos[dir_state->num_sent];
+
+ DBG_DEBUG("Adding %s\n", finfo->name);
+
+ buflen = fuse_add_direntry(freq, buf, sizeof(buf),
+ finfo->name, &sbuf, 0);
+ fuse_reply_buf(freq, buf, buflen);
+ dir_state->num_sent += 1;
+ return;
+}
+
+static NTSTATUS cli_ll_readdir_one(const char *mnt, struct file_info *finfo,
+ const char *path, void *private_data)
+{
+ struct ll_dir_state *dir_state = talloc_get_type_abort(
+ private_data, struct ll_dir_state);
+
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ DEBUG(10, ("%s: Ignoring %s\n", __func__, finfo->name));
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("Got entry %s\n", finfo->name);
+
+ if (!ll_dir_state_add(dir_state, finfo->name)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static void cli_ll_readdir_done(struct tevent_req *req)
+{
+ struct ll_readdir_state *state = tevent_req_callback_data(
+ req, struct ll_readdir_state);
+ NTSTATUS status;
+
+ status = cli_smb2_listdir_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+ cli_ll_readdir_reply_one(state->freq, state->dir_state);
+ TALLOC_FREE(state);
+}
+
+
+struct ll_releasedir_state {
+ struct mount_state *mstate;
+ fuse_req_t freq;
+ struct ll_dir_state *dir_state;
+};
+
+static void cli_ll_releasedir_done(struct tevent_req *req);
+
+static void cli_ll_releasedir(fuse_req_t freq, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct mount_state *mstate = talloc_get_type_abort(
+ fuse_req_userdata(freq), struct mount_state);
+ struct cli_state *cli = mstate->cli;
+ struct ll_releasedir_state *state;
+ struct tevent_req *req;
+
+ DBG_DEBUG("ino=%ju\n", (uintmax_t)ino);
+
+ state = talloc(mstate, struct ll_releasedir_state);
+ if (state == NULL) {
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ state->mstate = mstate;
+ state->freq = freq;
+
+ state->dir_state = talloc_get_type_abort(
+ (void *)fi->fh, struct ll_dir_state);
+
+ req = smb2cli_close_send(state, mstate->ev, cli->conn, cli->timeout,
+ cli->smb2.session, cli->smb2.tcon, 0,
+ state->dir_state->fid_persistent,
+ state->dir_state->fid_volatile);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ fuse_reply_err(freq, ENOMEM);
+ return;
+ }
+ tevent_req_set_callback(req, cli_ll_releasedir_done, state);
+}
+
+static void cli_ll_releasedir_done(struct tevent_req *req)
+{
+ struct ll_releasedir_state *state = tevent_req_callback_data(
+ req, struct ll_releasedir_state);
+ NTSTATUS status;
+
+ status = smb2cli_close_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fuse_reply_err(state->freq, map_errno_from_nt_status(status));
+ return;
+ }
+ TALLOC_FREE(state->dir_state);
+ fuse_reply_err(state->freq, 0);
+ TALLOC_FREE(state);
+}
+
+static struct fuse_lowlevel_ops cli_ll_ops = {
+ .lookup = cli_ll_lookup,
+ .forget = cli_ll_forget,
+ .getattr = cli_ll_getattr,
+ .open = cli_ll_open,
+ .create = cli_ll_create,
+ .release = cli_ll_release,
+ .read = cli_ll_read,
+ .write = cli_ll_write,
+ .opendir = cli_ll_opendir,
+ .readdir = cli_ll_readdir,
+ .releasedir = cli_ll_releasedir,
+};
+
+static void fuse_chan_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+static void fuse_chan_signal_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data);
+
+static int mount_state_destructor(struct mount_state *s);
+
+int do_mount(struct cli_state *cli, const char *mountpoint)
+{
+ struct mount_state *state;
+ struct inode_state *ino;
+ struct fuse_args args = { 0 };
+ int fd;
+ int ret = 1;
+
+ state = talloc_zero(talloc_tos(), struct mount_state);
+ if (state == NULL) {
+ fprintf(stderr, "talloc failed\n");
+ return 1;
+ }
+
+ state->ev = tevent_context_init(state);
+ if (state->ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ TALLOC_FREE(state);
+ return 1;
+ }
+
+ state->ino_ctx = idr_init(state);
+ if (state->ino_ctx == NULL) {
+ fprintf(stderr, "idr_init failed\n");
+ TALLOC_FREE(state);
+ return 1;
+ }
+
+ state->ino_parent = talloc_new(state);
+ if (state->ino_parent == NULL) {
+ fprintf(stderr, "talloc_new failed\n");
+ TALLOC_FREE(state);
+ return 1;
+ }
+
+ talloc_set_destructor(state, mount_state_destructor);
+
+ ino = inode_state_new(state, "");
+ if (ino == NULL) {
+ fprintf(stderr, "inode_state_new failed\n");
+ TALLOC_FREE(state);
+ return 1;
+ }
+ if (ino->ino != FUSE_ROOT_ID) {
+ fprintf(stderr, "first inode gave %d, expected %d\n",
+ (int)ino->ino, FUSE_ROOT_ID);
+ TALLOC_FREE(state);
+ return 1;
+ }
+
+ state->cli = cli;
+
+ state->ch = fuse_mount(mountpoint, &args);
+ if (state->ch == NULL) {
+ perror("fuse_mount failed");
+ goto fail_free;
+ }
+
+ state->bufsize = fuse_chan_bufsize(state->ch);
+ state->buf = talloc_array(state, char, state->bufsize);
+ if (state->buf == NULL) {
+ fprintf(stderr, "talloc failed\n");
+ goto fail_unmount;
+ }
+
+ fd = fuse_chan_fd(state->ch);
+
+ state->fde = tevent_add_fd(state->ev, state, fd, TEVENT_FD_READ,
+ fuse_chan_fd_handler, state);
+ if (state->fde == NULL) {
+ fprintf(stderr, "tevent_add_fd failed\n");
+ goto fail_unmount;
+ }
+
+ state->signal_ev = tevent_add_signal(state->ev, state, SIGINT, 0,
+ fuse_chan_signal_handler, state);
+ if (state->signal_ev == NULL) {
+ fprintf(stderr, "tevent_add_signal failed\n");
+ goto fail_unmount;
+ }
+
+ state->se = fuse_lowlevel_new(&args, &cli_ll_ops, sizeof(cli_ll_ops),
+ state);
+ if (state->se == NULL) {
+ perror("fuse_lowlevel_new failed");
+ goto fail_unmount;
+ }
+
+ fuse_session_add_chan(state->se, state->ch);
+
+ while (!state->done) {
+ ret = tevent_loop_once(state->ev);
+ if (ret == -1) {
+ perror("tevent_loop_once failed");
+ break;
+ }
+ }
+
+ fuse_session_remove_chan(state->ch);
+ fuse_session_destroy(state->se);
+fail_unmount:
+ fuse_unmount(mountpoint, state->ch);
+fail_free:
+ TALLOC_FREE(state);
+ return ret;
+}
+
+static int mount_state_destructor(struct mount_state *s)
+{
+ TALLOC_FREE(s->ino_parent);
+ return 0;
+}
+
+
+static void fuse_chan_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct mount_state *state = talloc_get_type_abort(
+ private_data, struct mount_state);
+ int recvd;
+
+ if ((flags & TEVENT_FD_READ) == 0) {
+ return;
+ }
+
+ recvd = fuse_chan_recv(&state->ch, state->buf, state->bufsize);
+ if (recvd <= 0) {
+ state->done = true;
+ return;
+ }
+ fuse_session_process(state->se, state->buf, recvd, state->ch);
+}
+
+static void fuse_chan_signal_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct mount_state *state = talloc_get_type_abort(
+ private_data, struct mount_state);
+ state->done = true;
+}
diff --git a/examples/fuse/clifuse.h b/examples/fuse/clifuse.h
new file mode 100644
index 0000000..62a2666
--- /dev/null
+++ b/examples/fuse/clifuse.h
@@ -0,0 +1,27 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * fusermount smb2 client
+ * Copyright (C) Volker Lendecke 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __EXAMPLES_FUSE_CLIFUSE_H__
+#define __EXAMPLES_FUSE_CLIFUSE_H__
+
+struct cli_state;
+
+int do_mount(struct cli_state *cli, const char *mountpoint);
+
+#endif
diff --git a/examples/fuse/smb2mount.c b/examples/fuse/smb2mount.c
new file mode 100644
index 0000000..0594ced
--- /dev/null
+++ b/examples/fuse/smb2mount.c
@@ -0,0 +1,160 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * fusermount smb2 client
+ *
+ * Copyright (C) Volker Lendecke 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include "popt.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/param/param.h"
+#include "client.h"
+#include "libsmb/proto.h"
+#include "clifuse.h"
+
+static struct cli_state *connect_one(struct cli_credentials *creds,
+ const char *server, int port,
+ const char *share)
+{
+ struct cli_state *c = NULL;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+
+ nt_status = cli_full_connection_creds(&c, lp_netbios_name(), server,
+ NULL, port,
+ share, "?????",
+ creds,
+ flags);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("cli_full_connection failed! (%s)\n",
+ nt_errstr(nt_status));
+ return NULL;
+ }
+
+ return c;
+}
+
+int main(int argc, char *argv[])
+{
+ const char **argv_const = discard_const_p(const char *, argv);
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+ poptContext pc;
+ int opt, ret;
+ int port = 0;
+ char *unc, *mountpoint, *server, *share;
+ struct cli_state *cli;
+ struct cli_credentials *creds = NULL;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CREDENTIALS
+ { "port", 'p', POPT_ARG_INT, &port, 'p', "Port to connect to",
+ "PORT" },
+ POPT_TABLEEND
+ };
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "client min protocol", "SMB2");
+ lpcfg_set_cmdline(lp_ctx, "client max protocol", "SMB3_11");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "//server1/share1 mountpoint");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ case 'p':
+ break;
+ default:
+ fprintf(stderr, "Unknown Option: %c\n", opt);
+ exit(1);
+ }
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ return -1;
+ }
+ unc = talloc_strdup(frame, poptGetArg(pc));
+ if (unc == NULL) {
+ return -1;
+ }
+ string_replace(unc,'/','\\');
+
+ if (!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ return -1;
+ }
+ mountpoint = talloc_strdup(frame, poptGetArg(pc));
+ if (mountpoint == NULL) {
+ return -1;
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ server = talloc_strdup(frame, unc+2);
+ if (!server) {
+ return -1;
+ }
+ share = strchr_m(server,'\\');
+ if (!share) {
+ fprintf(stderr, "Invalid argument: %s\n", server);
+ return -1;
+ }
+
+ *share = 0;
+ share++;
+
+ creds = samba_cmdline_get_creds();
+
+ cli = connect_one(creds, server, port, share);
+ if (cli == NULL) {
+ return -1;
+ }
+
+ ret = do_mount(cli, mountpoint);
+ if (ret != 0) {
+ fprintf(stderr, "mount failed\n");
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/examples/fuse/wscript b/examples/fuse/wscript
new file mode 100644
index 0000000..1c85139
--- /dev/null
+++ b/examples/fuse/wscript
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+def configure(conf):
+ # Check for fuse support
+ if conf.CHECK_CODE('''
+ #define FUSE_USE_VERSION 26
+ #define _FILE_OFFSET_BITS 64
+ #include "fuse/fuse_lowlevel.h"
+ int main(void) { return 0; }
+ ''', 'HAVE_FUSE_FUSE_LOWLEVEL_H',
+ addmain=False,
+ execute=False) and conf.CHECK_FUNCS_IN('fuse_mount',
+ 'fuse'):
+ conf.DEFINE('HAVE_FUSE', 1)
diff --git a/examples/fuse/wscript_build b/examples/fuse/wscript_build
new file mode 100644
index 0000000..ceef925
--- /dev/null
+++ b/examples/fuse/wscript_build
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+
+if bld.env.HAVE_FUSE:
+ bld.SAMBA_BINARY('smb2mount',
+ source='smb2mount.c clifuse.c',
+ deps='smbconf CMDLINE_S3 libsmb fuse',
+ install=False)