/*
- * Copyright (c) James Peach 2006
+ * Copyright (c) James Peach 2006, 2007
+ * Copyright (c) David Losada Carballo 2007
*
* 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
*/
#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_unix.h"
/* Commit data module.
*
* Tunables:
*
* commit: dthresh Amount of dirty data that can accumulate
- * before we commit (sync) it.
+ * before we commit (sync) it.
*
* commit: debug Debug level at which to emit messages.
*
+ * commit: eof mode String. Tunes how the module tries to guess when
+ * the client has written the last bytes of the file.
+ * Possible values (default = hinted):
+ *
+ * (*) = hinted Some clients (i.e. Windows Explorer) declare the
+ * size of the file before transferring it. With this
+ * option, we remember that hint, and commit after
+ * writing in that file position. If the client
+ * doesn't declare the size of file, committing on EOF
+ * is not triggered.
+ *
+ * = growth Commits after a write operation has made the file
+ * size grow. If the client declares a file size, it
+ * refrains to commit until the file has reached it.
+ * Useful for defeating writeback on NFS shares.
+ *
*/
#define MODULE "commit"
static int module_debug;
+enum eof_mode
+{
+ EOF_NONE = 0x0000,
+ EOF_HINTED = 0x0001,
+ EOF_GROWTH = 0x0002
+};
+
struct commit_info
{
- SMB_OFF_T dbytes; /* Dirty (uncommitted) bytes */
- SMB_OFF_T dthresh; /* Dirty data threshold */
+ /* For chunk-based commits */
+ off_t dbytes; /* Dirty (uncommitted) bytes */
+ off_t dthresh; /* Dirty data threshold */
+ /* For commits on EOF */
+ enum eof_mode on_eof;
+ off_t eof; /* Expected file size */
};
-static void commit_all(
+static int commit_do(
+ struct commit_info * c,
+ int fd)
+{
+ int result;
+
+ DEBUG(module_debug,
+ ("%s: flushing %lu dirty bytes\n",
+ MODULE, (unsigned long)c->dbytes));
+
+#if defined(HAVE_FDATASYNC)
+ result = fdatasync(fd);
+#elif defined(HAVE_FSYNC)
+ result = fsync(fd);
+#else
+ DEBUG(0, ("%s: WARNING: no commit support on this platform\n",
+ MODULE));
+ result = 0
+#endif
+ if (result == 0) {
+ c->dbytes = 0; /* on success, no dirty bytes */
+ }
+ return result;
+}
+
+static int commit_all(
struct vfs_handle_struct * handle,
files_struct * fsp)
{
struct commit_info *c;
- if ((c = VFS_FETCH_FSP_EXTENSION(handle, fsp))) {
+ if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))) {
if (c->dbytes) {
DEBUG(module_debug,
("%s: flushing %lu dirty bytes\n",
MODULE, (unsigned long)c->dbytes));
- fdatasync(fsp->fh->fd);
- c->dbytes = 0;
+ return commit_do(c, fsp_get_io_fd(fsp));
}
}
+ return 0;
}
-static void commit(
+static int commit(
struct vfs_handle_struct * handle,
files_struct * fsp,
+ off_t offset,
ssize_t last_write)
{
struct commit_info *c;
- if ((c = VFS_FETCH_FSP_EXTENSION(handle, fsp))) {
+ if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))
+ == NULL) {
+ return 0;
+ }
- if (last_write > 0) {
- c->dbytes += last_write;
- }
+ c->dbytes += last_write; /* dirty bytes always counted */
- if (c->dbytes > c->dthresh) {
- DEBUG(module_debug,
- ("%s: flushing %lu dirty bytes\n",
- MODULE, (unsigned long)c->dbytes));
+ if (c->dthresh && (c->dbytes > c->dthresh)) {
+ return commit_do(c, fsp_get_io_fd(fsp));
+ }
- fdatasync(fsp->fh->fd);
- c->dbytes = 0;
- }
- }
+ /* Return if we are not in EOF mode or if we have temporarily opted
+ * out of it.
+ */
+ if (c->on_eof == EOF_NONE || c->eof < 0) {
+ return 0;
+ }
+
+ /* This write hit or went past our cache the file size. */
+ if ((offset + last_write) >= c->eof) {
+ if (commit_do(c, fsp_get_io_fd(fsp)) == -1) {
+ return -1;
+ }
+
+ /* Hinted mode only commits the first time we hit EOF. */
+ if (c->on_eof == EOF_HINTED) {
+ c->eof = -1;
+ } else if (c->on_eof == EOF_GROWTH) {
+ c->eof = offset + last_write;
+ }
+ }
+
+ return 0;
}
static int commit_connect(
const char * service,
const char * user)
{
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
module_debug = lp_parm_int(SNUM(handle->conn), MODULE, "debug", 100);
- return SMB_VFS_NEXT_CONNECT(handle, service, user);
+ return 0;
}
-static int commit_open(
- vfs_handle_struct * handle,
- const char * fname,
- files_struct * fsp,
- int flags,
- mode_t mode)
+static int commit_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
{
- SMB_OFF_T dthresh;
+ off_t dthresh;
+ const char *eof_mode;
+ struct commit_info *c = NULL;
+ int fd;
/* Don't bother with read-only files. */
- if ((flags & O_ACCMODE) == O_RDONLY) {
- return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
+ if ((how->flags & O_ACCMODE) == O_RDONLY) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
}
+ /* Read and check module configuration */
dthresh = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
MODULE, "dthresh", NULL));
- if (dthresh > 0) {
- struct commit_info * c;
- c = VFS_ADD_FSP_EXTENSION(handle, fsp, struct commit_info);
+ eof_mode = lp_parm_const_string(SNUM(handle->conn),
+ MODULE, "eof mode", "none");
+
+ if (dthresh > 0 || !strequal(eof_mode, "none")) {
+ c = VFS_ADD_FSP_EXTENSION(
+ handle, fsp, struct commit_info, NULL);
+ /* Process main tunables */
if (c) {
c->dthresh = dthresh;
c->dbytes = 0;
+ c->on_eof = EOF_NONE;
+ c->eof = 0;
+ }
+ }
+ /* Process eof_mode tunable */
+ if (c) {
+ if (strequal(eof_mode, "hinted")) {
+ c->on_eof = EOF_HINTED;
+ } else if (strequal(eof_mode, "growth")) {
+ c->on_eof = EOF_GROWTH;
+ }
+ }
+
+ fd = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+ if (fd == -1) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ return fd;
+ }
+
+ /* EOF commit modes require us to know the initial file size. */
+ if (c && (c->on_eof != EOF_NONE)) {
+ SMB_STRUCT_STAT st;
+ /*
+ * Setting the fd of the FSP is a hack
+ * but also practiced elsewhere -
+ * needed for calling the VFS.
+ */
+ fsp_set_fd(fsp, fd);
+ if (SMB_VFS_FSTAT(fsp, &st) == -1) {
+ int saved_errno = errno;
+ SMB_VFS_CLOSE(fsp);
+ fsp_set_fd(fsp, -1);
+ errno = saved_errno;
+ return -1;
}
+ c->eof = st.st_ex_size;
}
- return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
+ return fd;
}
-static ssize_t commit_write(
+static ssize_t commit_pwrite(
vfs_handle_struct * handle,
files_struct * fsp,
- int fd,
- void * data,
- size_t count)
+ const void * data,
+ size_t count,
+ off_t offset)
{
ssize_t ret;
- ret = SMB_VFS_NEXT_WRITE(handle, fsp, fd, data, count);
- commit(handle, fsp, ret);
+ ret = SMB_VFS_NEXT_PWRITE(handle, fsp, data, count, offset);
+ if (ret > 0) {
+ if (commit(handle, fsp, offset, ret) == -1) {
+ return -1;
+ }
+ }
return ret;
}
-static ssize_t commit_pwrite(
- vfs_handle_struct * handle,
- files_struct * fsp,
- int fd,
- void * data,
- size_t count,
- SMB_OFF_T offset)
+struct commit_pwrite_state {
+ struct vfs_handle_struct *handle;
+ struct files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void commit_pwrite_written(struct tevent_req *subreq);
+
+static struct tevent_req *commit_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
{
- ssize_t ret;
+ struct tevent_req *req, *subreq;
+ struct commit_pwrite_state *state;
- ret = SMB_VFS_NEXT_PWRITE(handle, fsp, fd, data, count, offset);
- commit(handle, fsp, ret);
+ req = tevent_req_create(mem_ctx, &state, struct commit_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->handle = handle;
+ state->fsp = fsp;
- return ret;
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, commit_pwrite_written, req);
+ return req;
}
-static ssize_t commit_close(
+static void commit_pwrite_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct commit_pwrite_state *state = tevent_req_data(
+ req, struct commit_pwrite_state);
+ int commit_ret;
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ if (state->ret <= 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Ok, this is a sync fake. We should make the sync async as well, but
+ * I'm too lazy for that right now -- vl
+ */
+ commit_ret = commit(state->handle,
+ state->fsp,
+ fh_get_pos(state->fsp->fh),
+ state->ret);
+
+ if (commit_ret == -1) {
+ state->ret = -1;
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t commit_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct commit_pwrite_state *state =
+ tevent_req_data(req, struct commit_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int commit_close(
vfs_handle_struct * handle,
- files_struct * fsp,
- int fd)
+ files_struct * fsp)
{
+ /* Commit errors not checked, close() will find them again */
commit_all(handle, fsp);
- return SMB_VFS_NEXT_CLOSE(handle, fsp, fd);
+ return SMB_VFS_NEXT_CLOSE(handle, fsp);
}
-static vfs_op_tuple commit_ops [] =
+static int commit_ftruncate(
+ vfs_handle_struct * handle,
+ files_struct * fsp,
+ off_t len)
{
- {SMB_VFS_OP(commit_open),
- SMB_VFS_OP_OPEN, SMB_VFS_LAYER_TRANSPARENT},
- {SMB_VFS_OP(commit_close),
- SMB_VFS_OP_CLOSE, SMB_VFS_LAYER_TRANSPARENT},
- {SMB_VFS_OP(commit_write),
- SMB_VFS_OP_WRITE, SMB_VFS_LAYER_TRANSPARENT},
- {SMB_VFS_OP(commit_pwrite),
- SMB_VFS_OP_PWRITE, SMB_VFS_LAYER_TRANSPARENT},
- {SMB_VFS_OP(commit_connect),
- SMB_VFS_OP_CONNECT, SMB_VFS_LAYER_TRANSPARENT},
-
- {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP}
+ int result;
+
+ result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
+ if (result == 0) {
+ struct commit_info *c;
+ if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(
+ handle, fsp))) {
+ commit(handle, fsp, len, 0);
+ c->eof = len;
+ }
+ }
+
+ return result;
+}
+
+static struct vfs_fn_pointers vfs_commit_fns = {
+ .openat_fn = commit_openat,
+ .close_fn = commit_close,
+ .pwrite_fn = commit_pwrite,
+ .pwrite_send_fn = commit_pwrite_send,
+ .pwrite_recv_fn = commit_pwrite_recv,
+ .connect_fn = commit_connect,
+ .ftruncate_fn = commit_ftruncate
};
-NTSTATUS vfs_commit_init(void);
-NTSTATUS vfs_commit_init(void)
+static_decl_vfs;
+NTSTATUS vfs_commit_init(TALLOC_CTX *ctx)
{
- return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE, commit_ops);
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE,
+ &vfs_commit_fns);
}
+