summaryrefslogtreecommitdiffstats
path: root/source4/ntvfs/posix
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/ntvfs/posix
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/ntvfs/posix')
-rw-r--r--source4/ntvfs/posix/posix_eadb.c299
-rw-r--r--source4/ntvfs/posix/posix_eadb.h20
-rw-r--r--source4/ntvfs/posix/pvfs_acl.c1083
-rw-r--r--source4/ntvfs/posix/pvfs_acl_nfs4.c199
-rw-r--r--source4/ntvfs/posix/pvfs_acl_xattr.c104
-rw-r--r--source4/ntvfs/posix/pvfs_dirlist.c411
-rw-r--r--source4/ntvfs/posix/pvfs_fileinfo.c158
-rw-r--r--source4/ntvfs/posix/pvfs_flush.c80
-rw-r--r--source4/ntvfs/posix/pvfs_fsinfo.c224
-rw-r--r--source4/ntvfs/posix/pvfs_ioctl.c82
-rw-r--r--source4/ntvfs/posix/pvfs_lock.c404
-rw-r--r--source4/ntvfs/posix/pvfs_mkdir.c196
-rw-r--r--source4/ntvfs/posix/pvfs_notify.c300
-rw-r--r--source4/ntvfs/posix/pvfs_open.c2094
-rw-r--r--source4/ntvfs/posix/pvfs_oplock.c307
-rw-r--r--source4/ntvfs/posix/pvfs_qfileinfo.c468
-rw-r--r--source4/ntvfs/posix/pvfs_read.c103
-rw-r--r--source4/ntvfs/posix/pvfs_rename.c675
-rw-r--r--source4/ntvfs/posix/pvfs_resolve.c826
-rw-r--r--source4/ntvfs/posix/pvfs_search.c865
-rw-r--r--source4/ntvfs/posix/pvfs_seek.c65
-rw-r--r--source4/ntvfs/posix/pvfs_setfileinfo.c884
-rw-r--r--source4/ntvfs/posix/pvfs_shortname.c699
-rw-r--r--source4/ntvfs/posix/pvfs_streams.c556
-rw-r--r--source4/ntvfs/posix/pvfs_sys.c662
-rw-r--r--source4/ntvfs/posix/pvfs_unlink.c276
-rw-r--r--source4/ntvfs/posix/pvfs_util.c206
-rw-r--r--source4/ntvfs/posix/pvfs_wait.c212
-rw-r--r--source4/ntvfs/posix/pvfs_write.c145
-rw-r--r--source4/ntvfs/posix/pvfs_xattr.c488
-rw-r--r--source4/ntvfs/posix/python/pyposix_eadb.c140
-rw-r--r--source4/ntvfs/posix/python/pyxattr_native.c129
-rw-r--r--source4/ntvfs/posix/python/pyxattr_tdb.c177
-rw-r--r--source4/ntvfs/posix/vfs_posix.c426
-rw-r--r--source4/ntvfs/posix/vfs_posix.h290
-rw-r--r--source4/ntvfs/posix/wscript_build61
-rw-r--r--source4/ntvfs/posix/xattr_system.c145
37 files changed, 14459 insertions, 0 deletions
diff --git a/source4/ntvfs/posix/posix_eadb.c b/source4/ntvfs/posix/posix_eadb.c
new file mode 100644
index 0000000..2d181fd
--- /dev/null
+++ b/source4/ntvfs/posix/posix_eadb.c
@@ -0,0 +1,299 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - xattr support using a tdb
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#ifdef WITH_NTVFS_FILESERVER
+#include "vfs_posix.h"
+#endif
+#include "posix_eadb.h"
+
+#define XATTR_LIST_ATTR ".xattr_list"
+
+/*
+ we need to maintain a list of attributes on each file, so that unlink
+ can automatically clean them up
+*/
+static NTSTATUS posix_eadb_add_list(struct tdb_wrap *ea_tdb, TALLOC_CTX *ctx, const char *attr_name,
+ const char *fname, int fd)
+{
+ DATA_BLOB blob;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ size_t len;
+
+ if (strcmp(attr_name, XATTR_LIST_ATTR) == 0) {
+ return NT_STATUS_OK;
+ }
+
+ mem_ctx = talloc_new(ctx);
+
+ status = pull_xattr_blob_tdb_raw(ea_tdb, mem_ctx, XATTR_LIST_ATTR,
+ fname, fd, 100, &blob);
+ if (NT_STATUS_IS_OK(status)) {
+ const char *s;
+
+ for (s = (const char *)blob.data;
+ s < (const char *)(blob.data + blob.length);
+ s += strlen(s) + 1) {
+ if (strcmp(attr_name, s) == 0) {
+ talloc_free(mem_ctx);
+ return NT_STATUS_OK;
+ }
+ }
+ } else {
+ blob = data_blob(NULL, 0);
+ /* No need to parse an empty blob */
+ }
+
+ len = strlen(attr_name) + 1;
+
+ blob.data = talloc_realloc(mem_ctx, blob.data, uint8_t, blob.length + len);
+ if (blob.data == NULL) {
+ talloc_free(mem_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ memcpy(blob.data + blob.length, attr_name, len);
+ blob.length += len;
+
+ status = push_xattr_blob_tdb_raw(ea_tdb, XATTR_LIST_ATTR, fname, fd, &blob);
+ talloc_free(mem_ctx);
+
+ return status;
+}
+
+/*
+ form a key for using in the ea_tdb
+*/
+static NTSTATUS get_ea_tdb_key(TALLOC_CTX *mem_ctx,
+ const char *attr_name,
+ const char *fname, int fd,
+ TDB_DATA *key)
+{
+ struct stat st;
+ size_t len = strlen(attr_name);
+
+ if (fd == -1) {
+ if (stat(fname, &st) == -1) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ } else {
+ if (fstat(fd, &st) == -1) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ }
+
+ key->dptr = talloc_array(mem_ctx, uint8_t, 16 + len);
+ if (key->dptr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ key->dsize = 16 + len;
+
+ SBVAL(key->dptr, 0, st.st_dev);
+ SBVAL(key->dptr, 8, st.st_ino);
+ memcpy(key->dptr+16, attr_name, len);
+
+ return NT_STATUS_OK;
+}
+
+
+
+/*
+ pull a xattr as a blob, using the ea_tdb_context tdb
+*/
+NTSTATUS pull_xattr_blob_tdb_raw(struct tdb_wrap *ea_tdb,
+ TALLOC_CTX *mem_ctx,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ size_t estimated_size,
+ DATA_BLOB *blob)
+{
+ TDB_DATA tkey, tdata;
+ NTSTATUS status;
+
+ status = get_ea_tdb_key(mem_ctx, attr_name, fname, fd, &tkey);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ tdata = tdb_fetch(ea_tdb->tdb, tkey);
+ if (tdata.dptr == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ *blob = data_blob_talloc(mem_ctx, tdata.dptr, tdata.dsize);
+ free(tdata.dptr);
+ if (blob->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ push a xattr as a blob, using ea_tdb
+*/
+NTSTATUS push_xattr_blob_tdb_raw(struct tdb_wrap *ea_tdb,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ const DATA_BLOB *blob)
+{
+ TDB_DATA tkey, tdata;
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx = talloc_new(ea_tdb);
+ if (!mem_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = get_ea_tdb_key(mem_ctx, attr_name, fname, fd, &tkey);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(mem_ctx);
+ return status;
+ }
+
+ tdata.dptr = blob->data;
+ tdata.dsize = blob->length;
+
+ if (tdb_chainlock(ea_tdb->tdb, tkey) != 0) {
+ talloc_free(mem_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = posix_eadb_add_list(ea_tdb,mem_ctx, attr_name, fname, fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(mem_ctx);
+ goto done;
+ }
+
+ if (tdb_store(ea_tdb->tdb, tkey, tdata, TDB_REPLACE) != 0) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+done:
+ tdb_chainunlock(ea_tdb->tdb, tkey);
+ talloc_free(mem_ctx);
+ return status;
+}
+
+
+/*
+ delete a xattr
+*/
+NTSTATUS delete_posix_eadb_raw(struct tdb_wrap *ea_tdb, const char *attr_name,
+ const char *fname, int fd)
+{
+ TDB_DATA tkey;
+ NTSTATUS status;
+
+ status = get_ea_tdb_key(NULL, attr_name, fname, fd, &tkey);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (tdb_delete(ea_tdb->tdb, tkey) != 0) {
+ talloc_free(tkey.dptr);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ talloc_free(tkey.dptr);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ delete all xattrs for a file
+*/
+NTSTATUS unlink_posix_eadb_raw(struct tdb_wrap *ea_tdb, const char *fname, int fd)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(ea_tdb);
+ DATA_BLOB blob;
+ const char *s;
+ NTSTATUS status;
+
+ status = pull_xattr_blob_tdb_raw(ea_tdb, mem_ctx, XATTR_LIST_ATTR,
+ fname, fd, 100, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ for (s=(const char *)blob.data; s < (const char *)(blob.data+blob.length); s += strlen(s) + 1) {
+ delete_posix_eadb_raw(ea_tdb, s, fname, -1);
+ }
+
+ status = delete_posix_eadb_raw(ea_tdb, XATTR_LIST_ATTR, fname, fd);
+ talloc_free(mem_ctx);
+ return status;
+}
+
+/*
+ list all xattrs for a file
+*/
+NTSTATUS list_posix_eadb_raw(struct tdb_wrap *ea_tdb, TALLOC_CTX *mem_ctx,
+ const char *fname, int fd,
+ DATA_BLOB *list)
+{
+ return pull_xattr_blob_tdb_raw(ea_tdb, mem_ctx, XATTR_LIST_ATTR,
+ fname, fd, 100, list);
+}
+
+#ifdef WITH_NTVFS_FILESERVER
+NTSTATUS pull_xattr_blob_tdb(struct pvfs_state *pvfs_state,
+ TALLOC_CTX *mem_ctx,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ size_t estimated_size,
+ DATA_BLOB *blob)
+{
+ return pull_xattr_blob_tdb_raw(pvfs_state->ea_db,mem_ctx,attr_name,fname,fd,estimated_size,blob);
+}
+
+NTSTATUS push_xattr_blob_tdb(struct pvfs_state *pvfs_state,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ const DATA_BLOB *blob)
+{
+ return push_xattr_blob_tdb_raw(pvfs_state->ea_db, attr_name, fname, fd, blob);
+}
+
+/*
+ delete a xattr
+*/
+NTSTATUS delete_posix_eadb(struct pvfs_state *pvfs_state, const char *attr_name,
+ const char *fname, int fd)
+{
+ return delete_posix_eadb_raw(pvfs_state->ea_db,
+ attr_name, fname, fd);
+}
+
+/*
+ delete all xattrs for a file
+*/
+NTSTATUS unlink_posix_eadb(struct pvfs_state *pvfs_state, const char *fname)
+{
+ return unlink_posix_eadb_raw(pvfs_state->ea_db, fname, -1);
+}
+
+#endif
diff --git a/source4/ntvfs/posix/posix_eadb.h b/source4/ntvfs/posix/posix_eadb.h
new file mode 100644
index 0000000..14b9439
--- /dev/null
+++ b/source4/ntvfs/posix/posix_eadb.h
@@ -0,0 +1,20 @@
+/*
+ Unix SMB/CIFS implementation. Xattr manipulation bindings.
+ Copyright (C) Andrew Bartlett 2011
+
+ 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/>.
+*/
+
+struct pvfs_state;
+#include "source4/ntvfs/posix/posix_eadb_proto.h"
diff --git a/source4/ntvfs/posix/pvfs_acl.c b/source4/ntvfs/posix/pvfs_acl.c
new file mode 100644
index 0000000..e643293
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_acl.c
@@ -0,0 +1,1083 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - ACL support
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "system/passwd.h"
+#include "auth/auth.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/xattr.h"
+#include "libcli/security/security.h"
+#include "param/param.h"
+#include "../lib/util/unix_privs.h"
+#include "lib/util/samba_modules.h"
+
+/* the list of currently registered ACL backends */
+static struct pvfs_acl_backend {
+ const struct pvfs_acl_ops *ops;
+} *backends = NULL;
+static int num_backends;
+
+/*
+ register a pvfs acl backend.
+
+ The 'name' can be later used by other backends to find the operations
+ structure for this backend.
+*/
+NTSTATUS pvfs_acl_register(TALLOC_CTX *ctx, const struct pvfs_acl_ops *ops)
+{
+ struct pvfs_acl_ops *new_ops;
+
+ if (pvfs_acl_backend_byname(ops->name) != NULL) {
+ DEBUG(0,("pvfs acl backend '%s' already registered\n", ops->name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ backends = talloc_realloc(ctx, backends,
+ struct pvfs_acl_backend, num_backends+1);
+ NT_STATUS_HAVE_NO_MEMORY(backends);
+
+ new_ops = (struct pvfs_acl_ops *)talloc_memdup(backends, ops, sizeof(*ops));
+ new_ops->name = talloc_strdup(new_ops, ops->name);
+
+ backends[num_backends].ops = new_ops;
+
+ num_backends++;
+
+ DEBUG(3,("NTVFS backend '%s' registered\n", ops->name));
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ return the operations structure for a named backend
+*/
+const struct pvfs_acl_ops *pvfs_acl_backend_byname(const char *name)
+{
+ int i;
+
+ for (i=0;i<num_backends;i++) {
+ if (strcmp(backends[i].ops->name, name) == 0) {
+ return backends[i].ops;
+ }
+ }
+
+ return NULL;
+}
+
+NTSTATUS pvfs_acl_init(void)
+{
+ static bool initialized = false;
+#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *);
+ STATIC_pvfs_acl_MODULES_PROTO;
+ init_module_fn static_init[] = { STATIC_pvfs_acl_MODULES };
+ init_module_fn *shared_init;
+
+ if (initialized) return NT_STATUS_OK;
+ initialized = true;
+
+ shared_init = load_samba_modules(NULL, "pvfs_acl");
+
+ run_init_functions(NULL, static_init);
+ run_init_functions(NULL, shared_init);
+
+ talloc_free(shared_init);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ map a single access_mask from generic to specific bits for files/dirs
+*/
+static uint32_t pvfs_translate_mask(uint32_t access_mask)
+{
+ if (access_mask & SEC_MASK_GENERIC) {
+ if (access_mask & SEC_GENERIC_READ) access_mask |= SEC_RIGHTS_FILE_READ;
+ if (access_mask & SEC_GENERIC_WRITE) access_mask |= SEC_RIGHTS_FILE_WRITE;
+ if (access_mask & SEC_GENERIC_EXECUTE) access_mask |= SEC_RIGHTS_FILE_EXECUTE;
+ if (access_mask & SEC_GENERIC_ALL) access_mask |= SEC_RIGHTS_FILE_ALL;
+ access_mask &= ~SEC_MASK_GENERIC;
+ }
+ return access_mask;
+}
+
+
+/*
+ map any generic access bits in the given acl
+ this relies on the fact that the mappings for files and directories
+ are the same
+*/
+static void pvfs_translate_generic_bits(struct security_acl *acl)
+{
+ unsigned i;
+
+ if (!acl) return;
+
+ for (i=0;i<acl->num_aces;i++) {
+ struct security_ace *ace = &acl->aces[i];
+ ace->access_mask = pvfs_translate_mask(ace->access_mask);
+ }
+}
+
+
+/*
+ setup a default ACL for a file
+*/
+static NTSTATUS pvfs_default_acl(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name, int fd,
+ struct security_descriptor **psd)
+{
+ struct security_descriptor *sd;
+ NTSTATUS status;
+ struct security_ace ace = {};
+ mode_t mode;
+ struct id_map *ids;
+
+ *psd = security_descriptor_initialise(req);
+ if (*psd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ sd = *psd;
+
+ ids = talloc_zero_array(sd, struct id_map, 2);
+ NT_STATUS_HAVE_NO_MEMORY(ids);
+
+ ids[0].xid.id = name->st.st_uid;
+ ids[0].xid.type = ID_TYPE_UID;
+ ids[0].sid = NULL;
+
+ ids[1].xid.id = name->st.st_gid;
+ ids[1].xid.type = ID_TYPE_GID;
+ ids[1].sid = NULL;
+
+ status = wbc_xids_to_sids(ids, 2);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ sd->owner_sid = talloc_steal(sd, ids[0].sid);
+ sd->group_sid = talloc_steal(sd, ids[1].sid);
+
+ talloc_free(ids);
+ sd->type |= SEC_DESC_DACL_PRESENT;
+
+ mode = name->st.st_mode;
+
+ /*
+ we provide up to 4 ACEs
+ - Owner
+ - Group
+ - Everyone
+ - Administrator
+ */
+
+
+ /* setup owner ACE */
+ ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+ ace.flags = 0;
+ ace.trustee = *sd->owner_sid;
+ ace.access_mask = 0;
+
+ if (mode & S_IRUSR) {
+ if (mode & S_IWUSR) {
+ ace.access_mask |= SEC_RIGHTS_FILE_ALL;
+ } else {
+ ace.access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ }
+ if (mode & S_IWUSR) {
+ ace.access_mask |= SEC_RIGHTS_FILE_WRITE | SEC_STD_DELETE;
+ }
+ if (ace.access_mask) {
+ security_descriptor_dacl_add(sd, &ace);
+ }
+
+
+ /* setup group ACE */
+ ace.trustee = *sd->group_sid;
+ ace.access_mask = 0;
+ if (mode & S_IRGRP) {
+ ace.access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ if (mode & S_IWGRP) {
+ /* note that delete is not granted - this matches posix behaviour */
+ ace.access_mask |= SEC_RIGHTS_FILE_WRITE;
+ }
+ if (ace.access_mask) {
+ security_descriptor_dacl_add(sd, &ace);
+ }
+
+ /* setup other ACE */
+ ace.trustee = global_sid_World;
+ ace.access_mask = 0;
+ if (mode & S_IROTH) {
+ ace.access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ if (mode & S_IWOTH) {
+ ace.access_mask |= SEC_RIGHTS_FILE_WRITE;
+ }
+ if (ace.access_mask) {
+ security_descriptor_dacl_add(sd, &ace);
+ }
+
+ /* setup system ACE */
+ ace.trustee = global_sid_System;
+ ace.access_mask = SEC_RIGHTS_FILE_ALL;
+ security_descriptor_dacl_add(sd, &ace);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ omit any security_descriptor elements not specified in the given
+ secinfo flags
+*/
+static void normalise_sd_flags(struct security_descriptor *sd, uint32_t secinfo_flags)
+{
+ if (!(secinfo_flags & SECINFO_OWNER)) {
+ sd->owner_sid = NULL;
+ }
+ if (!(secinfo_flags & SECINFO_GROUP)) {
+ sd->group_sid = NULL;
+ }
+ if (!(secinfo_flags & SECINFO_DACL)) {
+ sd->dacl = NULL;
+ }
+ if (!(secinfo_flags & SECINFO_SACL)) {
+ sd->sacl = NULL;
+ }
+}
+
+static bool pvfs_privileged_access(uid_t uid)
+{
+ uid_t euid;
+
+ if (uid_wrapper_enabled()) {
+ setenv("UID_WRAPPER_MYUID", "1", 1);
+ }
+
+ euid = geteuid();
+
+ if (uid_wrapper_enabled()) {
+ unsetenv("UID_WRAPPER_MYUID");
+ }
+
+ return (uid == euid);
+}
+
+/*
+ answer a setfileinfo for an ACL
+*/
+NTSTATUS pvfs_acl_set(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name, int fd,
+ uint32_t access_mask,
+ union smb_setfileinfo *info)
+{
+ uint32_t secinfo_flags = info->set_secdesc.in.secinfo_flags;
+ struct security_descriptor *new_sd, *sd, orig_sd;
+ NTSTATUS status = NT_STATUS_NOT_FOUND;
+ uid_t old_uid = -1;
+ gid_t old_gid = -1;
+ uid_t new_uid = -1;
+ gid_t new_gid = -1;
+ struct id_map *ids;
+
+ if (pvfs->acl_ops != NULL) {
+ status = pvfs->acl_ops->acl_load(pvfs, name, fd, req, &sd);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = pvfs_default_acl(pvfs, req, name, fd, &sd);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ids = talloc(req, struct id_map);
+ NT_STATUS_HAVE_NO_MEMORY(ids);
+ ZERO_STRUCT(ids->xid);
+ ids->sid = NULL;
+ ids->status = ID_UNKNOWN;
+
+ new_sd = info->set_secdesc.in.sd;
+ orig_sd = *sd;
+
+ old_uid = name->st.st_uid;
+ old_gid = name->st.st_gid;
+
+ /* only set the elements that have been specified */
+ if (secinfo_flags & SECINFO_OWNER) {
+ if (!(access_mask & SEC_STD_WRITE_OWNER)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (!dom_sid_equal(sd->owner_sid, new_sd->owner_sid)) {
+ ids->sid = new_sd->owner_sid;
+ status = wbc_sids_to_xids(ids, 1);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (ids->xid.type == ID_TYPE_BOTH ||
+ ids->xid.type == ID_TYPE_UID) {
+ new_uid = ids->xid.id;
+ }
+ }
+ sd->owner_sid = new_sd->owner_sid;
+ }
+
+ if (secinfo_flags & SECINFO_GROUP) {
+ if (!(access_mask & SEC_STD_WRITE_OWNER)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (!dom_sid_equal(sd->group_sid, new_sd->group_sid)) {
+ ids->sid = new_sd->group_sid;
+ status = wbc_sids_to_xids(ids, 1);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (ids->xid.type == ID_TYPE_BOTH ||
+ ids->xid.type == ID_TYPE_GID) {
+ new_gid = ids->xid.id;
+ }
+
+ }
+ sd->group_sid = new_sd->group_sid;
+ }
+
+ if (secinfo_flags & SECINFO_DACL) {
+ if (!(access_mask & SEC_STD_WRITE_DAC)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ sd->dacl = new_sd->dacl;
+ pvfs_translate_generic_bits(sd->dacl);
+ sd->type |= SEC_DESC_DACL_PRESENT;
+ }
+
+ if (secinfo_flags & SECINFO_SACL) {
+ if (!(access_mask & SEC_FLAG_SYSTEM_SECURITY)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ sd->sacl = new_sd->sacl;
+ pvfs_translate_generic_bits(sd->sacl);
+ sd->type |= SEC_DESC_SACL_PRESENT;
+ }
+
+ if (secinfo_flags & SECINFO_PROTECTED_DACL) {
+ if (new_sd->type & SEC_DESC_DACL_PROTECTED) {
+ sd->type |= SEC_DESC_DACL_PROTECTED;
+ } else {
+ sd->type &= ~SEC_DESC_DACL_PROTECTED;
+ }
+ }
+
+ if (secinfo_flags & SECINFO_PROTECTED_SACL) {
+ if (new_sd->type & SEC_DESC_SACL_PROTECTED) {
+ sd->type |= SEC_DESC_SACL_PROTECTED;
+ } else {
+ sd->type &= ~SEC_DESC_SACL_PROTECTED;
+ }
+ }
+
+ if (new_uid == old_uid) {
+ new_uid = -1;
+ }
+
+ if (new_gid == old_gid) {
+ new_gid = -1;
+ }
+
+ /* if there's something to change try it */
+ if (new_uid != -1 || new_gid != -1) {
+ int ret;
+ if (fd == -1) {
+ ret = chown(name->full_name, new_uid, new_gid);
+ } else {
+ ret = fchown(fd, new_uid, new_gid);
+ }
+ if (errno == EPERM) {
+ if (pvfs_privileged_access(name->st.st_uid)) {
+ ret = 0;
+ } else {
+ /* try again as root if we have SEC_PRIV_RESTORE or
+ SEC_PRIV_TAKE_OWNERSHIP */
+ if (security_token_has_privilege(req->session_info->security_token,
+ SEC_PRIV_RESTORE) ||
+ security_token_has_privilege(req->session_info->security_token,
+ SEC_PRIV_TAKE_OWNERSHIP)) {
+ void *privs;
+ privs = root_privileges();
+ if (fd == -1) {
+ ret = chown(name->full_name, new_uid, new_gid);
+ } else {
+ ret = fchown(fd, new_uid, new_gid);
+ }
+ talloc_free(privs);
+ }
+ }
+ }
+ if (ret == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ }
+
+ /* we avoid saving if the sd is the same. This means when clients
+ copy files and end up copying the default sd that we don't
+ needlessly use xattrs */
+ if (!security_descriptor_equal(sd, &orig_sd) && pvfs->acl_ops) {
+ status = pvfs->acl_ops->acl_save(pvfs, name, fd, sd);
+ }
+
+ return status;
+}
+
+
+/*
+ answer a fileinfo query for the ACL
+*/
+NTSTATUS pvfs_acl_query(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name, int fd,
+ union smb_fileinfo *info)
+{
+ NTSTATUS status = NT_STATUS_NOT_FOUND;
+ struct security_descriptor *sd;
+
+ if (pvfs->acl_ops) {
+ status = pvfs->acl_ops->acl_load(pvfs, name, fd, req, &sd);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = pvfs_default_acl(pvfs, req, name, fd, &sd);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ normalise_sd_flags(sd, info->query_secdesc.in.secinfo_flags);
+
+ info->query_secdesc.out.sd = sd;
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ check the read only bit against any of the write access bits
+*/
+static bool pvfs_read_only(struct pvfs_state *pvfs, uint32_t access_mask)
+{
+ if ((pvfs->flags & PVFS_FLAG_READONLY) &&
+ (access_mask & (SEC_FILE_WRITE_DATA |
+ SEC_FILE_APPEND_DATA |
+ SEC_FILE_WRITE_EA |
+ SEC_FILE_WRITE_ATTRIBUTE |
+ SEC_STD_DELETE |
+ SEC_STD_WRITE_DAC |
+ SEC_STD_WRITE_OWNER |
+ SEC_DIR_DELETE_CHILD))) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ see if we are a member of the appropriate unix group
+ */
+static bool pvfs_group_member(struct pvfs_state *pvfs, gid_t gid)
+{
+ int i, ngroups;
+ gid_t *groups;
+ if (getegid() == gid) {
+ return true;
+ }
+ ngroups = getgroups(0, NULL);
+ if (ngroups <= 0) {
+ return false;
+ }
+ groups = talloc_array(pvfs, gid_t, ngroups);
+ if (groups == NULL) {
+ return false;
+ }
+ if (getgroups(ngroups, groups) != ngroups) {
+ talloc_free(groups);
+ return false;
+ }
+ for (i=0; i<ngroups; i++) {
+ if (groups[i] == gid) break;
+ }
+ talloc_free(groups);
+ return i < ngroups;
+}
+
+/*
+ default access check function based on unix permissions
+ doing this saves on building a full security descriptor
+ for the common case of access check on files with no
+ specific NT ACL
+
+ If name is NULL then treat as a new file creation
+*/
+static NTSTATUS pvfs_access_check_unix(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t *access_mask)
+{
+ uint32_t max_bits = 0;
+ struct security_token *token = req->session_info->security_token;
+
+ if (pvfs_read_only(pvfs, *access_mask)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (name == NULL) {
+ max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL;
+ } else if (pvfs_privileged_access(name->st.st_uid)) {
+ /* use the IxUSR bits */
+ if ((name->st.st_mode & S_IWUSR)) {
+ max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL;
+ } else if ((name->st.st_mode & (S_IRUSR | S_IXUSR))) {
+ max_bits |= SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_EXECUTE | SEC_STD_ALL;
+ }
+ } else if (pvfs_group_member(pvfs, name->st.st_gid)) {
+ /* use the IxGRP bits */
+ if ((name->st.st_mode & S_IWGRP)) {
+ max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL;
+ } else if ((name->st.st_mode & (S_IRGRP | S_IXGRP))) {
+ max_bits |= SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_EXECUTE | SEC_STD_ALL;
+ }
+ } else {
+ /* use the IxOTH bits */
+ if ((name->st.st_mode & S_IWOTH)) {
+ max_bits |= SEC_RIGHTS_FILE_ALL | SEC_STD_ALL;
+ } else if ((name->st.st_mode & (S_IROTH | S_IXOTH))) {
+ max_bits |= SEC_RIGHTS_FILE_READ | SEC_RIGHTS_FILE_EXECUTE | SEC_STD_ALL;
+ }
+ }
+
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED) {
+ *access_mask |= max_bits;
+ *access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED;
+ }
+
+ if ((*access_mask & SEC_FLAG_SYSTEM_SECURITY) &&
+ security_token_has_privilege(token, SEC_PRIV_SECURITY)) {
+ max_bits |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+
+ if (((*access_mask & ~max_bits) & SEC_RIGHTS_PRIV_RESTORE) &&
+ security_token_has_privilege(token, SEC_PRIV_RESTORE)) {
+ max_bits |= ~(SEC_RIGHTS_PRIV_RESTORE);
+ }
+ if (((*access_mask & ~max_bits) & SEC_RIGHTS_PRIV_BACKUP) &&
+ security_token_has_privilege(token, SEC_PRIV_BACKUP)) {
+ max_bits |= ~(SEC_RIGHTS_PRIV_BACKUP);
+ }
+
+ if (*access_mask & ~max_bits) {
+ DEBUG(5,(__location__ " denied access to '%s' - wanted 0x%08x but got 0x%08x (missing 0x%08x)\n",
+ name?name->full_name:"(new file)", *access_mask, max_bits, *access_mask & ~max_bits));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ /* on SMB, this bit is always granted, even if not
+ asked for */
+ *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ check the security descriptor on a file, if any
+
+ *access_mask is modified with the access actually granted
+*/
+NTSTATUS pvfs_access_check(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t *access_mask)
+{
+ struct security_token *token = req->session_info->security_token;
+ struct xattr_NTACL *acl;
+ NTSTATUS status;
+ struct security_descriptor *sd;
+ bool allow_delete = false;
+
+ /* on SMB2 a blank access mask is always denied */
+ if (pvfs->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02 &&
+ *access_mask == 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (pvfs_read_only(pvfs, *access_mask)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED ||
+ *access_mask & SEC_STD_DELETE) {
+ status = pvfs_access_check_parent(pvfs, req,
+ name, SEC_DIR_DELETE_CHILD);
+ if (NT_STATUS_IS_OK(status)) {
+ allow_delete = true;
+ *access_mask &= ~SEC_STD_DELETE;
+ }
+ }
+
+ acl = talloc(req, struct xattr_NTACL);
+ if (acl == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* expand the generic access bits to file specific bits */
+ *access_mask = pvfs_translate_mask(*access_mask);
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ *access_mask &= ~SEC_FILE_READ_ATTRIBUTE;
+ }
+
+ status = pvfs_acl_load(pvfs, name, -1, acl);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ talloc_free(acl);
+ status = pvfs_access_check_unix(pvfs, req, name, access_mask);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ switch (acl->version) {
+ case 1:
+ sd = acl->info.sd;
+ break;
+ default:
+ return NT_STATUS_INVALID_ACL;
+ }
+
+ /* check the acl against the required access mask */
+ status = se_file_access_check(sd, token, false, *access_mask, access_mask);
+ talloc_free(acl);
+
+ /* if we used a NT acl, then allow access override if the
+ share allows for posix permission override
+ */
+ if (NT_STATUS_IS_OK(status)) {
+ name->allow_override = (pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) != 0;
+ }
+
+done:
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ /* on SMB, this bit is always granted, even if not
+ asked for */
+ *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ }
+
+ if (allow_delete) {
+ *access_mask |= SEC_STD_DELETE;
+ }
+
+ return status;
+}
+
+
+/*
+ a simplified interface to access check, designed for calls that
+ do not take or return an access check mask
+*/
+NTSTATUS pvfs_access_check_simple(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t access_needed)
+{
+ if (access_needed == 0) {
+ return NT_STATUS_OK;
+ }
+ return pvfs_access_check(pvfs, req, name, &access_needed);
+}
+
+/*
+ access check for creating a new file/directory
+*/
+NTSTATUS pvfs_access_check_create(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t *access_mask,
+ bool container,
+ struct security_descriptor **sd)
+{
+ struct pvfs_filename *parent;
+ NTSTATUS status;
+ uint32_t parent_mask;
+ bool allow_delete = false;
+
+ if (pvfs_read_only(pvfs, *access_mask)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pvfs_resolve_parent(pvfs, req, name, &parent);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (container) {
+ parent_mask = SEC_DIR_ADD_SUBDIR;
+ } else {
+ parent_mask = SEC_DIR_ADD_FILE;
+ }
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED ||
+ *access_mask & SEC_STD_DELETE) {
+ parent_mask |= SEC_DIR_DELETE_CHILD;
+ }
+
+ status = pvfs_access_check(pvfs, req, parent, &parent_mask);
+ if (NT_STATUS_IS_OK(status)) {
+ if (parent_mask & SEC_DIR_DELETE_CHILD) {
+ allow_delete = true;
+ }
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ /*
+ * on ACCESS_DENIED we get the rejected bits
+ * remove the non critical SEC_DIR_DELETE_CHILD
+ * and check if something else was rejected.
+ */
+ parent_mask &= ~SEC_DIR_DELETE_CHILD;
+ if (parent_mask != 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ status = NT_STATUS_OK;
+ } else {
+ return status;
+ }
+
+ if (*sd == NULL) {
+ status = pvfs_acl_inherited_sd(pvfs, req, req, parent, container, sd);
+ }
+
+ talloc_free(parent);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* expand the generic access bits to file specific bits */
+ *access_mask = pvfs_translate_mask(*access_mask);
+
+ if (*access_mask & SEC_FLAG_MAXIMUM_ALLOWED) {
+ *access_mask |= SEC_RIGHTS_FILE_ALL;
+ *access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED;
+ }
+
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ /* on SMB, this bit is always granted, even if not
+ asked for */
+ *access_mask |= SEC_FILE_READ_ATTRIBUTE;
+ }
+
+ if (allow_delete) {
+ *access_mask |= SEC_STD_DELETE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ access check for creating a new file/directory - no access mask supplied
+*/
+NTSTATUS pvfs_access_check_parent(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t access_mask)
+{
+ struct pvfs_filename *parent;
+ NTSTATUS status;
+
+ status = pvfs_resolve_parent(pvfs, req, name, &parent);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_access_check_simple(pvfs, req, parent, access_mask);
+ if (NT_STATUS_IS_OK(status) && parent->allow_override) {
+ name->allow_override = true;
+ }
+ return status;
+}
+
+
+/*
+ determine if an ACE is inheritable
+*/
+static bool pvfs_inheritable_ace(struct pvfs_state *pvfs,
+ const struct security_ace *ace,
+ bool container)
+{
+ if (!container) {
+ return (ace->flags & SEC_ACE_FLAG_OBJECT_INHERIT) != 0;
+ }
+
+ if (ace->flags & SEC_ACE_FLAG_CONTAINER_INHERIT) {
+ return true;
+ }
+
+ if ((ace->flags & SEC_ACE_FLAG_OBJECT_INHERIT) &&
+ !(ace->flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT)) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ this is the core of ACL inheritance. It copies any inheritable
+ aces from the parent SD to the child SD. Note that the algorithm
+ depends on whether the child is a container or not
+*/
+static NTSTATUS pvfs_acl_inherit_aces(struct pvfs_state *pvfs,
+ struct security_descriptor *parent_sd,
+ struct security_descriptor *sd,
+ bool container)
+{
+ int i;
+
+ for (i=0;i<parent_sd->dacl->num_aces;i++) {
+ struct security_ace ace = parent_sd->dacl->aces[i];
+ NTSTATUS status;
+ const struct dom_sid *creator = NULL, *new_id = NULL;
+ uint32_t orig_flags;
+
+ if (!pvfs_inheritable_ace(pvfs, &ace, container)) {
+ continue;
+ }
+
+ orig_flags = ace.flags;
+
+ /* see the RAW-ACLS inheritance test for details on these rules */
+ if (!container) {
+ ace.flags = 0;
+ } else {
+ ace.flags &= ~SEC_ACE_FLAG_INHERIT_ONLY;
+
+ if (!(ace.flags & SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ ace.flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+ }
+ if (ace.flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) {
+ ace.flags = 0;
+ }
+ }
+
+ /* the CREATOR sids are special when inherited */
+ if (dom_sid_equal(&ace.trustee, pvfs->sid_cache.creator_owner)) {
+ creator = pvfs->sid_cache.creator_owner;
+ new_id = sd->owner_sid;
+ } else if (dom_sid_equal(&ace.trustee, pvfs->sid_cache.creator_group)) {
+ creator = pvfs->sid_cache.creator_group;
+ new_id = sd->group_sid;
+ } else {
+ new_id = &ace.trustee;
+ }
+
+ if (creator && container &&
+ (ace.flags & SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ uint32_t flags = ace.flags;
+
+ ace.trustee = *new_id;
+ ace.flags = 0;
+ status = security_descriptor_dacl_add(sd, &ace);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ace.trustee = *creator;
+ ace.flags = flags | SEC_ACE_FLAG_INHERIT_ONLY;
+ status = security_descriptor_dacl_add(sd, &ace);
+ } else if (container &&
+ !(orig_flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT)) {
+ status = security_descriptor_dacl_add(sd, &ace);
+ } else {
+ ace.trustee = *new_id;
+ status = security_descriptor_dacl_add(sd, &ace);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+
+/*
+ calculate the ACL on a new file/directory based on the inherited ACL
+ from the parent. If there is no inherited ACL then return a NULL
+ ACL, which means the default ACL should be used
+*/
+NTSTATUS pvfs_acl_inherited_sd(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ struct ntvfs_request *req,
+ struct pvfs_filename *parent,
+ bool container,
+ struct security_descriptor **ret_sd)
+{
+ struct xattr_NTACL *acl;
+ NTSTATUS status;
+ struct security_descriptor *parent_sd, *sd;
+ struct id_map *ids;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ *ret_sd = NULL;
+
+ acl = talloc(req, struct xattr_NTACL);
+ if (acl == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_acl_load(pvfs, parent, -1, acl);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ switch (acl->version) {
+ case 1:
+ parent_sd = acl->info.sd;
+ break;
+ default:
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_ACL;
+ }
+
+ if (parent_sd == NULL ||
+ parent_sd->dacl == NULL ||
+ parent_sd->dacl->num_aces == 0) {
+ /* go with the default ACL */
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ /* create the new sd */
+ sd = security_descriptor_initialise(req);
+ if (sd == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ids = talloc_array(sd, struct id_map, 2);
+ if (ids == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ids[0].xid.id = geteuid();
+ ids[0].xid.type = ID_TYPE_UID;
+ ids[0].sid = NULL;
+ ids[0].status = ID_UNKNOWN;
+
+ ids[1].xid.id = getegid();
+ ids[1].xid.type = ID_TYPE_GID;
+ ids[1].sid = NULL;
+ ids[1].status = ID_UNKNOWN;
+
+ status = wbc_xids_to_sids(ids, 2);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ sd->owner_sid = talloc_steal(sd, ids[0].sid);
+ sd->group_sid = talloc_steal(sd, ids[1].sid);
+
+ sd->type |= SEC_DESC_DACL_PRESENT;
+
+ /* fill in the aces from the parent */
+ status = pvfs_acl_inherit_aces(pvfs, parent_sd, sd, container);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ /* if there is nothing to inherit then we fallback to the
+ default acl */
+ if (sd->dacl == NULL || sd->dacl->num_aces == 0) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ *ret_sd = talloc_steal(mem_ctx, sd);
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ setup an ACL on a new file/directory based on the inherited ACL from
+ the parent. If there is no inherited ACL then we don't set anything,
+ as the default ACL applies anyway
+*/
+NTSTATUS pvfs_acl_inherit(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ int fd)
+{
+ struct xattr_NTACL acl;
+ NTSTATUS status;
+ struct security_descriptor *sd;
+ struct pvfs_filename *parent;
+ bool container;
+
+ /* form the parents path */
+ status = pvfs_resolve_parent(pvfs, req, name, &parent);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ container = (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) ? true:false;
+
+ status = pvfs_acl_inherited_sd(pvfs, req, req, parent, container, &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(parent);
+ return status;
+ }
+
+ if (sd == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ acl.version = 1;
+ acl.info.sd = sd;
+
+ status = pvfs_acl_save(pvfs, name, fd, &acl);
+ talloc_free(sd);
+ talloc_free(parent);
+
+ return status;
+}
+
+/*
+ return the maximum allowed access mask
+*/
+NTSTATUS pvfs_access_maximal_allowed(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ uint32_t *maximal_access)
+{
+ *maximal_access = SEC_FLAG_MAXIMUM_ALLOWED;
+ return pvfs_access_check(pvfs, req, name, maximal_access);
+}
diff --git a/source4/ntvfs/posix/pvfs_acl_nfs4.c b/source4/ntvfs/posix/pvfs_acl_nfs4.c
new file mode 100644
index 0000000..3ddc1b6
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_acl_nfs4.c
@@ -0,0 +1,199 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - NT ACLs mapped to NFS4 ACLs, as per
+ http://www.suse.de/~agruen/nfs4acl/
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "../lib/util/unix_privs.h"
+#include "librpc/gen_ndr/ndr_nfs4acl.h"
+#include "libcli/security/security.h"
+
+NTSTATUS pvfs_acl_nfs4_init(TALLOC_CTX *);
+
+#define ACE4_IDENTIFIER_GROUP 0x40
+
+/*
+ load the current ACL from system.nfs4acl
+*/
+static NTSTATUS pvfs_acl_load_nfs4(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **psd)
+{
+ NTSTATUS status;
+ struct nfs4acl *acl;
+ struct security_descriptor *sd;
+ int i, num_ids;
+ struct id_map *ids;
+
+ acl = talloc_zero(mem_ctx, struct nfs4acl);
+ NT_STATUS_HAVE_NO_MEMORY(acl);
+
+ status = pvfs_xattr_ndr_load(pvfs, mem_ctx, name->full_name, fd,
+ NFS4ACL_NDR_XATTR_NAME,
+ acl, (void *) ndr_pull_nfs4acl);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(acl);
+ return status;
+ }
+
+ *psd = security_descriptor_initialise(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(*psd);
+
+ sd = *psd;
+
+ sd->type |= acl->a_flags;
+
+ /* the number of ids to map is the acl count plus uid and gid */
+ num_ids = acl->a_count +2;
+ ids = talloc_array(sd, struct id_map, num_ids);
+ NT_STATUS_HAVE_NO_MEMORY(ids);
+
+ ids[0].xid.id = name->st.st_uid;
+ ids[0].xid.type = ID_TYPE_UID;
+ ids[0].sid = NULL;
+ ids[0].status = ID_UNKNOWN;
+
+ ids[1].xid.id = name->st.st_gid;
+ ids[1].xid.type = ID_TYPE_GID;
+ ids[1].sid = NULL;
+ ids[1].status = ID_UNKNOWN;
+
+ for (i=0;i<acl->a_count;i++) {
+ struct nfs4ace *a = &acl->ace[i];
+ ids[i+2].xid.id = a->e_id;
+ if (a->e_flags & ACE4_IDENTIFIER_GROUP) {
+ ids[i+2].xid.type = ID_TYPE_GID;
+ } else {
+ ids[i+2].xid.type = ID_TYPE_UID;
+ }
+ ids[i+2].sid = NULL;
+ ids[i+2].status = ID_UNKNOWN;
+ }
+
+ /* Allocate memory for the sids from the security descriptor to be on
+ * the safe side. */
+ status = wbc_xids_to_sids(ids, num_ids);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ sd->owner_sid = talloc_steal(sd, ids[0].sid);
+ sd->group_sid = talloc_steal(sd, ids[1].sid);
+
+ for (i=0;i<acl->a_count;i++) {
+ struct nfs4ace *a = &acl->ace[i];
+ struct security_ace ace = {};
+ ace.type = a->e_type;
+ ace.flags = a->e_flags;
+ ace.access_mask = a->e_mask;
+ ace.trustee = *ids[i+2].sid;
+ security_descriptor_dacl_add(sd, &ace);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ save the acl for a file into system.nfs4acl
+*/
+static NTSTATUS pvfs_acl_save_nfs4(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct security_descriptor *sd)
+{
+ NTSTATUS status;
+ void *privs;
+ struct nfs4acl acl;
+ int i;
+ TALLOC_CTX *tmp_ctx;
+ struct id_map *ids;
+
+ tmp_ctx = talloc_new(pvfs);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ acl.a_version = 0;
+ acl.a_flags = sd->type;
+ acl.a_count = sd->dacl?sd->dacl->num_aces:0;
+ acl.a_owner_mask = 0;
+ acl.a_group_mask = 0;
+ acl.a_other_mask = 0;
+
+ acl.ace = talloc_array(tmp_ctx, struct nfs4ace, acl.a_count);
+ if (!acl.ace) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ids = talloc_array(tmp_ctx, struct id_map, acl.a_count);
+ if (ids == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0;i<acl.a_count;i++) {
+ struct security_ace *ace = &sd->dacl->aces[i];
+ ZERO_STRUCT(ids[i].xid);
+ ids[i].sid = dom_sid_dup(ids, &ace->trustee);
+ if (ids[i].sid == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ids[i].status = ID_UNKNOWN;
+ }
+
+ status = wbc_sids_to_xids(ids, acl.a_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ for (i=0;i<acl.a_count;i++) {
+ struct nfs4ace *a = &acl.ace[i];
+ struct security_ace *ace = &sd->dacl->aces[i];
+ a->e_type = ace->type;
+ a->e_flags = ace->flags;
+ a->e_mask = ace->access_mask;
+ if (ids[i].xid.type != ID_TYPE_UID) {
+ a->e_flags |= ACE4_IDENTIFIER_GROUP;
+ }
+ a->e_id = ids[i].xid.id;
+ a->e_who = "";
+ }
+
+ privs = root_privileges();
+ status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
+ NFS4ACL_NDR_XATTR_NAME,
+ &acl, (void *) ndr_push_nfs4acl);
+ talloc_free(privs);
+
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+
+/*
+ initialise pvfs acl NFS4 backend
+*/
+NTSTATUS pvfs_acl_nfs4_init(TALLOC_CTX *ctx)
+{
+ struct pvfs_acl_ops ops = {
+ .name = "nfs4acl",
+ .acl_load = pvfs_acl_load_nfs4,
+ .acl_save = pvfs_acl_save_nfs4
+ };
+ return pvfs_acl_register(ctx, &ops);
+}
diff --git a/source4/ntvfs/posix/pvfs_acl_xattr.c b/source4/ntvfs/posix/pvfs_acl_xattr.c
new file mode 100644
index 0000000..1f569ca
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_acl_xattr.c
@@ -0,0 +1,104 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - NT ACLs in xattrs
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "../lib/util/unix_privs.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+
+NTSTATUS pvfs_acl_xattr_init(TALLOC_CTX *);
+
+/*
+ load the current ACL from extended attributes
+*/
+static NTSTATUS pvfs_acl_load_xattr(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd)
+{
+ NTSTATUS status;
+ struct xattr_NTACL *acl;
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ acl = talloc_zero(mem_ctx, struct xattr_NTACL);
+ NT_STATUS_HAVE_NO_MEMORY(acl);
+
+ status = pvfs_xattr_ndr_load(pvfs, mem_ctx, name->full_name, fd,
+ XATTR_NTACL_NAME,
+ acl, (void *) ndr_pull_xattr_NTACL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(acl);
+ return status;
+ }
+
+ if (acl->version != 1) {
+ talloc_free(acl);
+ return NT_STATUS_INVALID_ACL;
+ }
+
+ *sd = talloc_steal(mem_ctx, acl->info.sd);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ save the acl for a file into filesystem xattr
+*/
+static NTSTATUS pvfs_acl_save_xattr(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct security_descriptor *sd)
+{
+ NTSTATUS status;
+ void *privs;
+ struct xattr_NTACL acl;
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+
+ acl.version = 1;
+ acl.info.sd = sd;
+
+ /* this xattr is in the "system" namespace, so we need
+ admin privileges to set it */
+ privs = root_privileges();
+ status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
+ XATTR_NTACL_NAME,
+ &acl, (void *) ndr_push_xattr_NTACL);
+ talloc_free(privs);
+ return status;
+}
+
+
+/*
+ initialise pvfs acl xattr backend
+*/
+NTSTATUS pvfs_acl_xattr_init(TALLOC_CTX *ctx)
+{
+ struct pvfs_acl_ops ops = {
+ .name = "xattr",
+ .acl_load = pvfs_acl_load_xattr,
+ .acl_save = pvfs_acl_save_xattr
+ };
+ return pvfs_acl_register(ctx, &ops);
+}
diff --git a/source4/ntvfs/posix/pvfs_dirlist.c b/source4/ntvfs/posix/pvfs_dirlist.c
new file mode 100644
index 0000000..1207287
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_dirlist.c
@@ -0,0 +1,411 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+/*
+ directory listing functions for posix backend
+*/
+
+#include "includes.h"
+#include "vfs_posix.h"
+#include "system/dir.h"
+
+#define NAME_CACHE_SIZE 100
+
+struct name_cache_entry {
+ char *name;
+ off_t offset;
+};
+
+struct pvfs_dir {
+ struct pvfs_state *pvfs;
+ bool no_wildcard;
+ char *single_name;
+ const char *pattern;
+ off_t offset;
+ DIR *dir;
+ const char *unix_path;
+ bool end_of_search;
+ struct name_cache_entry *name_cache;
+ uint32_t name_cache_index;
+};
+
+/* these three numbers are chosen to minimise the chances of a bad
+ interaction with the OS value for 'end of directory'. On IRIX
+ telldir() returns 0xFFFFFFFF at the end of a directory, and that
+ caused an infinite loop with the original values of 0,1,2
+
+ On XFS on linux telldir returns 0x7FFFFFFF at the end of a
+ directory. Thus the change from 0x80000002, as otherwise
+ 0x7FFFFFFF+0x80000002==1==DIR_OFFSET_DOTDOT
+*/
+#define DIR_OFFSET_DOT 0
+#define DIR_OFFSET_DOTDOT 1
+#define DIR_OFFSET_BASE 0x80000022
+
+/*
+ a special directory listing case where the pattern has no wildcard. We can just do a single stat()
+ thus avoiding the more expensive directory scan
+*/
+static NTSTATUS pvfs_list_no_wildcard(struct pvfs_state *pvfs, struct pvfs_filename *name,
+ const char *pattern, struct pvfs_dir *dir)
+{
+ if (!name->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ dir->pvfs = pvfs;
+ dir->no_wildcard = true;
+ dir->end_of_search = false;
+ dir->unix_path = talloc_strdup(dir, name->full_name);
+ if (!dir->unix_path) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dir->single_name = talloc_strdup(dir, pattern);
+ if (!dir->single_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dir->dir = NULL;
+ dir->offset = 0;
+ dir->pattern = NULL;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ destroy an open search
+*/
+static int pvfs_dirlist_destructor(struct pvfs_dir *dir)
+{
+ if (dir->dir) closedir(dir->dir);
+ return 0;
+}
+
+/*
+ start to read a directory
+
+ if the pattern matches no files then we return NT_STATUS_OK, with dir->count = 0
+*/
+NTSTATUS pvfs_list_start(struct pvfs_state *pvfs, struct pvfs_filename *name,
+ TALLOC_CTX *mem_ctx, struct pvfs_dir **dirp)
+{
+ char *pattern;
+ struct pvfs_dir *dir;
+
+ (*dirp) = talloc_zero(mem_ctx, struct pvfs_dir);
+ if (*dirp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dir = *dirp;
+
+ /* split the unix path into a directory + pattern */
+ pattern = strrchr(name->full_name, '/');
+ if (!pattern) {
+ /* this should not happen, as pvfs_unix_path is supposed to
+ return an absolute path */
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *pattern++ = 0;
+
+ if (!name->has_wildcard) {
+ return pvfs_list_no_wildcard(pvfs, name, pattern, dir);
+ }
+
+ dir->unix_path = talloc_strdup(dir, name->full_name);
+ if (!dir->unix_path) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dir->pattern = talloc_strdup(dir, pattern);
+ if (dir->pattern == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dir->dir = opendir(name->full_name);
+ if (!dir->dir) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ dir->pvfs = pvfs;
+ dir->no_wildcard = false;
+ dir->end_of_search = false;
+ dir->offset = DIR_OFFSET_DOT;
+ dir->name_cache = talloc_zero_array(dir,
+ struct name_cache_entry,
+ NAME_CACHE_SIZE);
+ if (dir->name_cache == NULL) {
+ talloc_free(dir);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_set_destructor(dir, pvfs_dirlist_destructor);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ add an entry to the local cache
+*/
+static void dcache_add(struct pvfs_dir *dir, const char *name)
+{
+ struct name_cache_entry *e;
+
+ dir->name_cache_index = (dir->name_cache_index+1) % NAME_CACHE_SIZE;
+ e = &dir->name_cache[dir->name_cache_index];
+
+ if (e->name) talloc_free(e->name);
+
+ e->name = talloc_strdup(dir->name_cache, name);
+ e->offset = dir->offset;
+}
+
+/*
+ return the next entry
+*/
+const char *pvfs_list_next(struct pvfs_dir *dir, off_t *ofs)
+{
+ struct dirent *de;
+ enum protocol_types protocol = dir->pvfs->ntvfs->ctx->protocol;
+
+ /* non-wildcard searches are easy */
+ if (dir->no_wildcard) {
+ dir->end_of_search = true;
+ if (*ofs != 0) return NULL;
+ (*ofs)++;
+ return dir->single_name;
+ }
+
+ /* . and .. are handled separately as some unix systems will
+ not return them first in a directory, but windows client
+ may assume that these entries always appear first */
+ if (*ofs == DIR_OFFSET_DOT) {
+ (*ofs) = DIR_OFFSET_DOTDOT;
+ dir->offset = *ofs;
+ if (ms_fnmatch_protocol(dir->pattern, ".", protocol,
+ false) == 0) {
+ dcache_add(dir, ".");
+ return ".";
+ }
+ }
+
+ if (*ofs == DIR_OFFSET_DOTDOT) {
+ (*ofs) = DIR_OFFSET_BASE;
+ dir->offset = *ofs;
+ if (ms_fnmatch_protocol(dir->pattern, "..", protocol,
+ false) == 0) {
+ dcache_add(dir, "..");
+ return "..";
+ }
+ }
+
+ if (*ofs == DIR_OFFSET_BASE) {
+ rewinddir(dir->dir);
+ } else if (*ofs != dir->offset) {
+ seekdir(dir->dir, (*ofs) - DIR_OFFSET_BASE);
+ }
+ dir->offset = *ofs;
+
+ while ((de = readdir(dir->dir))) {
+ const char *dname = de->d_name;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ if (ms_fnmatch_protocol(dir->pattern, dname, protocol,
+ false) != 0) {
+ char *short_name = pvfs_short_name_component(dir->pvfs, dname);
+ if (short_name == NULL ||
+ ms_fnmatch_protocol(dir->pattern, short_name,
+ protocol, false) != 0) {
+ talloc_free(short_name);
+ continue;
+ }
+ talloc_free(short_name);
+ }
+
+ /* Casting is necessary to avoid signed integer overflow. */
+ dir->offset = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE;
+ (*ofs) = dir->offset;
+
+ dcache_add(dir, dname);
+
+ return dname;
+ }
+
+ dir->end_of_search = true;
+ return NULL;
+}
+
+/*
+ return unix directory of an open search
+*/
+const char *pvfs_list_unix_path(struct pvfs_dir *dir)
+{
+ return dir->unix_path;
+}
+
+/*
+ return true if end of search has been reached
+*/
+bool pvfs_list_eos(struct pvfs_dir *dir, off_t ofs)
+{
+ return dir->end_of_search;
+}
+
+/*
+ seek to the given name
+*/
+NTSTATUS pvfs_list_seek(struct pvfs_dir *dir, const char *name, off_t *ofs)
+{
+ struct dirent *de;
+ int i;
+
+ dir->end_of_search = false;
+
+ if (ISDOT(name)) {
+ dir->offset = DIR_OFFSET_DOTDOT;
+ *ofs = dir->offset;
+ return NT_STATUS_OK;
+ }
+
+ if (ISDOTDOT(name)) {
+ dir->offset = DIR_OFFSET_BASE;
+ *ofs = dir->offset;
+ return NT_STATUS_OK;
+ }
+
+ for (i=dir->name_cache_index;i>=0;i--) {
+ struct name_cache_entry *e = &dir->name_cache[i];
+ if (e->name && strcasecmp_m(name, e->name) == 0) {
+ *ofs = e->offset;
+ return NT_STATUS_OK;
+ }
+ }
+ for (i=NAME_CACHE_SIZE-1;i>dir->name_cache_index;i--) {
+ struct name_cache_entry *e = &dir->name_cache[i];
+ if (e->name && strcasecmp_m(name, e->name) == 0) {
+ *ofs = e->offset;
+ return NT_STATUS_OK;
+ }
+ }
+
+ rewinddir(dir->dir);
+
+ while ((de = readdir(dir->dir))) {
+ if (strcasecmp_m(name, de->d_name) == 0) {
+ /* Casting is necessary to avoid signed integer overflow. */
+ dir->offset = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE;
+ *ofs = dir->offset;
+ return NT_STATUS_OK;
+ }
+ }
+
+ dir->end_of_search = true;
+
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+/*
+ seek to the given offset
+*/
+NTSTATUS pvfs_list_seek_ofs(struct pvfs_dir *dir, uint32_t resume_key, off_t *ofs)
+{
+ struct dirent *de;
+ int i;
+
+ dir->end_of_search = false;
+
+ if (resume_key == DIR_OFFSET_DOT) {
+ *ofs = DIR_OFFSET_DOTDOT;
+ return NT_STATUS_OK;
+ }
+
+ if (resume_key == DIR_OFFSET_DOTDOT) {
+ *ofs = DIR_OFFSET_BASE;
+ return NT_STATUS_OK;
+ }
+
+ if (resume_key == DIR_OFFSET_BASE) {
+ rewinddir(dir->dir);
+ if ((de=readdir(dir->dir)) == NULL) {
+ dir->end_of_search = true;
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ /* Casting is necessary to avoid signed integer overflow. */
+ *ofs = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE;
+ dir->offset = *ofs;
+ return NT_STATUS_OK;
+ }
+
+ for (i=dir->name_cache_index;i>=0;i--) {
+ struct name_cache_entry *e = &dir->name_cache[i];
+ if (resume_key == (uint32_t)e->offset) {
+ *ofs = e->offset;
+ return NT_STATUS_OK;
+ }
+ }
+ for (i=NAME_CACHE_SIZE-1;i>dir->name_cache_index;i--) {
+ struct name_cache_entry *e = &dir->name_cache[i];
+ if (resume_key == (uint32_t)e->offset) {
+ *ofs = e->offset;
+ return NT_STATUS_OK;
+ }
+ }
+
+ rewinddir(dir->dir);
+
+ while ((de = readdir(dir->dir))) {
+ /* Casting is necessary to avoid signed integer overflow. */
+ dir->offset = (unsigned long)telldir(dir->dir) + (unsigned long)DIR_OFFSET_BASE;
+ if (resume_key == (uint32_t)dir->offset) {
+ *ofs = dir->offset;
+ return NT_STATUS_OK;
+ }
+ }
+
+ dir->end_of_search = true;
+
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+
+/*
+ see if a directory is empty
+*/
+bool pvfs_directory_empty(struct pvfs_state *pvfs, struct pvfs_filename *name)
+{
+ struct dirent *de;
+ DIR *dir = opendir(name->full_name);
+ if (dir == NULL) {
+ return true;
+ }
+
+ while ((de = readdir(dir))) {
+ if (!ISDOT(de->d_name) && !ISDOTDOT(de->d_name)) {
+ closedir(dir);
+ return false;
+ }
+ }
+
+ closedir(dir);
+ return true;
+}
diff --git a/source4/ntvfs/posix/pvfs_fileinfo.c b/source4/ntvfs/posix/pvfs_fileinfo.c
new file mode 100644
index 0000000..977ea4f
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_fileinfo.c
@@ -0,0 +1,158 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend -
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "lib/util/time.h"
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+****************************************************************************/
+static uint32_t dos_mode_from_stat(struct pvfs_state *pvfs, struct stat *st)
+{
+ int result = 0;
+
+ if ((st->st_mode & S_IWUSR) == 0)
+ result |= FILE_ATTRIBUTE_READONLY;
+
+ if ((pvfs->flags & PVFS_FLAG_MAP_ARCHIVE) && ((st->st_mode & S_IXUSR) != 0))
+ result |= FILE_ATTRIBUTE_ARCHIVE;
+
+ if ((pvfs->flags & PVFS_FLAG_MAP_SYSTEM) && ((st->st_mode & S_IXGRP) != 0))
+ result |= FILE_ATTRIBUTE_SYSTEM;
+
+ if ((pvfs->flags & PVFS_FLAG_MAP_HIDDEN) && ((st->st_mode & S_IXOTH) != 0))
+ result |= FILE_ATTRIBUTE_HIDDEN;
+
+ if (S_ISDIR(st->st_mode))
+ result = FILE_ATTRIBUTE_DIRECTORY | (result & FILE_ATTRIBUTE_READONLY);
+
+ return result;
+}
+
+
+
+/*
+ fill in the dos file attributes for a file
+*/
+NTSTATUS pvfs_fill_dos_info(struct pvfs_state *pvfs, struct pvfs_filename *name,
+ unsigned int flags, int fd)
+{
+ NTSTATUS status;
+ DATA_BLOB lkey;
+ NTTIME write_time;
+
+ /* make directories appear as size 0 with 1 link */
+ if (S_ISDIR(name->st.st_mode)) {
+ name->st.st_size = 0;
+ name->st.st_nlink = 1;
+ } else if (name->stream_id == 0) {
+ name->stream_name = NULL;
+ }
+
+ /* for now just use the simple samba mapping */
+ unix_to_nt_time(&name->dos.create_time, name->st.st_ctime);
+ unix_to_nt_time(&name->dos.access_time, name->st.st_atime);
+ unix_to_nt_time(&name->dos.write_time, name->st.st_mtime);
+ unix_to_nt_time(&name->dos.change_time, name->st.st_ctime);
+ name->dos.create_time += get_ctimensec(&name->st) / 100;
+ name->dos.access_time += get_atimensec(&name->st) / 100;
+ name->dos.write_time += get_mtimensec(&name->st) / 100;
+ name->dos.change_time += get_ctimensec(&name->st) / 100;
+ name->dos.attrib = dos_mode_from_stat(pvfs, &name->st);
+ name->dos.alloc_size = pvfs_round_alloc_size(pvfs, name->st.st_size);
+ name->dos.nlink = name->st.st_nlink;
+ name->dos.ea_size = 4; /* TODO: Fill this in without hitting the stream bad in pvfs_doseas_load() */
+ if (pvfs->ntvfs->ctx->protocol >= PROTOCOL_SMB2_02) {
+ /* SMB2 represents a null EA with zero bytes */
+ name->dos.ea_size = 0;
+ }
+
+ name->dos.file_id = (((uint64_t)name->st.st_dev)<<32) | name->st.st_ino;
+ name->dos.flags = 0;
+
+ status = pvfs_dosattrib_load(pvfs, name, fd);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (flags & PVFS_RESOLVE_NO_OPENDB) {
+ return NT_STATUS_OK;
+ }
+
+ status = pvfs_locking_key(name, name, &lkey);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = odb_get_file_infos(pvfs->odb_context, &lkey,
+ NULL, &write_time);
+ data_blob_free(&lkey);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("WARNING: odb_get_file_infos: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ if (!null_time(write_time)) {
+ name->dos.write_time = write_time;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ return a set of unix file permissions for a new file or directory
+*/
+mode_t pvfs_fileperms(struct pvfs_state *pvfs, uint32_t attrib)
+{
+ mode_t mode = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE) &&
+ (attrib & FILE_ATTRIBUTE_READONLY)) {
+ mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ }
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ if ((attrib & FILE_ATTRIBUTE_ARCHIVE) &&
+ (pvfs->flags & PVFS_FLAG_MAP_ARCHIVE)) {
+ mode |= S_IXUSR;
+ }
+ if ((attrib & FILE_ATTRIBUTE_SYSTEM) &&
+ (pvfs->flags & PVFS_FLAG_MAP_SYSTEM)) {
+ mode |= S_IXGRP;
+ }
+ if ((attrib & FILE_ATTRIBUTE_HIDDEN) &&
+ (pvfs->flags & PVFS_FLAG_MAP_HIDDEN)) {
+ mode |= S_IXOTH;
+ }
+ }
+
+ if (attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ mode |= (S_IFDIR | S_IWUSR);
+ mode |= (S_IXUSR | S_IXGRP | S_IXOTH);
+ mode &= pvfs->options.dir_mask;
+ mode |= pvfs->options.force_dir_mode;
+ } else {
+ mode &= pvfs->options.create_mask;
+ mode |= pvfs->options.force_create_mode;
+ }
+
+ return mode;
+}
+
+
diff --git a/source4/ntvfs/posix/pvfs_flush.c b/source4/ntvfs/posix/pvfs_flush.c
new file mode 100644
index 0000000..f425fcc
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_flush.c
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - flush
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+
+/*
+ flush a single open file
+*/
+static void pvfs_flush_file(struct pvfs_state *pvfs, struct pvfs_file *f)
+{
+ if (f->handle->fd == -1) {
+ return;
+ }
+ if (pvfs->flags & PVFS_FLAG_STRICT_SYNC) {
+ fsync(f->handle->fd);
+ }
+}
+
+/*
+ flush a fnum
+*/
+NTSTATUS pvfs_flush(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_flush *io)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+
+ switch (io->generic.level) {
+ case RAW_FLUSH_FLUSH:
+ case RAW_FLUSH_SMB2:
+ /* TODO: take care of io->smb2.in.unknown */
+ f = pvfs_find_fd(pvfs, req, io->generic.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ pvfs_flush_file(pvfs, f);
+ io->smb2.out.reserved = 0;
+ return NT_STATUS_OK;
+
+ case RAW_FLUSH_ALL:
+ if (!(pvfs->flags & PVFS_FLAG_STRICT_SYNC)) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * they are asking to flush all open files
+ * for the given SMBPID
+ */
+ for (f=pvfs->files.list;f;f=f->next) {
+ if (f->ntvfs->smbpid != req->smbpid) continue;
+
+ pvfs_flush_file(pvfs, f);
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
diff --git a/source4/ntvfs/posix/pvfs_fsinfo.c b/source4/ntvfs/posix/pvfs_fsinfo.c
new file mode 100644
index 0000000..c355c19
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_fsinfo.c
@@ -0,0 +1,224 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - fsinfo
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/xattr.h"
+#include "librpc/ndr/libndr.h"
+
+/* We use libblkid out of e2fsprogs to identify UUID of a volume */
+#ifdef HAVE_LIBBLKID
+#include <blkid/blkid.h>
+#endif
+
+static NTSTATUS pvfs_blkid_fs_uuid(struct pvfs_state *pvfs, struct stat *st, struct GUID *uuid)
+{
+#ifdef HAVE_LIBBLKID
+ NTSTATUS status;
+ char *uuid_value = NULL;
+ char *devname = NULL;
+
+ devname = blkid_devno_to_devname(st->st_dev);
+ if (!devname) {
+ ZERO_STRUCTP(uuid);
+ return NT_STATUS_OK;
+ }
+
+ uuid_value = blkid_get_tag_value(NULL, "UUID", devname);
+ free(devname);
+ if (!uuid_value) {
+ ZERO_STRUCTP(uuid);
+ return NT_STATUS_OK;
+ }
+
+ status = GUID_from_string(uuid_value, uuid);
+ free(uuid_value);
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCTP(uuid);
+ return NT_STATUS_OK;
+ }
+ return NT_STATUS_OK;
+#else
+ ZERO_STRUCTP(uuid);
+ return NT_STATUS_OK;
+#endif
+}
+
+static NTSTATUS pvfs_cache_base_fs_uuid(struct pvfs_state *pvfs, struct stat *st)
+{
+ NTSTATUS status;
+ struct GUID uuid;
+
+ if (pvfs->base_fs_uuid) return NT_STATUS_OK;
+
+ status = pvfs_blkid_fs_uuid(pvfs, st, &uuid);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ pvfs->base_fs_uuid = talloc(pvfs, struct GUID);
+ NT_STATUS_HAVE_NO_MEMORY(pvfs->base_fs_uuid);
+ *pvfs->base_fs_uuid = uuid;
+
+ return NT_STATUS_OK;
+}
+/*
+ return filesystem space info
+*/
+NTSTATUS pvfs_fsinfo(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_fsinfo *fs)
+{
+ NTSTATUS status;
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ uint64_t blocks_free, blocks_total;
+ unsigned int bpunit;
+ struct stat st;
+ const uint16_t block_size = 512;
+
+ /* only some levels need the expensive sys_fsusage() call */
+ switch (fs->generic.level) {
+ case RAW_QFS_DSKATTR:
+ case RAW_QFS_ALLOCATION:
+ case RAW_QFS_SIZE_INFO:
+ case RAW_QFS_SIZE_INFORMATION:
+ case RAW_QFS_FULL_SIZE_INFORMATION:
+ if (sys_fsusage(pvfs->base_directory, &blocks_free, &blocks_total) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (stat(pvfs->base_directory, &st) != 0) {
+ return NT_STATUS_DISK_CORRUPT_ERROR;
+ }
+
+ /* now fill in the out fields */
+ switch (fs->generic.level) {
+ case RAW_QFS_GENERIC:
+ return NT_STATUS_INVALID_LEVEL;
+
+ case RAW_QFS_DSKATTR:
+ /* we need to scale the sizes to fit */
+ for (bpunit=64; bpunit<0x10000; bpunit *= 2) {
+ if (blocks_total * (double)block_size < bpunit * 512 * 65535.0) {
+ break;
+ }
+ }
+ fs->dskattr.out.blocks_per_unit = bpunit;
+ fs->dskattr.out.block_size = block_size;
+ fs->dskattr.out.units_total = (blocks_total * (double)block_size) / (bpunit * 512);
+ fs->dskattr.out.units_free = (blocks_free * (double)block_size) / (bpunit * 512);
+
+ /* we must return a maximum of 2G to old DOS systems, or they get very confused */
+ if (bpunit > 64 && req->ctx->protocol <= PROTOCOL_LANMAN2) {
+ fs->dskattr.out.blocks_per_unit = 64;
+ fs->dskattr.out.units_total = 0xFFFF;
+ fs->dskattr.out.units_free = 0xFFFF;
+ }
+ return NT_STATUS_OK;
+
+ case RAW_QFS_ALLOCATION:
+ fs->allocation.out.fs_id = st.st_dev;
+ fs->allocation.out.total_alloc_units = blocks_total;
+ fs->allocation.out.avail_alloc_units = blocks_free;
+ fs->allocation.out.sectors_per_unit = 1;
+ fs->allocation.out.bytes_per_sector = block_size;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_VOLUME:
+ fs->volume.out.serial_number = st.st_ino;
+ fs->volume.out.volume_name.s = pvfs->share_name;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_VOLUME_INFO:
+ case RAW_QFS_VOLUME_INFORMATION:
+ unix_to_nt_time(&fs->volume_info.out.create_time, st.st_ctime);
+ fs->volume_info.out.serial_number = st.st_ino;
+ fs->volume_info.out.volume_name.s = pvfs->share_name;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_SIZE_INFO:
+ case RAW_QFS_SIZE_INFORMATION:
+ fs->size_info.out.total_alloc_units = blocks_total;
+ fs->size_info.out.avail_alloc_units = blocks_free;
+ fs->size_info.out.sectors_per_unit = 1;
+ fs->size_info.out.bytes_per_sector = block_size;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_DEVICE_INFO:
+ case RAW_QFS_DEVICE_INFORMATION:
+ fs->device_info.out.device_type = 0;
+ fs->device_info.out.characteristics = 0;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_ATTRIBUTE_INFO:
+ case RAW_QFS_ATTRIBUTE_INFORMATION:
+ fs->attribute_info.out.fs_attr = pvfs->fs_attribs;
+ fs->attribute_info.out.max_file_component_length = 255;
+ fs->attribute_info.out.fs_type.s = ntvfs->ctx->fs_type;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_QUOTA_INFORMATION:
+ ZERO_STRUCT(fs->quota_information.out.unknown);
+ fs->quota_information.out.quota_soft = 0;
+ fs->quota_information.out.quota_hard = 0;
+ fs->quota_information.out.quota_flags = 0;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_FULL_SIZE_INFORMATION:
+ fs->full_size_information.out.total_alloc_units = blocks_total;
+ fs->full_size_information.out.call_avail_alloc_units = blocks_free;
+ fs->full_size_information.out.actual_avail_alloc_units = blocks_free;
+ fs->full_size_information.out.sectors_per_unit = 1;
+ fs->full_size_information.out.bytes_per_sector = block_size;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_OBJECTID_INFORMATION:
+ ZERO_STRUCT(fs->objectid_information.out.guid);
+ ZERO_STRUCT(fs->objectid_information.out.unknown);
+
+ status = pvfs_cache_base_fs_uuid(pvfs, &st);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ fs->objectid_information.out.guid = *pvfs->base_fs_uuid;
+ return NT_STATUS_OK;
+
+ case RAW_QFS_SECTOR_SIZE_INFORMATION:
+ fs->sector_size_info.out.logical_bytes_per_sector = block_size;
+ fs->sector_size_info.out.phys_bytes_per_sector_atomic
+ = block_size;
+ fs->sector_size_info.out.phys_bytes_per_sector_perf
+ = block_size;
+ fs->sector_size_info.out.fs_effective_phys_bytes_per_sector_atomic
+ = block_size;
+ fs->sector_size_info.out.flags
+ = QFS_SSINFO_FLAGS_ALIGNED_DEVICE
+ | QFS_SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE;
+ fs->sector_size_info.out.byte_off_sector_align = 0;
+ fs->sector_size_info.out.byte_off_partition_align = 0;
+ return NT_STATUS_OK;
+
+ default:
+ break;
+ }
+ return NT_STATUS_INVALID_LEVEL;
+}
diff --git a/source4/ntvfs/posix/pvfs_ioctl.c b/source4/ntvfs/posix/pvfs_ioctl.c
new file mode 100644
index 0000000..1d5e8fd
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_ioctl.c
@@ -0,0 +1,82 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - open and close
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "../libcli/smb/smb_constants.h"
+
+/*
+ old ioctl interface
+*/
+static NTSTATUS pvfs_ioctl_old(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_ioctl *io)
+{
+ return NT_STATUS_DOS(ERRSRV, ERRerror);
+}
+
+/*
+ nt ioctl interface
+*/
+static NTSTATUS pvfs_ntioctl(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_ioctl *io)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+
+ f = pvfs_find_fd(pvfs, req, io->ntioctl.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ switch (io->ntioctl.in.function) {
+ case FSCTL_SET_SPARSE:
+ /* maybe some posix systems have a way of marking
+ a file non-sparse? */
+ io->ntioctl.out.blob = data_blob(NULL, 0);
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+/*
+ ioctl interface
+*/
+NTSTATUS pvfs_ioctl(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_ioctl *io)
+{
+ switch (io->generic.level) {
+ case RAW_IOCTL_IOCTL:
+ return pvfs_ioctl_old(ntvfs, req, io);
+
+ case RAW_IOCTL_NTIOCTL:
+ return pvfs_ntioctl(ntvfs, req, io);
+
+ case RAW_IOCTL_SMB2:
+ case RAW_IOCTL_SMB2_NO_HANDLE:
+ /* see WSPP SMB2 test 46 */
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
diff --git a/source4/ntvfs/posix/pvfs_lock.c b/source4/ntvfs/posix/pvfs_lock.c
new file mode 100644
index 0000000..0802ded
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_lock.c
@@ -0,0 +1,404 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - locking
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "system/time.h"
+#include "../lib/util/dlinklist.h"
+#include "messaging/messaging.h"
+
+
+/*
+ check if we can perform IO on a range that might be locked
+*/
+NTSTATUS pvfs_check_lock(struct pvfs_state *pvfs,
+ struct pvfs_file *f,
+ uint32_t smbpid,
+ uint64_t offset, uint64_t count,
+ enum brl_type rw)
+{
+ if (!(pvfs->flags & PVFS_FLAG_STRICT_LOCKING)) {
+ return NT_STATUS_OK;
+ }
+
+ return brlock_locktest(pvfs->brl_context,
+ f->brl_handle,
+ smbpid,
+ offset, count, rw);
+}
+
+/* this state structure holds information about a lock we are waiting on */
+struct pvfs_pending_lock {
+ struct pvfs_pending_lock *next, *prev;
+ struct pvfs_state *pvfs;
+ union smb_lock *lck;
+ struct pvfs_file *f;
+ struct ntvfs_request *req;
+ int pending_lock;
+ struct pvfs_wait *wait_handle;
+ struct timeval end_time;
+};
+
+/*
+ a secondary attempt to setup a lock has failed - back out
+ the locks we did get and send an error
+*/
+static void pvfs_lock_async_failed(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_file *f,
+ struct smb_lock_entry *locks,
+ int i,
+ NTSTATUS status)
+{
+ /* undo the locks we just did */
+ for (i--;i>=0;i--) {
+ brlock_unlock(pvfs->brl_context,
+ f->brl_handle,
+ locks[i].pid,
+ locks[i].offset,
+ locks[i].count);
+ f->lock_count--;
+ }
+ req->async_states->status = status;
+ req->async_states->send_fn(req);
+}
+
+
+/*
+ called when we receive a pending lock notification. It means that
+ either our lock timed out or someone else has unlocked a overlapping
+ range, so we should try the lock again. Note that on timeout we
+ do retry the lock, giving it a last chance.
+*/
+static void pvfs_pending_lock_continue(void *private_data, enum pvfs_wait_notice reason)
+{
+ struct pvfs_pending_lock *pending = talloc_get_type(private_data,
+ struct pvfs_pending_lock);
+ struct pvfs_state *pvfs = pending->pvfs;
+ struct pvfs_file *f = pending->f;
+ struct ntvfs_request *req = pending->req;
+ union smb_lock *lck = pending->lck;
+ struct smb_lock_entry *locks;
+ enum brl_type rw;
+ NTSTATUS status;
+ int i;
+ bool timed_out;
+
+ timed_out = (reason != PVFS_WAIT_EVENT);
+
+ locks = lck->lockx.in.locks + lck->lockx.in.ulock_cnt;
+
+ if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
+ rw = READ_LOCK;
+ } else {
+ rw = WRITE_LOCK;
+ }
+
+ DLIST_REMOVE(f->pending_list, pending);
+
+ /* we don't retry on a cancel */
+ if (reason == PVFS_WAIT_CANCEL) {
+ if (pvfs->ntvfs->ctx->protocol < PROTOCOL_SMB2_02) {
+ status = NT_STATUS_FILE_LOCK_CONFLICT;
+ } else {
+ status = NT_STATUS_CANCELLED;
+ }
+ } else {
+ /*
+ * here it's important to pass the pending pointer
+ * because with this we'll get the correct error code
+ * FILE_LOCK_CONFLICT in the error case
+ */
+ status = brlock_lock(pvfs->brl_context,
+ f->brl_handle,
+ locks[pending->pending_lock].pid,
+ locks[pending->pending_lock].offset,
+ locks[pending->pending_lock].count,
+ rw, pending);
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ f->lock_count++;
+ timed_out = false;
+ }
+
+ /* if we have failed and timed out, or succeeded, then we
+ don't need the pending lock any more */
+ if (NT_STATUS_IS_OK(status) || timed_out) {
+ NTSTATUS status2;
+ status2 = brlock_remove_pending(pvfs->brl_context,
+ f->brl_handle, pending);
+ if (!NT_STATUS_IS_OK(status2)) {
+ DEBUG(0,("pvfs_lock: failed to remove pending lock - %s\n", nt_errstr(status2)));
+ }
+ talloc_free(pending->wait_handle);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (timed_out) {
+ /* no more chances */
+ pvfs_lock_async_failed(pvfs, req, f, locks, pending->pending_lock, status);
+ talloc_free(pending);
+ } else {
+ /* we can try again */
+ DLIST_ADD(f->pending_list, pending);
+ }
+ return;
+ }
+
+ /* if we haven't timed out yet, then we can do more pending locks */
+ if (rw == READ_LOCK) {
+ rw = PENDING_READ_LOCK;
+ } else {
+ rw = PENDING_WRITE_LOCK;
+ }
+
+ /* we've now got the pending lock. try and get the rest, which might
+ lead to more pending locks */
+ for (i=pending->pending_lock+1;i<lck->lockx.in.lock_cnt;i++) {
+ pending->pending_lock = i;
+
+ status = brlock_lock(pvfs->brl_context,
+ f->brl_handle,
+ locks[i].pid,
+ locks[i].offset,
+ locks[i].count,
+ rw, pending);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* a timed lock failed - setup a wait message to handle
+ the pending lock notification or a timeout */
+ pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY,
+ pending->end_time,
+ pvfs_pending_lock_continue,
+ pending);
+ if (pending->wait_handle == NULL) {
+ pvfs_lock_async_failed(pvfs, req, f, locks, i, NT_STATUS_NO_MEMORY);
+ talloc_free(pending);
+ } else {
+ talloc_steal(pending, pending->wait_handle);
+ DLIST_ADD(f->pending_list, pending);
+ }
+ return;
+ }
+
+ f->lock_count++;
+ }
+
+ /* we've managed to get all the locks. Tell the client */
+ req->async_states->status = NT_STATUS_OK;
+ req->async_states->send_fn(req);
+ talloc_free(pending);
+}
+
+
+/*
+ called when we close a file that might have locks
+*/
+void pvfs_lock_close(struct pvfs_state *pvfs, struct pvfs_file *f)
+{
+ struct pvfs_pending_lock *p, *next;
+
+ if (f->lock_count || f->pending_list) {
+ DEBUG(5,("pvfs_lock: removing %.0f locks on close\n",
+ (double)f->lock_count));
+ brlock_close(f->pvfs->brl_context, f->brl_handle);
+ f->lock_count = 0;
+ }
+
+ /* reply to all the pending lock requests, telling them the
+ lock failed */
+ for (p=f->pending_list;p;p=next) {
+ next = p->next;
+ DLIST_REMOVE(f->pending_list, p);
+ p->req->async_states->status = NT_STATUS_RANGE_NOT_LOCKED;
+ p->req->async_states->send_fn(p->req);
+ }
+}
+
+
+/*
+ cancel a set of locks
+*/
+static NTSTATUS pvfs_lock_cancel(struct pvfs_state *pvfs, struct ntvfs_request *req, union smb_lock *lck,
+ struct pvfs_file *f)
+{
+ struct pvfs_pending_lock *p;
+
+ for (p=f->pending_list;p;p=p->next) {
+ /* check if the lock request matches exactly - you can only cancel with exact matches */
+ if (p->lck->lockx.in.ulock_cnt == lck->lockx.in.ulock_cnt &&
+ p->lck->lockx.in.lock_cnt == lck->lockx.in.lock_cnt &&
+ p->lck->lockx.in.file.ntvfs== lck->lockx.in.file.ntvfs &&
+ p->lck->lockx.in.mode == (lck->lockx.in.mode & ~LOCKING_ANDX_CANCEL_LOCK)) {
+ int i;
+
+ for (i=0;i<lck->lockx.in.ulock_cnt + lck->lockx.in.lock_cnt;i++) {
+ if (p->lck->lockx.in.locks[i].pid != lck->lockx.in.locks[i].pid ||
+ p->lck->lockx.in.locks[i].offset != lck->lockx.in.locks[i].offset ||
+ p->lck->lockx.in.locks[i].count != lck->lockx.in.locks[i].count) {
+ break;
+ }
+ }
+ if (i < lck->lockx.in.ulock_cnt) continue;
+
+ /* an exact match! we can cancel it, which is equivalent
+ to triggering the timeout early */
+ pvfs_pending_lock_continue(p, PVFS_WAIT_TIMEOUT);
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_DOS(ERRDOS, ERRcancelviolation);
+}
+
+
+/*
+ lock or unlock a byte range
+*/
+NTSTATUS pvfs_lock(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_lock *lck)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+ struct smb_lock_entry *locks;
+ int i;
+ enum brl_type rw;
+ struct pvfs_pending_lock *pending = NULL;
+ NTSTATUS status;
+
+ if (lck->generic.level != RAW_LOCK_GENERIC) {
+ return ntvfs_map_lock(ntvfs, req, lck);
+ }
+
+ if (lck->lockx.in.mode & LOCKING_ANDX_OPLOCK_RELEASE) {
+ return pvfs_oplock_release(ntvfs, req, lck);
+ }
+
+ f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (f->handle->fd == -1) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ status = pvfs_break_level2_oplocks(f);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (lck->lockx.in.timeout != 0 &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ pending = talloc(f, struct pvfs_pending_lock);
+ if (pending == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pending->pvfs = pvfs;
+ pending->lck = lck;
+ pending->f = f;
+ pending->req = req;
+
+ pending->end_time =
+ timeval_current_ofs_msec(lck->lockx.in.timeout);
+ }
+
+ if (lck->lockx.in.mode & LOCKING_ANDX_SHARED_LOCK) {
+ rw = pending? PENDING_READ_LOCK : READ_LOCK;
+ } else {
+ rw = pending? PENDING_WRITE_LOCK : WRITE_LOCK;
+ }
+
+ if (lck->lockx.in.mode & LOCKING_ANDX_CANCEL_LOCK) {
+ talloc_free(pending);
+ return pvfs_lock_cancel(pvfs, req, lck, f);
+ }
+
+ if (lck->lockx.in.mode & LOCKING_ANDX_CHANGE_LOCKTYPE) {
+ /* this seems to not be supported by any windows server,
+ or used by any clients */
+ talloc_free(pending);
+ return NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks);
+ }
+
+ /* the unlocks happen first */
+ locks = lck->lockx.in.locks;
+
+ for (i=0;i<lck->lockx.in.ulock_cnt;i++) {
+ status = brlock_unlock(pvfs->brl_context,
+ f->brl_handle,
+ locks[i].pid,
+ locks[i].offset,
+ locks[i].count);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(pending);
+ return status;
+ }
+ f->lock_count--;
+ }
+
+ locks += i;
+
+ for (i=0;i<lck->lockx.in.lock_cnt;i++) {
+ if (pending) {
+ pending->pending_lock = i;
+ }
+
+ status = brlock_lock(pvfs->brl_context,
+ f->brl_handle,
+ locks[i].pid,
+ locks[i].offset,
+ locks[i].count,
+ rw, pending);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (pending) {
+ /* a timed lock failed - setup a wait message to handle
+ the pending lock notification or a timeout */
+ pending->wait_handle = pvfs_wait_message(pvfs, req, MSG_BRL_RETRY,
+ pending->end_time,
+ pvfs_pending_lock_continue,
+ pending);
+ if (pending->wait_handle == NULL) {
+ talloc_free(pending);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_steal(pending, pending->wait_handle);
+ DLIST_ADD(f->pending_list, pending);
+ return NT_STATUS_OK;
+ }
+
+ /* undo the locks we just did */
+ for (i--;i>=0;i--) {
+ brlock_unlock(pvfs->brl_context,
+ f->brl_handle,
+ locks[i].pid,
+ locks[i].offset,
+ locks[i].count);
+ f->lock_count--;
+ }
+ talloc_free(pending);
+ return status;
+ }
+ f->lock_count++;
+ }
+
+ talloc_free(pending);
+ return NT_STATUS_OK;
+}
diff --git a/source4/ntvfs/posix/pvfs_mkdir.c b/source4/ntvfs/posix/pvfs_mkdir.c
new file mode 100644
index 0000000..2cf43ab
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_mkdir.c
@@ -0,0 +1,196 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - mkdir and rmdir
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "system/dir.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/security.h"
+
+/*
+ create a directory with EAs
+*/
+static NTSTATUS pvfs_t2mkdir(struct pvfs_state *pvfs,
+ struct ntvfs_request *req, union smb_mkdir *md)
+{
+ NTSTATUS status;
+ struct pvfs_filename *name;
+ mode_t mode;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, md->t2mkdir.in.path, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (name->exists) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ mode = pvfs_fileperms(pvfs, FILE_ATTRIBUTE_DIRECTORY);
+
+ if (pvfs_sys_mkdir(pvfs, name->full_name, mode, name->allow_override) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ pvfs_xattr_unlink_hook(pvfs, name->full_name);
+
+ status = pvfs_resolve_name(pvfs, req, md->t2mkdir.in.path, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!name->exists ||
+ !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* setup an inherited acl from the parent */
+ status = pvfs_acl_inherit(pvfs, req, name, -1);
+ if (!NT_STATUS_IS_OK(status)) {
+ pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override);
+ return status;
+ }
+
+ /* setup any EAs that were asked for */
+ status = pvfs_setfileinfo_ea_set(pvfs, name, -1,
+ md->t2mkdir.in.num_eas,
+ md->t2mkdir.in.eas);
+ if (!NT_STATUS_IS_OK(status)) {
+ pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override);
+ return status;
+ }
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ name->full_name);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ create a directory
+*/
+NTSTATUS pvfs_mkdir(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_mkdir *md)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ NTSTATUS status;
+ struct pvfs_filename *name;
+ mode_t mode;
+
+ if (md->generic.level == RAW_MKDIR_T2MKDIR) {
+ return pvfs_t2mkdir(pvfs, req, md);
+ }
+
+ if (md->generic.level != RAW_MKDIR_MKDIR) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, md->mkdir.in.path, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (name->exists) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ mode = pvfs_fileperms(pvfs, FILE_ATTRIBUTE_DIRECTORY);
+
+ if (pvfs_sys_mkdir(pvfs, name->full_name, mode, name->allow_override) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ pvfs_xattr_unlink_hook(pvfs, name->full_name);
+
+ /* setup an inherited acl from the parent */
+ status = pvfs_acl_inherit(pvfs, req, name, -1);
+ if (!NT_STATUS_IS_OK(status)) {
+ pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override);
+ return status;
+ }
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ name->full_name);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ remove a directory
+*/
+NTSTATUS pvfs_rmdir(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, struct smb_rmdir *rd)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ NTSTATUS status;
+ struct pvfs_filename *name;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, rd->in.path, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!name->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = pvfs_access_check_simple(pvfs, req, name, SEC_STD_DELETE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_xattr_unlink_hook(pvfs, name->full_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override) == -1) {
+ /* some olders systems don't return ENOTEMPTY to rmdir() */
+ if (errno == EEXIST) {
+ return NT_STATUS_DIRECTORY_NOT_EMPTY;
+ }
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ name->full_name);
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/ntvfs/posix/pvfs_notify.c b/source4/ntvfs/posix/pvfs_notify.c
new file mode 100644
index 0000000..91a151b
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_notify.c
@@ -0,0 +1,300 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - notify
+
+ Copyright (C) Andrew Tridgell 2006
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "lib/messaging/irpc.h"
+#include "messaging/messaging.h"
+#include "../lib/util/dlinklist.h"
+#include "lib/events/events.h"
+
+/* pending notifies buffer, hung off struct pvfs_file for open directories
+ that have used change notify */
+struct pvfs_notify_buffer {
+ struct pvfs_file *f;
+ uint32_t num_changes;
+ struct notify_changes *changes;
+ uint32_t max_buffer_size;
+ uint32_t current_buffer_size;
+ bool overflowed;
+
+ /* a list of requests waiting for events on this handle */
+ struct notify_pending {
+ struct notify_pending *next, *prev;
+ struct ntvfs_request *req;
+ union smb_notify *info;
+ } *pending;
+};
+
+/*
+ send a notify on the next event run.
+*/
+static void pvfs_notify_send_next(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct ntvfs_request *req = talloc_get_type(ptr, struct ntvfs_request);
+ req->async_states->send_fn(req);
+}
+
+
+/*
+ send a reply to a pending notify request
+*/
+static void pvfs_notify_send(struct pvfs_notify_buffer *notify_buffer,
+ NTSTATUS status, bool immediate)
+{
+ struct notify_pending *pending = notify_buffer->pending;
+ struct ntvfs_request *req;
+ union smb_notify *info;
+
+ if (notify_buffer->current_buffer_size > notify_buffer->max_buffer_size &&
+ notify_buffer->num_changes != 0) {
+ /* on buffer overflow return no changes and destroys the notify buffer */
+ notify_buffer->num_changes = 0;
+ while (notify_buffer->pending) {
+ pvfs_notify_send(notify_buffer, NT_STATUS_OK, immediate);
+ }
+ notify_buffer->overflowed = true;
+ return;
+ }
+
+ /* see if there is anyone waiting */
+ if (notify_buffer->pending == NULL) {
+ return;
+ }
+
+ DLIST_REMOVE(notify_buffer->pending, pending);
+
+ req = pending->req;
+ info = pending->info;
+
+ info->nttrans.out.num_changes = notify_buffer->num_changes;
+ info->nttrans.out.changes = talloc_steal(req, notify_buffer->changes);
+ notify_buffer->num_changes = 0;
+ notify_buffer->overflowed = false;
+ notify_buffer->changes = NULL;
+ notify_buffer->current_buffer_size = 0;
+
+ talloc_free(pending);
+
+ if (info->nttrans.out.num_changes != 0) {
+ status = NT_STATUS_OK;
+ }
+
+ req->async_states->status = status;
+
+ if (immediate) {
+ req->async_states->send_fn(req);
+ return;
+ }
+
+ /* we can't call pvfs_notify_send() directly here, as that
+ would free the request, and the ntvfs modules above us
+ could use it, so call it on the next event */
+ tevent_add_timer(req->ctx->event_ctx,
+ req, timeval_zero(), pvfs_notify_send_next, req);
+}
+
+/*
+ destroy a notify buffer. Called when the handle is closed
+ */
+static int pvfs_notify_destructor(struct pvfs_notify_buffer *n)
+{
+ notify_remove(n->f->pvfs->notify_context, n);
+ n->f->notify_buffer = NULL;
+ pvfs_notify_send(n, NT_STATUS_OK, true);
+ return 0;
+}
+
+
+/*
+ called when a async notify event comes in
+*/
+static void pvfs_notify_callback(void *private_data, const struct notify_event *ev)
+{
+ struct pvfs_notify_buffer *n = talloc_get_type(private_data, struct pvfs_notify_buffer);
+ size_t len;
+ struct notify_changes *n2;
+ char *new_path;
+
+ if (n->overflowed) {
+ return;
+ }
+
+ n2 = talloc_realloc(n, n->changes, struct notify_changes, n->num_changes+1);
+ if (n2 == NULL) {
+ /* nothing much we can do for this */
+ return;
+ }
+ n->changes = n2;
+
+ new_path = talloc_strdup(n->changes, ev->path);
+ if (new_path == NULL) {
+ return;
+ }
+ string_replace(new_path, '/', '\\');
+
+ n->changes[n->num_changes].action = ev->action;
+ n->changes[n->num_changes].name.s = new_path;
+ n->num_changes++;
+
+ /*
+ work out how much room this will take in the buffer
+ */
+ len = 12 + strlen_m(ev->path)*2;
+ if (len & 3) {
+ len += 4 - (len & 3);
+ }
+ n->current_buffer_size += len;
+
+ /* send what we have, unless its the first part of a rename */
+ if (ev->action != NOTIFY_ACTION_OLD_NAME) {
+ pvfs_notify_send(n, NT_STATUS_OK, true);
+ }
+}
+
+/*
+ setup a notify buffer on a directory handle
+*/
+static NTSTATUS pvfs_notify_setup(struct pvfs_state *pvfs, struct pvfs_file *f,
+ uint32_t buffer_size, uint32_t filter, bool recursive)
+{
+ NTSTATUS status;
+ struct notify_entry e;
+
+ /* We may not fill in all the elements in this entry -
+ * structure may in future be shared with Samba3 */
+ ZERO_STRUCT(e);
+
+ /* We may not fill in all the elements in this entry -
+ * structure may in future be shared with Samba3 */
+ ZERO_STRUCT(e);
+
+ f->notify_buffer = talloc_zero(f, struct pvfs_notify_buffer);
+ NT_STATUS_HAVE_NO_MEMORY(f->notify_buffer);
+
+ f->notify_buffer->max_buffer_size = buffer_size;
+ f->notify_buffer->f = f;
+
+ e.filter = filter;
+ e.path = f->handle->name->full_name;
+ if (recursive) {
+ e.subdir_filter = filter;
+ } else {
+ e.subdir_filter = 0;
+ }
+
+ status = notify_add(pvfs->notify_context, &e,
+ pvfs_notify_callback, f->notify_buffer);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ talloc_set_destructor(f->notify_buffer, pvfs_notify_destructor);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ called from the pvfs_wait code when either an event has come in, or
+ the notify request has been cancelled
+*/
+static void pvfs_notify_end(void *private_data, enum pvfs_wait_notice reason)
+{
+ struct pvfs_notify_buffer *notify_buffer = talloc_get_type(private_data,
+ struct pvfs_notify_buffer);
+ if (reason == PVFS_WAIT_CANCEL) {
+ pvfs_notify_send(notify_buffer, NT_STATUS_CANCELLED, false);
+ } else {
+ pvfs_notify_send(notify_buffer, NT_STATUS_OK, true);
+ }
+}
+
+/* change notify request - always async. This request blocks until the
+ event buffer is non-empty */
+NTSTATUS pvfs_notify(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_notify *info)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+ NTSTATUS status;
+ struct notify_pending *pending;
+
+ if (info->nttrans.level != RAW_NOTIFY_NTTRANS) {
+ return ntvfs_map_notify(ntvfs, req, info);
+ }
+
+ f = pvfs_find_fd(pvfs, req, info->nttrans.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* this request doesn't make sense unless its async */
+ if (!(req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* its only valid for directories */
+ if (f->handle->fd != -1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* if the handle doesn't currently have a notify buffer then
+ create one */
+ if (f->notify_buffer == NULL) {
+ status = pvfs_notify_setup(pvfs, f,
+ info->nttrans.in.buffer_size,
+ info->nttrans.in.completion_filter,
+ info->nttrans.in.recursive);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ /* we update the max_buffer_size on each call, but we do not
+ update the recursive flag or filter */
+ f->notify_buffer->max_buffer_size = info->nttrans.in.buffer_size;
+
+ pending = talloc(f->notify_buffer, struct notify_pending);
+ NT_STATUS_HAVE_NO_MEMORY(pending);
+
+ pending->req = talloc_reference(pending, req);
+ NT_STATUS_HAVE_NO_MEMORY(pending->req);
+ pending->info = info;
+
+ DLIST_ADD_END(f->notify_buffer->pending, pending);
+
+ /* if the buffer is empty then start waiting */
+ if (f->notify_buffer->num_changes == 0 &&
+ !f->notify_buffer->overflowed) {
+ struct pvfs_wait *wait_handle;
+ wait_handle = pvfs_wait_message(pvfs, req, -1,
+ timeval_zero(),
+ pvfs_notify_end,
+ f->notify_buffer);
+ NT_STATUS_HAVE_NO_MEMORY(wait_handle);
+ talloc_steal(req, wait_handle);
+ return NT_STATUS_OK;
+ }
+
+ req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+ pvfs_notify_send(f->notify_buffer, NT_STATUS_OK, false);
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/ntvfs/posix/pvfs_open.c b/source4/ntvfs/posix/pvfs_open.c
new file mode 100644
index 0000000..860861d
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_open.c
@@ -0,0 +1,2094 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - open and close
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "system/dir.h"
+#include "system/time.h"
+#include "../lib/util/dlinklist.h"
+#include "messaging/messaging.h"
+#include "librpc/gen_ndr/xattr.h"
+
+/*
+ find open file handle given fnum
+*/
+struct pvfs_file *pvfs_find_fd(struct pvfs_state *pvfs,
+ struct ntvfs_request *req, struct ntvfs_handle *h)
+{
+ void *p;
+ struct pvfs_file *f;
+
+ p = ntvfs_handle_get_backend_data(h, pvfs->ntvfs);
+ if (!p) return NULL;
+
+ f = talloc_get_type(p, struct pvfs_file);
+ if (!f) return NULL;
+
+ return f;
+}
+
+/*
+ cleanup a open directory handle
+*/
+static int pvfs_dir_handle_destructor(struct pvfs_file_handle *h)
+{
+ if (h->have_opendb_entry) {
+ struct odb_lock *lck;
+ NTSTATUS status;
+ const char *delete_path = NULL;
+
+ lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for close\n"));
+ return 0;
+ }
+
+ status = odb_close_file(lck, h, &delete_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to remove opendb entry for '%s' - %s\n",
+ h->name->full_name, nt_errstr(status)));
+ }
+
+ if (h->name->stream_name == NULL && delete_path) {
+ status = pvfs_xattr_unlink_hook(h->pvfs, delete_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Warning: xattr unlink hook failed for '%s' - %s\n",
+ delete_path, nt_errstr(status)));
+ }
+ if (pvfs_sys_rmdir(h->pvfs, delete_path, h->name->allow_override) != 0) {
+ DEBUG(0,("pvfs_dir_handle_destructor: failed to rmdir '%s' - %s\n",
+ delete_path, strerror(errno)));
+ }
+ }
+
+ talloc_free(lck);
+ }
+
+ return 0;
+}
+
+/*
+ cleanup a open directory fnum
+*/
+static int pvfs_dir_fnum_destructor(struct pvfs_file *f)
+{
+ DLIST_REMOVE(f->pvfs->files.list, f);
+ ntvfs_handle_remove_backend_data(f->ntvfs, f->pvfs->ntvfs);
+
+ return 0;
+}
+
+/*
+ setup any EAs and the ACL on newly created files/directories
+*/
+static NTSTATUS pvfs_open_setup_eas_acl(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ int fd, struct pvfs_file *f,
+ union smb_open *io,
+ struct security_descriptor *sd)
+{
+ NTSTATUS status = NT_STATUS_OK;
+
+ /* setup any EAs that were asked for */
+ if (io->ntcreatex.in.ea_list) {
+ status = pvfs_setfileinfo_ea_set(pvfs, name, fd,
+ io->ntcreatex.in.ea_list->num_eas,
+ io->ntcreatex.in.ea_list->eas);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* setup an initial sec_desc if requested */
+ if (sd && (sd->type & SEC_DESC_DACL_PRESENT)) {
+ union smb_setfileinfo set;
+/*
+ * TODO: set the full ACL!
+ * - vista denies the creation of the file with NT_STATUS_PRIVILEGE_NOT_HELD,
+ * when a SACL is present on the sd,
+ * but the user doesn't have SeSecurityPrivilege
+ * - w2k3 allows it
+ */
+ set.set_secdesc.in.file.ntvfs = f->ntvfs;
+ set.set_secdesc.in.secinfo_flags = SECINFO_DACL;
+ set.set_secdesc.in.sd = sd;
+
+ status = pvfs_acl_set(pvfs, req, name, fd, SEC_STD_WRITE_DAC, &set);
+ }
+
+ return status;
+}
+
+/*
+ form the lock context used for opendb locking. Note that we must
+ zero here to take account of possible padding on some architectures
+*/
+NTSTATUS pvfs_locking_key(struct pvfs_filename *name,
+ TALLOC_CTX *mem_ctx, DATA_BLOB *key)
+{
+ struct {
+ dev_t device;
+ ino_t inode;
+ } lock_context;
+ ZERO_STRUCT(lock_context);
+
+ lock_context.device = name->st.st_dev;
+ lock_context.inode = name->st.st_ino;
+
+ *key = data_blob_talloc(mem_ctx, &lock_context, sizeof(lock_context));
+ if (key->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ open a directory
+*/
+static NTSTATUS pvfs_open_directory(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ union smb_open *io)
+{
+ struct pvfs_file *f;
+ struct ntvfs_handle *h;
+ NTSTATUS status;
+ uint32_t create_action;
+ uint32_t access_mask = io->generic.in.access_mask;
+ struct odb_lock *lck;
+ bool del_on_close;
+ uint32_t create_options;
+ uint32_t share_access;
+ bool forced;
+ struct security_descriptor *sd = NULL;
+
+ create_options = io->generic.in.create_options;
+ share_access = io->generic.in.share_access;
+
+ forced = (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY)?true:false;
+
+ if (name->stream_name) {
+ if (forced) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ } else {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ }
+
+ /* if the client says it must be a directory, and it isn't,
+ then fail */
+ if (name->exists && !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /* found with gentest */
+ if (io->ntcreatex.in.access_mask == SEC_FLAG_MAXIMUM_ALLOWED &&
+ (io->ntcreatex.in.create_options & NTCREATEX_OPTIONS_DIRECTORY) &&
+ (io->ntcreatex.in.create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) {
+ DEBUG(3,(__location__ ": Invalid access_mask/create_options 0x%08x 0x%08x for %s\n",
+ io->ntcreatex.in.access_mask, io->ntcreatex.in.create_options, name->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (io->generic.in.open_disposition) {
+ case NTCREATEX_DISP_OPEN_IF:
+ break;
+
+ case NTCREATEX_DISP_OPEN:
+ if (!name->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ break;
+
+ case NTCREATEX_DISP_CREATE:
+ if (name->exists) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+ break;
+
+ case NTCREATEX_DISP_OVERWRITE_IF:
+ case NTCREATEX_DISP_OVERWRITE:
+ case NTCREATEX_DISP_SUPERSEDE:
+ default:
+ DEBUG(3,(__location__ ": Invalid open disposition 0x%08x for %s\n",
+ io->generic.in.open_disposition, name->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = ntvfs_handle_new(pvfs->ntvfs, req, &h);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ f = talloc(h, struct pvfs_file);
+ if (f == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ f->handle = talloc(f, struct pvfs_file_handle);
+ if (f->handle == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (name->exists) {
+ /* check the security descriptor */
+ status = pvfs_access_check(pvfs, req, name, &access_mask);
+ } else {
+ sd = io->ntcreatex.in.sec_desc;
+ status = pvfs_access_check_create(pvfs, req, name, &access_mask, true, &sd);
+ }
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (io->generic.in.query_maximal_access) {
+ status = pvfs_access_maximal_allowed(pvfs, req, name,
+ &io->generic.out.maximal_access);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ f->ntvfs = h;
+ f->pvfs = pvfs;
+ f->pending_list = NULL;
+ f->lock_count = 0;
+ f->share_access = io->generic.in.share_access;
+ f->impersonation = io->generic.in.impersonation;
+ f->access_mask = access_mask;
+ f->brl_handle = NULL;
+ f->notify_buffer = NULL;
+ f->search = NULL;
+
+ f->handle->pvfs = pvfs;
+ f->handle->name = talloc_steal(f->handle, name);
+ f->handle->fd = -1;
+ f->handle->odb_locking_key = data_blob(NULL, 0);
+ f->handle->create_options = io->generic.in.create_options;
+ f->handle->private_flags = io->generic.in.private_flags;
+ f->handle->seek_offset = 0;
+ f->handle->position = 0;
+ f->handle->mode = 0;
+ f->handle->oplock = NULL;
+ ZERO_STRUCT(f->handle->write_time);
+ f->handle->open_completed = false;
+
+ if ((create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) &&
+ pvfs_directory_empty(pvfs, f->handle->name)) {
+ del_on_close = true;
+ } else {
+ del_on_close = false;
+ }
+
+ if (name->exists) {
+ /* form the lock context used for opendb locking */
+ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* get a lock on this file before the actual open */
+ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n",
+ name->full_name));
+ /* we were supposed to do a blocking lock, so something
+ is badly wrong! */
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* see if we are allowed to open at the same time as existing opens */
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, del_on_close,
+ io->generic.in.open_disposition, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+
+ /* now really mark the file as open */
+ status = odb_open_file(lck, f->handle, name->full_name,
+ NULL, name->dos.write_time,
+ false, OPLOCK_NONE, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+
+ f->handle->have_opendb_entry = true;
+ }
+
+ DLIST_ADD(pvfs->files.list, f);
+
+ /* setup destructors to avoid leaks on abnormal termination */
+ talloc_set_destructor(f->handle, pvfs_dir_handle_destructor);
+ talloc_set_destructor(f, pvfs_dir_fnum_destructor);
+
+ if (!name->exists) {
+ uint32_t attrib = io->generic.in.file_attr | FILE_ATTRIBUTE_DIRECTORY;
+ mode_t mode = pvfs_fileperms(pvfs, attrib);
+
+ if (pvfs_sys_mkdir(pvfs, name->full_name, mode, name->allow_override) == -1) {
+ return pvfs_map_errno(pvfs,errno);
+ }
+
+ pvfs_xattr_unlink_hook(pvfs, name->full_name);
+
+ status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ status = pvfs_open_setup_eas_acl(pvfs, req, name, -1, f, io, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ /* form the lock context used for opendb locking */
+ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n",
+ name->full_name));
+ /* we were supposed to do a blocking lock, so something
+ is badly wrong! */
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, del_on_close,
+ io->generic.in.open_disposition, false);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ status = odb_open_file(lck, f->handle, name->full_name,
+ NULL, name->dos.write_time,
+ false, OPLOCK_NONE, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ f->handle->have_opendb_entry = true;
+
+ create_action = NTCREATEX_ACTION_CREATED;
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ name->full_name);
+ } else {
+ create_action = NTCREATEX_ACTION_EXISTED;
+ }
+
+ if (!name->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (io->generic.in.query_on_disk_id) {
+ ZERO_ARRAY(io->generic.out.on_disk_id);
+ SBVAL(io->generic.out.on_disk_id, 0, name->st.st_ino);
+ SBVAL(io->generic.out.on_disk_id, 8, name->st.st_dev);
+ }
+
+ /* the open succeeded, keep this handle permanently */
+ status = ntvfs_handle_set_backend_data(h, pvfs->ntvfs, f);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ f->handle->open_completed = true;
+
+ io->generic.out.oplock_level = OPLOCK_NONE;
+ io->generic.out.file.ntvfs = h;
+ io->generic.out.create_action = create_action;
+ io->generic.out.create_time = name->dos.create_time;
+ io->generic.out.access_time = name->dos.access_time;
+ io->generic.out.write_time = name->dos.write_time;
+ io->generic.out.change_time = name->dos.change_time;
+ io->generic.out.attrib = name->dos.attrib;
+ io->generic.out.alloc_size = name->dos.alloc_size;
+ io->generic.out.size = name->st.st_size;
+ io->generic.out.file_type = FILE_TYPE_DISK;
+ io->generic.out.ipc_state = 0;
+ io->generic.out.is_directory = 1;
+
+ return NT_STATUS_OK;
+
+cleanup_delete:
+ pvfs_sys_rmdir(pvfs, name->full_name, name->allow_override);
+ return status;
+}
+
+/*
+ destroy a struct pvfs_file_handle
+*/
+static int pvfs_handle_destructor(struct pvfs_file_handle *h)
+{
+ talloc_free(h->write_time.update_event);
+ h->write_time.update_event = NULL;
+
+ if ((h->create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) &&
+ h->name->stream_name) {
+ NTSTATUS status;
+ status = pvfs_stream_delete(h->pvfs, h->name, h->fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to delete stream '%s' on close of '%s'\n",
+ h->name->stream_name, h->name->full_name));
+ }
+ }
+
+ if (h->fd != -1) {
+ if (close(h->fd) != 0) {
+ DEBUG(0,("pvfs_handle_destructor: close(%d) failed for %s - %s\n",
+ h->fd, h->name->full_name, strerror(errno)));
+ }
+ h->fd = -1;
+ }
+
+ if (!h->write_time.update_forced &&
+ h->write_time.update_on_close &&
+ h->write_time.close_time == 0) {
+ struct timeval tv;
+ tv = timeval_current();
+ h->write_time.close_time = timeval_to_nttime(&tv);
+ }
+
+ if (h->have_opendb_entry) {
+ struct odb_lock *lck;
+ NTSTATUS status;
+ const char *delete_path = NULL;
+
+ lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for close\n"));
+ return 0;
+ }
+
+ if (h->write_time.update_forced) {
+ status = odb_get_file_infos(h->pvfs->odb_context,
+ &h->odb_locking_key,
+ NULL,
+ &h->write_time.close_time);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable get write time for '%s' - %s\n",
+ h->name->full_name, nt_errstr(status)));
+ }
+
+ h->write_time.update_forced = false;
+ h->write_time.update_on_close = true;
+ } else if (h->write_time.update_on_close) {
+ status = odb_set_write_time(lck, h->write_time.close_time, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable set write time for '%s' - %s\n",
+ h->name->full_name, nt_errstr(status)));
+ }
+ }
+
+ status = odb_close_file(lck, h, &delete_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to remove opendb entry for '%s' - %s\n",
+ h->name->full_name, nt_errstr(status)));
+ }
+
+ if (h->name->stream_name == NULL &&
+ h->open_completed && delete_path) {
+ status = pvfs_xattr_unlink_hook(h->pvfs, delete_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Warning: xattr unlink hook failed for '%s' - %s\n",
+ delete_path, nt_errstr(status)));
+ }
+ if (pvfs_sys_unlink(h->pvfs, delete_path, h->name->allow_override) != 0) {
+ DEBUG(0,("pvfs_close: failed to delete '%s' - %s\n",
+ delete_path, strerror(errno)));
+ } else {
+ notify_trigger(h->pvfs->notify_context,
+ NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ delete_path);
+ }
+ h->write_time.update_on_close = false;
+ }
+
+ talloc_free(lck);
+ }
+
+ if (h->write_time.update_on_close) {
+ struct timeval tv[2];
+
+ nttime_to_timeval(&tv[0], h->name->dos.access_time);
+ nttime_to_timeval(&tv[1], h->write_time.close_time);
+
+ if (!timeval_is_zero(&tv[0]) || !timeval_is_zero(&tv[1])) {
+ if (utimes(h->name->full_name, tv) == -1) {
+ DEBUG(3,("pvfs_handle_destructor: utimes() failed '%s' - %s\n",
+ h->name->full_name, strerror(errno)));
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ destroy a struct pvfs_file
+*/
+static int pvfs_fnum_destructor(struct pvfs_file *f)
+{
+ DLIST_REMOVE(f->pvfs->files.list, f);
+ pvfs_lock_close(f->pvfs, f);
+ ntvfs_handle_remove_backend_data(f->ntvfs, f->pvfs->ntvfs);
+
+ return 0;
+}
+
+
+/*
+ form the lock context used for byte range locking. This is separate
+ from the locking key used for opendb locking as it needs to take
+ account of file streams (each stream is a separate byte range
+ locking space)
+*/
+static NTSTATUS pvfs_brl_locking_handle(TALLOC_CTX *mem_ctx,
+ struct pvfs_filename *name,
+ struct ntvfs_handle *ntvfs,
+ struct brl_handle **_h)
+{
+ DATA_BLOB odb_key, key;
+ NTSTATUS status;
+ struct brl_handle *h;
+
+ status = pvfs_locking_key(name, mem_ctx, &odb_key);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (name->stream_name == NULL) {
+ key = odb_key;
+ } else {
+ key = data_blob_talloc(mem_ctx, NULL,
+ odb_key.length + strlen(name->stream_name) + 1);
+ NT_STATUS_HAVE_NO_MEMORY(key.data);
+ memcpy(key.data, odb_key.data, odb_key.length);
+ memcpy(key.data + odb_key.length,
+ name->stream_name, strlen(name->stream_name) + 1);
+ data_blob_free(&odb_key);
+ }
+
+ h = brlock_create_handle(mem_ctx, ntvfs, &key);
+ NT_STATUS_HAVE_NO_MEMORY(h);
+
+ *_h = h;
+ return NT_STATUS_OK;
+}
+
+/*
+ create a new file
+*/
+static NTSTATUS pvfs_create_file(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ union smb_open *io)
+{
+ struct pvfs_file *f;
+ NTSTATUS status;
+ struct ntvfs_handle *h;
+ int flags, fd;
+ struct odb_lock *lck;
+ uint32_t create_options = io->generic.in.create_options;
+ uint32_t share_access = io->generic.in.share_access;
+ uint32_t access_mask = io->generic.in.access_mask;
+ mode_t mode;
+ uint32_t attrib;
+ bool del_on_close;
+ struct pvfs_filename *parent;
+ uint32_t oplock_level = OPLOCK_NONE, oplock_granted;
+ bool allow_level_II_oplock = false;
+ struct security_descriptor *sd = NULL;
+
+ if (io->ntcreatex.in.file_attr & ~FILE_ATTRIBUTE_ALL_MASK) {
+ DEBUG(3,(__location__ ": Invalid file_attr 0x%08x for %s\n",
+ io->ntcreatex.in.file_attr, name->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (io->ntcreatex.in.file_attr & FILE_ATTRIBUTE_ENCRYPTED) {
+ DEBUG(3,(__location__ ": Invalid encryption request for %s\n",
+ name->original_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if ((io->ntcreatex.in.file_attr & FILE_ATTRIBUTE_READONLY) &&
+ (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) {
+ DEBUG(4,(__location__ ": Invalid delete on close for readonly file %s\n",
+ name->original_name));
+ return NT_STATUS_CANNOT_DELETE;
+ }
+
+ sd = io->ntcreatex.in.sec_desc;
+ status = pvfs_access_check_create(pvfs, req, name, &access_mask, false, &sd);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ /* check that the parent isn't opened with delete on close set */
+ status = pvfs_resolve_parent(pvfs, req, name, &parent);
+ if (NT_STATUS_IS_OK(status)) {
+ DATA_BLOB locking_key;
+ status = pvfs_locking_key(parent, req, &locking_key);
+ NT_STATUS_NOT_OK_RETURN(status);
+ status = odb_get_file_infos(pvfs->odb_context, &locking_key,
+ &del_on_close, NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+ if (del_on_close) {
+ return NT_STATUS_DELETE_PENDING;
+ }
+ }
+
+ if (access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)) {
+ flags = O_RDWR;
+ } else {
+ flags = O_RDONLY;
+ }
+
+ status = ntvfs_handle_new(pvfs->ntvfs, req, &h);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ f = talloc(h, struct pvfs_file);
+ NT_STATUS_HAVE_NO_MEMORY(f);
+
+ f->handle = talloc(f, struct pvfs_file_handle);
+ NT_STATUS_HAVE_NO_MEMORY(f->handle);
+
+ attrib = io->ntcreatex.in.file_attr | FILE_ATTRIBUTE_ARCHIVE;
+ mode = pvfs_fileperms(pvfs, attrib);
+
+ /* create the file */
+ fd = pvfs_sys_open(pvfs, name->full_name, flags | O_CREAT | O_EXCL| O_NONBLOCK, mode, name->allow_override);
+ if (fd == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ pvfs_xattr_unlink_hook(pvfs, name->full_name);
+
+ /* if this was a stream create then create the stream as well */
+ if (name->stream_name) {
+ status = pvfs_stream_create(pvfs, name, fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ close(fd);
+ return status;
+ }
+ }
+
+ /* re-resolve the open fd */
+ status = pvfs_resolve_name_fd(pvfs, fd, name, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ close(fd);
+ return status;
+ }
+
+ /* support initial alloc sizes */
+ name->dos.alloc_size = io->ntcreatex.in.alloc_size;
+ name->dos.attrib = attrib;
+ status = pvfs_dosattrib_save(pvfs, name, fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+
+ status = pvfs_open_setup_eas_acl(pvfs, req, name, fd, f, io, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ if (io->generic.in.query_maximal_access) {
+ status = pvfs_access_maximal_allowed(pvfs, req, name,
+ &io->generic.out.maximal_access);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+ }
+
+ if (io->generic.in.query_on_disk_id) {
+ ZERO_ARRAY(io->generic.out.on_disk_id);
+ SBVAL(io->generic.out.on_disk_id, 0, name->st.st_ino);
+ SBVAL(io->generic.out.on_disk_id, 8, name->st.st_dev);
+ }
+
+ /* form the lock context used for byte range locking and
+ opendb locking */
+ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ status = pvfs_brl_locking_handle(f, name, h, &f->brl_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ /* grab a lock on the open file record */
+ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n",
+ name->full_name));
+ /* we were supposed to do a blocking lock, so something
+ is badly wrong! */
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto cleanup_delete;
+ }
+
+ if (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) {
+ del_on_close = true;
+ } else {
+ del_on_close = false;
+ }
+
+ if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) {
+ oplock_level = OPLOCK_NONE;
+ } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK) {
+ oplock_level = OPLOCK_BATCH;
+ } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_OPLOCK) {
+ oplock_level = OPLOCK_EXCLUSIVE;
+ }
+
+ if (req->client_caps & NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS) {
+ allow_level_II_oplock = true;
+ }
+
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, del_on_close,
+ io->generic.in.open_disposition, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ /* bad news, we must have hit a race - we don't delete the file
+ here as the most likely scenario is that someone else created
+ the file at the same time */
+ close(fd);
+ return status;
+ }
+
+ f->ntvfs = h;
+ f->pvfs = pvfs;
+ f->pending_list = NULL;
+ f->lock_count = 0;
+ f->share_access = io->generic.in.share_access;
+ f->access_mask = access_mask;
+ f->impersonation = io->generic.in.impersonation;
+ f->notify_buffer = NULL;
+ f->search = NULL;
+
+ f->handle->pvfs = pvfs;
+ f->handle->name = talloc_steal(f->handle, name);
+ f->handle->fd = fd;
+ f->handle->create_options = io->generic.in.create_options;
+ f->handle->private_flags = io->generic.in.private_flags;
+ f->handle->seek_offset = 0;
+ f->handle->position = 0;
+ f->handle->mode = 0;
+ f->handle->oplock = NULL;
+ f->handle->have_opendb_entry = true;
+ ZERO_STRUCT(f->handle->write_time);
+ f->handle->open_completed = false;
+
+ status = odb_open_file(lck, f->handle, name->full_name,
+ &f->handle->fd, name->dos.write_time,
+ allow_level_II_oplock,
+ oplock_level, &oplock_granted);
+ talloc_free(lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* bad news, we must have hit a race - we don't delete the file
+ here as the most likely scenario is that someone else created
+ the file at the same time */
+ close(fd);
+ return status;
+ }
+
+ DLIST_ADD(pvfs->files.list, f);
+
+ /* setup a destructor to avoid file descriptor leaks on
+ abnormal termination */
+ talloc_set_destructor(f, pvfs_fnum_destructor);
+ talloc_set_destructor(f->handle, pvfs_handle_destructor);
+
+ if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) {
+ oplock_granted = OPLOCK_BATCH;
+ } else if (oplock_granted != OPLOCK_NONE) {
+ status = pvfs_setup_oplock(f, oplock_granted);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ io->generic.out.oplock_level = oplock_granted;
+ io->generic.out.file.ntvfs = f->ntvfs;
+ io->generic.out.create_action = NTCREATEX_ACTION_CREATED;
+ io->generic.out.create_time = name->dos.create_time;
+ io->generic.out.access_time = name->dos.access_time;
+ io->generic.out.write_time = name->dos.write_time;
+ io->generic.out.change_time = name->dos.change_time;
+ io->generic.out.attrib = name->dos.attrib;
+ io->generic.out.alloc_size = name->dos.alloc_size;
+ io->generic.out.size = name->st.st_size;
+ io->generic.out.file_type = FILE_TYPE_DISK;
+ io->generic.out.ipc_state = 0;
+ io->generic.out.is_directory = 0;
+
+ /* success - keep the file handle */
+ status = ntvfs_handle_set_backend_data(h, pvfs->ntvfs, f);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup_delete;
+ }
+
+ f->handle->open_completed = true;
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ name->full_name);
+
+ return NT_STATUS_OK;
+
+cleanup_delete:
+ close(fd);
+ pvfs_sys_unlink(pvfs, name->full_name, name->allow_override);
+ return status;
+}
+
+/*
+ state of a pending retry
+*/
+struct pvfs_odb_retry {
+ struct ntvfs_module_context *ntvfs;
+ struct ntvfs_request *req;
+ DATA_BLOB odb_locking_key;
+ void *io;
+ void *private_data;
+ void (*callback)(struct pvfs_odb_retry *r,
+ struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ void *io,
+ void *private_data,
+ enum pvfs_wait_notice reason);
+};
+
+/* destroy a pending request */
+static int pvfs_odb_retry_destructor(struct pvfs_odb_retry *r)
+{
+ struct pvfs_state *pvfs = talloc_get_type(r->ntvfs->private_data,
+ struct pvfs_state);
+ if (r->odb_locking_key.data) {
+ struct odb_lock *lck;
+ lck = odb_lock(r->req, pvfs->odb_context, &r->odb_locking_key);
+ if (lck != NULL) {
+ odb_remove_pending(lck, r);
+ }
+ talloc_free(lck);
+ }
+ return 0;
+}
+
+static void pvfs_odb_retry_callback(void *_r, enum pvfs_wait_notice reason)
+{
+ struct pvfs_odb_retry *r = talloc_get_type(_r, struct pvfs_odb_retry);
+
+ if (reason == PVFS_WAIT_EVENT) {
+ /*
+ * The pending odb entry is already removed.
+ * We use a null locking key to indicate this
+ * to the destructor.
+ */
+ data_blob_free(&r->odb_locking_key);
+ }
+
+ r->callback(r, r->ntvfs, r->req, r->io, r->private_data, reason);
+}
+
+/*
+ setup for a retry of a request that was rejected
+ by odb_can_open()
+*/
+NTSTATUS pvfs_odb_retry_setup(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ struct odb_lock *lck,
+ struct timeval end_time,
+ void *io,
+ void *private_data,
+ void (*callback)(struct pvfs_odb_retry *r,
+ struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ void *io,
+ void *private_data,
+ enum pvfs_wait_notice reason))
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_odb_retry *r;
+ struct pvfs_wait *wait_handle;
+ NTSTATUS status;
+
+ r = talloc(req, struct pvfs_odb_retry);
+ NT_STATUS_HAVE_NO_MEMORY(r);
+
+ r->ntvfs = ntvfs;
+ r->req = req;
+ r->io = io;
+ r->private_data = private_data;
+ r->callback = callback;
+ r->odb_locking_key = odb_get_key(r, lck);
+ if (r->odb_locking_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* setup a pending lock */
+ status = odb_open_file_pending(lck, r);
+ if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_NOT_FOUND,status)) {
+ /*
+ * maybe only a unix application
+ * has the file open
+ */
+ data_blob_free(&r->odb_locking_key);
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ talloc_free(lck);
+
+ talloc_set_destructor(r, pvfs_odb_retry_destructor);
+
+ wait_handle = pvfs_wait_message(pvfs, req,
+ MSG_PVFS_RETRY_OPEN, end_time,
+ pvfs_odb_retry_callback, r);
+ if (wait_handle == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_steal(r, wait_handle);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ retry an open after a sharing violation
+*/
+static void pvfs_retry_open_sharing(struct pvfs_odb_retry *r,
+ struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ void *_io,
+ void *private_data,
+ enum pvfs_wait_notice reason)
+{
+ union smb_open *io = talloc_get_type(_io, union smb_open);
+ struct timeval *final_timeout = NULL;
+ NTSTATUS status;
+
+ if (private_data) {
+ final_timeout = talloc_get_type(private_data,
+ struct timeval);
+ }
+
+ /* w2k3 ignores SMBntcancel for outstanding open requests. It's probably
+ just a bug in their server, but we better do the same */
+ if (reason == PVFS_WAIT_CANCEL) {
+ return;
+ }
+
+ if (reason == PVFS_WAIT_TIMEOUT) {
+ if (final_timeout &&
+ !timeval_expired(final_timeout)) {
+ /*
+ * we need to retry periodictly
+ * after an EAGAIN as there's
+ * no way the kernel tell us
+ * an oplock is released.
+ */
+ goto retry;
+ }
+ /* if it timed out, then give the failure
+ immediately */
+ talloc_free(r);
+ req->async_states->status = NT_STATUS_SHARING_VIOLATION;
+ req->async_states->send_fn(req);
+ return;
+ }
+
+retry:
+ talloc_free(r);
+
+ /* try the open again, which could trigger another retry setup
+ if it wants to, so we have to unmark the async flag so we
+ will know if it does a second async reply */
+ req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC;
+
+ status = pvfs_open(ntvfs, req, io);
+ if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) {
+ /* the 2nd try also replied async, so we don't send
+ the reply yet */
+ return;
+ }
+
+ /* re-mark it async, just in case someone up the chain does
+ paranoid checking */
+ req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+
+ /* send the reply up the chain */
+ req->async_states->status = status;
+ req->async_states->send_fn(req);
+}
+
+
+/*
+ special handling for openx DENY_DOS semantics
+
+ This function attempts a reference open using an existing handle. If its allowed,
+ then it returns NT_STATUS_OK, otherwise it returns any other code and normal
+ open processing continues.
+*/
+static NTSTATUS pvfs_open_deny_dos(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_open *io,
+ struct pvfs_file *f, struct odb_lock *lck)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f2;
+ struct pvfs_filename *name;
+ NTSTATUS status;
+
+ /* search for an existing open with the right parameters. Note
+ the magic ntcreatex options flag, which is set in the
+ generic mapping code. This might look ugly, but its
+ actually pretty much now w2k does it internally as well.
+
+ If you look at the BASE-DENYDOS test you will see that a
+ DENY_DOS is a very special case, and in the right
+ circumstances you actually get the _same_ handle back
+ twice, rather than a new handle.
+ */
+ for (f2=pvfs->files.list;f2;f2=f2->next) {
+ if (f2 != f &&
+ f2->ntvfs->session_info == req->session_info &&
+ f2->ntvfs->smbpid == req->smbpid &&
+ (f2->handle->private_flags &
+ (NTCREATEX_FLAG_DENY_DOS |
+ NTCREATEX_FLAG_DENY_FCB)) &&
+ (f2->access_mask & SEC_FILE_WRITE_DATA) &&
+ strcasecmp_m(f2->handle->name->original_name,
+ io->generic.in.fname)==0) {
+ break;
+ }
+ }
+
+ if (!f2) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /* quite an insane set of semantics ... */
+ if (is_exe_filename(io->generic.in.fname) &&
+ (f2->handle->private_flags & NTCREATEX_FLAG_DENY_DOS)) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /*
+ setup a reference to the existing handle
+ */
+ talloc_free(f->handle);
+ f->handle = talloc_reference(f, f2->handle);
+
+ talloc_free(lck);
+
+ name = f->handle->name;
+
+ io->generic.out.oplock_level = OPLOCK_NONE;
+ io->generic.out.file.ntvfs = f->ntvfs;
+ io->generic.out.create_action = NTCREATEX_ACTION_EXISTED;
+ io->generic.out.create_time = name->dos.create_time;
+ io->generic.out.access_time = name->dos.access_time;
+ io->generic.out.write_time = name->dos.write_time;
+ io->generic.out.change_time = name->dos.change_time;
+ io->generic.out.attrib = name->dos.attrib;
+ io->generic.out.alloc_size = name->dos.alloc_size;
+ io->generic.out.size = name->st.st_size;
+ io->generic.out.file_type = FILE_TYPE_DISK;
+ io->generic.out.ipc_state = 0;
+ io->generic.out.is_directory = 0;
+
+ status = ntvfs_handle_set_backend_data(f->ntvfs, ntvfs, f);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ return NT_STATUS_OK;
+}
+
+
+
+/*
+ setup for a open retry after a sharing violation
+*/
+static NTSTATUS pvfs_open_setup_retry(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_open *io,
+ struct pvfs_file *f,
+ struct odb_lock *lck,
+ NTSTATUS parent_status)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ NTSTATUS status;
+ struct timeval end_time;
+ struct timeval *final_timeout = NULL;
+
+ if (io->generic.in.private_flags &
+ (NTCREATEX_FLAG_DENY_DOS | NTCREATEX_FLAG_DENY_FCB)) {
+ /* see if we can satisfy the request using the special DENY_DOS
+ code */
+ status = pvfs_open_deny_dos(ntvfs, req, io, f, lck);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* the retry should allocate a new file handle */
+ talloc_free(f);
+
+ if (NT_STATUS_EQUAL(parent_status, NT_STATUS_SHARING_VIOLATION)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ 0, pvfs->sharing_violation_delay);
+ } else if (NT_STATUS_EQUAL(parent_status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ pvfs->oplock_break_timeout, 0);
+ } else if (NT_STATUS_EQUAL(parent_status, STATUS_MORE_ENTRIES)) {
+ /*
+ * we got EAGAIN which means a unix application
+ * has an oplock or share mode
+ *
+ * we retry every 4/5 of the sharing violation delay
+ * to see if the unix application
+ * has released the oplock or share mode.
+ */
+ final_timeout = talloc(req, struct timeval);
+ NT_STATUS_HAVE_NO_MEMORY(final_timeout);
+ *final_timeout = timeval_add(&req->statistics.request_time,
+ pvfs->oplock_break_timeout,
+ 0);
+ end_time = timeval_current_ofs_usec((pvfs->sharing_violation_delay*4)/5);
+ end_time = timeval_min(final_timeout, &end_time);
+ } else {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io,
+ final_timeout, pvfs_retry_open_sharing);
+}
+
+/*
+ open a file
+*/
+NTSTATUS pvfs_open(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_open *io)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ int flags = 0;
+ struct pvfs_filename *name;
+ struct pvfs_file *f;
+ struct ntvfs_handle *h;
+ NTSTATUS status;
+ int fd, count;
+ struct odb_lock *lck;
+ uint32_t create_options;
+ uint32_t create_options_must_ignore_mask;
+ uint32_t share_access;
+ uint32_t access_mask;
+ uint32_t create_action = NTCREATEX_ACTION_EXISTED;
+ bool del_on_close;
+ bool stream_existed, stream_truncate=false;
+ uint32_t oplock_level = OPLOCK_NONE, oplock_granted;
+ bool allow_level_II_oplock = false;
+
+ /* use the generic mapping code to avoid implementing all the
+ different open calls. */
+ if (io->generic.level != RAW_OPEN_GENERIC &&
+ io->generic.level != RAW_OPEN_NTTRANS_CREATE) {
+ return ntvfs_map_open(ntvfs, req, io);
+ }
+
+ ZERO_STRUCT(io->generic.out);
+
+ create_options = io->generic.in.create_options;
+ share_access = io->generic.in.share_access;
+ access_mask = io->generic.in.access_mask;
+
+ if (share_access & ~NTCREATEX_SHARE_ACCESS_MASK) {
+ DEBUG(3,(__location__ ": Invalid share_access 0x%08x for %s\n",
+ share_access, io->ntcreatex.in.fname));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * These options are ignored,
+ * but we reuse some of them as private values for the generic mapping
+ */
+ create_options_must_ignore_mask = NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+ create_options &= ~create_options_must_ignore_mask;
+
+ if (create_options & NTCREATEX_OPTIONS_NOT_SUPPORTED_MASK) {
+ DEBUG(2,(__location__ " create_options 0x%x not supported\n",
+ create_options));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (create_options & NTCREATEX_OPTIONS_INVALID_PARAM_MASK) {
+ DEBUG(3,(__location__ ": Invalid create_options 0x%08x for %s\n",
+ create_options, io->ntcreatex.in.fname));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO: When we implement HSM, add a hook here not to pull
+ * the actual file off tape, when this option is passed from
+ * the client */
+ if (create_options & NTCREATEX_OPTIONS_NO_RECALL) {
+ /* no-op */
+ }
+
+ /* TODO: If (unlikely) Linux does a good compressed
+ * filesystem, we might need an ioctl call for this */
+ if (create_options & NTCREATEX_OPTIONS_NO_COMPRESSION) {
+ /* no-op */
+ }
+
+ if (create_options & NTCREATEX_OPTIONS_NO_INTERMEDIATE_BUFFERING) {
+ create_options |= NTCREATEX_OPTIONS_WRITE_THROUGH;
+ }
+
+ /* Open the file with sync, if they asked for it, but
+ 'strict sync = no' turns this client request into a no-op */
+ if (create_options & (NTCREATEX_OPTIONS_WRITE_THROUGH) && pvfs->flags & PVFS_FLAG_STRICT_SYNC) {
+ flags |= O_SYNC;
+ }
+
+
+ /* other create options are not allowed */
+ if ((create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE) &&
+ !(access_mask & SEC_STD_DELETE)) {
+ DEBUG(3,(__location__ ": Invalid delete_on_close option 0x%08x with access_mask 0x%08x for %s\n",
+ create_options, access_mask, io->ntcreatex.in.fname));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (access_mask & SEC_MASK_INVALID) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* what does this bit really mean?? */
+ if (req->ctx->protocol >= PROTOCOL_SMB2_02 &&
+ access_mask == SEC_STD_SYNCHRONIZE) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* cope with non-zero root_fid */
+ if (io->ntcreatex.in.root_fid.ntvfs != NULL) {
+ f = pvfs_find_fd(pvfs, req, io->ntcreatex.in.root_fid.ntvfs);
+ if (f == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ if (f->handle->fd != -1) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ io->ntcreatex.in.fname = talloc_asprintf(req, "%s\\%s",
+ f->handle->name->original_name,
+ io->ntcreatex.in.fname);
+ NT_STATUS_HAVE_NO_MEMORY(io->ntcreatex.in.fname);
+ }
+
+ if (io->ntcreatex.in.file_attr & (FILE_ATTRIBUTE_DEVICE|
+ FILE_ATTRIBUTE_VOLUME|
+ (~FILE_ATTRIBUTE_ALL_MASK))) {
+ DEBUG(3,(__location__ ": Invalid file_attr 0x%08x for %s\n",
+ io->ntcreatex.in.file_attr, io->ntcreatex.in.fname));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* we ignore some file_attr bits */
+ io->ntcreatex.in.file_attr &= ~(FILE_ATTRIBUTE_NONINDEXED |
+ FILE_ATTRIBUTE_COMPRESSED |
+ FILE_ATTRIBUTE_REPARSE_POINT |
+ FILE_ATTRIBUTE_SPARSE |
+ FILE_ATTRIBUTE_NORMAL);
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname,
+ PVFS_RESOLVE_STREAMS, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* if the client specified that it must not be a directory then
+ check that it isn't */
+ if (name->exists && (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) &&
+ (io->generic.in.create_options & NTCREATEX_OPTIONS_NON_DIRECTORY_FILE)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ /* if the client specified that it must be a directory then
+ check that it is */
+ if (name->exists && !(name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) &&
+ (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY)) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /* directory opens are handled separately */
+ if ((name->exists && (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) ||
+ (io->generic.in.create_options & NTCREATEX_OPTIONS_DIRECTORY)) {
+ return pvfs_open_directory(pvfs, req, name, io);
+ }
+
+ /* FILE_ATTRIBUTE_DIRECTORY is ignored if the above test for directory
+ open doesn't match */
+ io->generic.in.file_attr &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ switch (io->generic.in.open_disposition) {
+ case NTCREATEX_DISP_SUPERSEDE:
+ case NTCREATEX_DISP_OVERWRITE_IF:
+ if (name->stream_name == NULL) {
+ flags |= O_TRUNC;
+ } else {
+ stream_truncate = true;
+ }
+ create_action = NTCREATEX_ACTION_TRUNCATED;
+ break;
+
+ case NTCREATEX_DISP_OPEN:
+ if (!name->stream_exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ break;
+
+ case NTCREATEX_DISP_OVERWRITE:
+ if (!name->stream_exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (name->stream_name == NULL) {
+ flags |= O_TRUNC;
+ } else {
+ stream_truncate = true;
+ }
+ create_action = NTCREATEX_ACTION_TRUNCATED;
+ break;
+
+ case NTCREATEX_DISP_CREATE:
+ if (name->stream_exists) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+ break;
+
+ case NTCREATEX_DISP_OPEN_IF:
+ break;
+
+ default:
+ DEBUG(3,(__location__ ": Invalid open disposition 0x%08x for %s\n",
+ io->generic.in.open_disposition, name->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* handle creating a new file separately */
+ if (!name->exists) {
+ status = pvfs_create_file(pvfs, req, name, io);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ return status;
+ }
+
+ /* we've hit a race - the file was created during this call */
+ if (io->generic.in.open_disposition == NTCREATEX_DISP_CREATE) {
+ return status;
+ }
+
+ /* try re-resolving the name */
+ status = pvfs_resolve_name(pvfs, req, io->ntcreatex.in.fname, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* fall through to a normal open */
+ }
+
+ if ((name->dos.attrib & FILE_ATTRIBUTE_READONLY) &&
+ (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE)) {
+ return NT_STATUS_CANNOT_DELETE;
+ }
+
+ /* check the security descriptor */
+ status = pvfs_access_check(pvfs, req, name, &access_mask);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (io->generic.in.query_maximal_access) {
+ status = pvfs_access_maximal_allowed(pvfs, req, name,
+ &io->generic.out.maximal_access);
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+
+ if (io->generic.in.query_on_disk_id) {
+ ZERO_ARRAY(io->generic.out.on_disk_id);
+ SBVAL(io->generic.out.on_disk_id, 0, name->st.st_ino);
+ SBVAL(io->generic.out.on_disk_id, 8, name->st.st_dev);
+ }
+
+ status = ntvfs_handle_new(pvfs->ntvfs, req, &h);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ f = talloc(h, struct pvfs_file);
+ if (f == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ f->handle = talloc(f, struct pvfs_file_handle);
+ if (f->handle == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ f->ntvfs = h;
+ f->pvfs = pvfs;
+ f->pending_list = NULL;
+ f->lock_count = 0;
+ f->share_access = io->generic.in.share_access;
+ f->access_mask = access_mask;
+ f->impersonation = io->generic.in.impersonation;
+ f->notify_buffer = NULL;
+ f->search = NULL;
+
+ f->handle->pvfs = pvfs;
+ f->handle->fd = -1;
+ f->handle->name = talloc_steal(f->handle, name);
+ f->handle->create_options = io->generic.in.create_options;
+ f->handle->private_flags = io->generic.in.private_flags;
+ f->handle->seek_offset = 0;
+ f->handle->position = 0;
+ f->handle->mode = 0;
+ f->handle->oplock = NULL;
+ f->handle->have_opendb_entry = false;
+ ZERO_STRUCT(f->handle->write_time);
+ f->handle->open_completed = false;
+
+ /* form the lock context used for byte range locking and
+ opendb locking */
+ status = pvfs_locking_key(name, f->handle, &f->handle->odb_locking_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_brl_locking_handle(f, name, h, &f->brl_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* get a lock on this file before the actual open */
+ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("pvfs_open: failed to lock file '%s' in opendb\n",
+ name->full_name));
+ /* we were supposed to do a blocking lock, so something
+ is badly wrong! */
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ DLIST_ADD(pvfs->files.list, f);
+
+ /* setup a destructor to avoid file descriptor leaks on
+ abnormal termination */
+ talloc_set_destructor(f, pvfs_fnum_destructor);
+ talloc_set_destructor(f->handle, pvfs_handle_destructor);
+
+ /*
+ * Only SMB2 takes care of the delete_on_close,
+ * on existing files
+ */
+ if (create_options & NTCREATEX_OPTIONS_DELETE_ON_CLOSE &&
+ req->ctx->protocol >= PROTOCOL_SMB2_02) {
+ del_on_close = true;
+ } else {
+ del_on_close = false;
+ }
+
+ if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) {
+ oplock_level = OPLOCK_NONE;
+ } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_BATCH_OPLOCK) {
+ oplock_level = OPLOCK_BATCH;
+ } else if (io->ntcreatex.in.flags & NTCREATEX_FLAGS_REQUEST_OPLOCK) {
+ oplock_level = OPLOCK_EXCLUSIVE;
+ }
+
+ if (req->client_caps & NTVFS_CLIENT_CAP_LEVEL_II_OPLOCKS) {
+ allow_level_II_oplock = true;
+ }
+
+ /* see if we are allowed to open at the same time as existing opens */
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, del_on_close,
+ io->generic.in.open_disposition, false);
+
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_open_setup_retry(ntvfs, req, io, f, lck, status);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+
+ if (access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA)) {
+ flags |= O_RDWR;
+ } else {
+ flags |= O_RDONLY;
+ }
+
+ /* do the actual open */
+ fd = pvfs_sys_open(pvfs, f->handle->name->full_name, flags | O_NONBLOCK, 0, name->allow_override);
+ if (fd == -1) {
+ status = pvfs_map_errno(f->pvfs, errno);
+
+ DEBUG(0,(__location__ " mapped errno %s for %s (was %d)\n",
+ nt_errstr(status), f->handle->name->full_name, errno));
+ /*
+ * STATUS_MORE_ENTRIES is EAGAIN or EWOULDBLOCK
+ */
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_open_setup_retry(ntvfs, req, io, f, lck, status);
+ }
+
+ talloc_free(lck);
+ return status;
+ }
+
+ f->handle->fd = fd;
+
+ status = brlock_count(f->pvfs->brl_context, f->brl_handle, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+
+ if (count != 0) {
+ oplock_level = OPLOCK_NONE;
+ }
+
+ /* now really mark the file as open */
+ status = odb_open_file(lck, f->handle, name->full_name,
+ &f->handle->fd, name->dos.write_time,
+ allow_level_II_oplock,
+ oplock_level, &oplock_granted);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+
+ f->handle->have_opendb_entry = true;
+
+ if (pvfs->flags & PVFS_FLAG_FAKE_OPLOCKS) {
+ oplock_granted = OPLOCK_BATCH;
+ } else if (oplock_granted != OPLOCK_NONE) {
+ status = pvfs_setup_oplock(f, oplock_granted);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+ }
+
+ stream_existed = name->stream_exists;
+
+ /* if this was a stream create then create the stream as well */
+ if (!name->stream_exists) {
+ status = pvfs_stream_create(pvfs, f->handle->name, fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+ if (stream_truncate) {
+ status = pvfs_stream_truncate(pvfs, f->handle->name, fd, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+ }
+ }
+
+ /* re-resolve the open fd */
+ status = pvfs_resolve_name_fd(f->pvfs, fd, f->handle->name, PVFS_RESOLVE_NO_OPENDB);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+
+ if (f->handle->name->stream_id == 0 &&
+ (io->generic.in.open_disposition == NTCREATEX_DISP_OVERWRITE ||
+ io->generic.in.open_disposition == NTCREATEX_DISP_OVERWRITE_IF)) {
+ /* for overwrite we may need to replace file permissions */
+ uint32_t attrib = io->ntcreatex.in.file_attr | FILE_ATTRIBUTE_ARCHIVE;
+ mode_t mode = pvfs_fileperms(pvfs, attrib);
+ if (f->handle->name->st.st_mode != mode &&
+ f->handle->name->dos.attrib != attrib &&
+ pvfs_sys_fchmod(pvfs, fd, mode, name->allow_override) == -1) {
+ talloc_free(lck);
+ return pvfs_map_errno(pvfs, errno);
+ }
+ name->dos.alloc_size = io->ntcreatex.in.alloc_size;
+ name->dos.attrib = attrib;
+ status = pvfs_dosattrib_save(pvfs, name, fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ return status;
+ }
+ }
+
+ talloc_free(lck);
+
+ status = ntvfs_handle_set_backend_data(h, ntvfs, f);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ /* mark the open as having completed fully, so delete on close
+ can now be used */
+ f->handle->open_completed = true;
+
+ io->generic.out.oplock_level = oplock_granted;
+ io->generic.out.file.ntvfs = h;
+ io->generic.out.create_action = stream_existed?
+ create_action:NTCREATEX_ACTION_CREATED;
+
+ io->generic.out.create_time = name->dos.create_time;
+ io->generic.out.access_time = name->dos.access_time;
+ io->generic.out.write_time = name->dos.write_time;
+ io->generic.out.change_time = name->dos.change_time;
+ io->generic.out.attrib = name->dos.attrib;
+ io->generic.out.alloc_size = name->dos.alloc_size;
+ io->generic.out.size = name->st.st_size;
+ io->generic.out.file_type = FILE_TYPE_DISK;
+ io->generic.out.ipc_state = 0;
+ io->generic.out.is_directory = 0;
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ close a file
+*/
+NTSTATUS pvfs_close(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_close *io)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+
+ if (io->generic.level == RAW_CLOSE_SPLCLOSE) {
+ return NT_STATUS_DOS(ERRSRV, ERRerror);
+ }
+
+ if (io->generic.level != RAW_CLOSE_GENERIC) {
+ return ntvfs_map_close(ntvfs, req, io);
+ }
+
+ f = pvfs_find_fd(pvfs, req, io->generic.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!null_time(io->generic.in.write_time)) {
+ f->handle->write_time.update_forced = false;
+ f->handle->write_time.update_on_close = true;
+ unix_to_nt_time(&f->handle->write_time.close_time, io->generic.in.write_time);
+ }
+
+ if (io->generic.in.flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) {
+ struct pvfs_filename *name;
+ NTSTATUS status;
+ struct pvfs_file_handle *h = f->handle;
+
+ status = pvfs_resolve_name_handle(pvfs, h);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ name = h->name;
+
+ io->generic.out.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION;
+ io->generic.out.create_time = name->dos.create_time;
+ io->generic.out.access_time = name->dos.access_time;
+ io->generic.out.write_time = name->dos.write_time;
+ io->generic.out.change_time = name->dos.change_time;
+ io->generic.out.alloc_size = name->dos.alloc_size;
+ io->generic.out.size = name->st.st_size;
+ io->generic.out.file_attr = name->dos.attrib;
+ } else {
+ ZERO_STRUCT(io->generic.out);
+ }
+
+ talloc_free(f);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ logoff - close all file descriptors open by a vuid
+*/
+NTSTATUS pvfs_logoff(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f, *next;
+
+ /* If pvfs is NULL, we never logged on, and no files are open. */
+ if(pvfs == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ for (f=pvfs->files.list;f;f=next) {
+ next = f->next;
+ if (f->ntvfs->session_info == req->session_info) {
+ talloc_free(f);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ exit - close files for the current pid
+*/
+NTSTATUS pvfs_exit(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f, *next;
+
+ for (f=pvfs->files.list;f;f=next) {
+ next = f->next;
+ if (f->ntvfs->session_info == req->session_info &&
+ f->ntvfs->smbpid == req->smbpid) {
+ talloc_free(f);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ change the delete on close flag on an already open file
+*/
+NTSTATUS pvfs_set_delete_on_close(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_file *f, bool del_on_close)
+{
+ struct odb_lock *lck;
+ NTSTATUS status;
+
+ if ((f->handle->name->dos.attrib & FILE_ATTRIBUTE_READONLY) && del_on_close) {
+ return NT_STATUS_CANNOT_DELETE;
+ }
+
+ if ((f->handle->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) &&
+ !pvfs_directory_empty(pvfs, f->handle->name)) {
+ return NT_STATUS_DIRECTORY_NOT_EMPTY;
+ }
+
+ if (del_on_close) {
+ f->handle->create_options |= NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
+ } else {
+ f->handle->create_options &= ~NTCREATEX_OPTIONS_DELETE_ON_CLOSE;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, &f->handle->odb_locking_key);
+ if (lck == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = odb_set_delete_on_close(lck, del_on_close);
+
+ talloc_free(lck);
+
+ return status;
+}
+
+
+/*
+ determine if a file can be deleted, or if it is prevented by an
+ already open file
+*/
+NTSTATUS pvfs_can_delete(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ struct odb_lock **lckp)
+{
+ NTSTATUS status;
+ DATA_BLOB key;
+ struct odb_lock *lck;
+ uint32_t share_access;
+ uint32_t access_mask;
+ bool delete_on_close;
+
+ status = pvfs_locking_key(name, name, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, &key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for can_delete\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ share_access = NTCREATEX_SHARE_ACCESS_READ |
+ NTCREATEX_SHARE_ACCESS_WRITE |
+ NTCREATEX_SHARE_ACCESS_DELETE;
+ access_mask = SEC_STD_DELETE;
+ delete_on_close = true;
+
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, delete_on_close,
+ NTCREATEX_DISP_OPEN, false);
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = pvfs_access_check_simple(pvfs, req, name, access_mask);
+ }
+
+ /*
+ * if it's a sharing violation or we got no oplock
+ * only keep the lock if the caller requested access
+ * to the lock
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ if (lckp) {
+ *lckp = lck;
+ } else {
+ talloc_free(lck);
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ if (lckp) {
+ *lckp = NULL;
+ }
+ } else if (lckp) {
+ *lckp = lck;
+ }
+
+ return status;
+}
+
+/*
+ determine if a file can be renamed, or if it is prevented by an
+ already open file
+*/
+NTSTATUS pvfs_can_rename(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ struct odb_lock **lckp)
+{
+ NTSTATUS status;
+ DATA_BLOB key;
+ struct odb_lock *lck;
+ uint32_t share_access;
+ uint32_t access_mask;
+ bool delete_on_close;
+
+ status = pvfs_locking_key(name, name, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, &key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for can_stat\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ share_access = NTCREATEX_SHARE_ACCESS_READ |
+ NTCREATEX_SHARE_ACCESS_WRITE;
+ access_mask = SEC_STD_DELETE;
+ delete_on_close = false;
+
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, delete_on_close,
+ NTCREATEX_DISP_OPEN, false);
+
+ /*
+ * if it's a sharing violation or we got no oplock
+ * only keep the lock if the caller requested access
+ * to the lock
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ if (lckp) {
+ *lckp = lck;
+ } else {
+ talloc_free(lck);
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ if (lckp) {
+ *lckp = NULL;
+ }
+ } else if (lckp) {
+ *lckp = lck;
+ }
+
+ return status;
+}
+
+/*
+ determine if the file size of a file can be changed,
+ or if it is prevented by an already open file
+*/
+NTSTATUS pvfs_can_update_file_size(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ struct odb_lock **lckp)
+{
+ NTSTATUS status;
+ DATA_BLOB key;
+ struct odb_lock *lck;
+ uint32_t share_access;
+ uint32_t access_mask;
+ bool break_to_none;
+ bool delete_on_close;
+
+ status = pvfs_locking_key(name, name, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, &key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for can_stat\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ share_access = NTCREATEX_SHARE_ACCESS_READ |
+ NTCREATEX_SHARE_ACCESS_WRITE |
+ NTCREATEX_SHARE_ACCESS_DELETE;
+ /*
+ * this code previous set only SEC_FILE_WRITE_ATTRIBUTE, with
+ * a comment that this seemed to be wrong, but matched windows
+ * behaviour. It now appears that this windows behaviour is
+ * just a bug.
+ */
+ access_mask = SEC_FILE_WRITE_ATTRIBUTE | SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA;
+ delete_on_close = false;
+ break_to_none = true;
+
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, delete_on_close,
+ NTCREATEX_DISP_OPEN, break_to_none);
+
+ /*
+ * if it's a sharing violation or we got no oplock
+ * only keep the lock if the caller requested access
+ * to the lock
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ if (lckp) {
+ *lckp = lck;
+ } else {
+ talloc_free(lck);
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ if (lckp) {
+ *lckp = NULL;
+ }
+ } else if (lckp) {
+ *lckp = lck;
+ }
+
+ return status;
+}
+
+/*
+ determine if file meta data can be accessed, or if it is prevented by an
+ already open file
+*/
+NTSTATUS pvfs_can_stat(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name)
+{
+ NTSTATUS status;
+ DATA_BLOB key;
+ struct odb_lock *lck;
+ uint32_t share_access;
+ uint32_t access_mask;
+ bool delete_on_close;
+
+ status = pvfs_locking_key(name, name, &key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, &key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for can_stat\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ share_access = NTCREATEX_SHARE_ACCESS_READ |
+ NTCREATEX_SHARE_ACCESS_WRITE;
+ access_mask = SEC_FILE_READ_ATTRIBUTE;
+ delete_on_close = false;
+
+ status = odb_can_open(lck, name->stream_id,
+ share_access, access_mask, delete_on_close,
+ NTCREATEX_DISP_OPEN, false);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ }
+
+ return status;
+}
+
+
+/*
+ determine if delete on close is set on
+*/
+bool pvfs_delete_on_close_set(struct pvfs_state *pvfs, struct pvfs_file_handle *h)
+{
+ NTSTATUS status;
+ bool del_on_close;
+
+ status = odb_get_file_infos(pvfs->odb_context, &h->odb_locking_key,
+ &del_on_close, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("WARNING: unable to determine delete on close status for open file\n"));
+ return false;
+ }
+
+ return del_on_close;
+}
diff --git a/source4/ntvfs/posix/pvfs_oplock.c b/source4/ntvfs/posix/pvfs_oplock.c
new file mode 100644
index 0000000..ba5d25d
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_oplock.c
@@ -0,0 +1,307 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - oplock handling
+
+ Copyright (C) Stefan Metzmacher 2008
+
+ 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 "includes.h"
+#include "lib/messaging/messaging.h"
+#include "lib/messaging/irpc.h"
+#include "system/time.h"
+#include "vfs_posix.h"
+
+
+struct pvfs_oplock {
+ struct pvfs_file_handle *handle;
+ struct pvfs_file *file;
+ uint32_t level;
+ struct timeval break_to_level_II;
+ struct timeval break_to_none;
+ struct imessaging_context *msg_ctx;
+};
+
+static NTSTATUS pvfs_oplock_release_internal(struct pvfs_file_handle *h,
+ uint8_t oplock_break)
+{
+ struct odb_lock *olck;
+ NTSTATUS status;
+
+ if (h->fd == -1) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if (!h->have_opendb_entry) {
+ return NT_STATUS_FOOBAR;
+ }
+
+ if (!h->oplock) {
+ return NT_STATUS_FOOBAR;
+ }
+
+ olck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+ if (olck == NULL) {
+ DEBUG(0,("Unable to lock opendb for oplock update\n"));
+ return NT_STATUS_FOOBAR;
+ }
+
+ if (oplock_break == OPLOCK_BREAK_TO_NONE) {
+ h->oplock->level = OPLOCK_NONE;
+ } else if (oplock_break == OPLOCK_BREAK_TO_LEVEL_II) {
+ h->oplock->level = OPLOCK_LEVEL_II;
+ } else {
+ /* fallback to level II in case of a invalid value */
+ DEBUG(1,("unexpected oplock break level[0x%02X]\n", oplock_break));
+ h->oplock->level = OPLOCK_LEVEL_II;
+ }
+ status = odb_update_oplock(olck, h, h->oplock->level);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to update oplock level for '%s' - %s\n",
+ h->name->full_name, nt_errstr(status)));
+ talloc_free(olck);
+ return status;
+ }
+
+ talloc_free(olck);
+
+ /* after a break to none, we no longer have an oplock attached */
+ if (h->oplock->level == OPLOCK_NONE) {
+ talloc_free(h->oplock);
+ h->oplock = NULL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ receive oplock breaks and forward them to the client
+*/
+static void pvfs_oplock_break(struct pvfs_oplock *opl, uint8_t level)
+{
+ NTSTATUS status;
+ struct pvfs_file *f = opl->file;
+ struct pvfs_file_handle *h = opl->handle;
+ struct pvfs_state *pvfs = h->pvfs;
+ struct timeval cur = timeval_current();
+ struct timeval *last = NULL;
+ struct timeval end;
+
+ switch (level) {
+ case OPLOCK_BREAK_TO_LEVEL_II:
+ last = &opl->break_to_level_II;
+ break;
+ case OPLOCK_BREAK_TO_NONE:
+ last = &opl->break_to_none;
+ break;
+ }
+
+ if (!last) {
+ DEBUG(0,("%s: got unexpected level[0x%02X]\n",
+ __FUNCTION__, level));
+ return;
+ }
+
+ if (timeval_is_zero(last)) {
+ /*
+ * this is the first break we for this level
+ * remember the time
+ */
+ *last = cur;
+
+ DEBUG(5,("%s: sending oplock break level %d for '%s' %p\n",
+ __FUNCTION__, level, h->name->original_name, h));
+ status = ntvfs_send_oplock_break(pvfs->ntvfs, f->ntvfs, level);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s: sending oplock break failed: %s\n",
+ __FUNCTION__, nt_errstr(status)));
+ }
+ return;
+ }
+
+ end = timeval_add(last, pvfs->oplock_break_timeout, 0);
+
+ if (timeval_compare(&cur, &end) < 0) {
+ /*
+ * If it's not expired just ignore the break
+ * as we already sent the break request to the client
+ */
+ DEBUG(0,("%s: do not resend oplock break level %d for '%s' %p\n",
+ __FUNCTION__, level, h->name->original_name, h));
+ return;
+ }
+
+ /*
+ * If the client did not send a release within the
+ * oplock break timeout time frame we auto release
+ * the oplock
+ */
+ DEBUG(0,("%s: auto release oplock level %d for '%s' %p\n",
+ __FUNCTION__, level, h->name->original_name, h));
+ status = pvfs_oplock_release_internal(h, level);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s: failed to auto release the oplock[0x%02X]: %s\n",
+ __FUNCTION__, level, nt_errstr(status)));
+ }
+}
+
+static void pvfs_oplock_break_dispatch(struct imessaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ size_t num_fds,
+ int *fds,
+ DATA_BLOB *data)
+{
+ struct pvfs_oplock *opl = talloc_get_type(private_data,
+ struct pvfs_oplock);
+ struct opendb_oplock_break opb;
+
+ if (num_fds != 0) {
+ DBG_WARNING("Received %zu fds, ignoring message\n", num_fds);
+ return;
+ }
+
+ ZERO_STRUCT(opb);
+
+ /* we need to check that this one is for us. See
+ imessaging_send_ptr() for the other side of this.
+ */
+ if (data->length == sizeof(struct opendb_oplock_break)) {
+ struct opendb_oplock_break *p;
+ p = (struct opendb_oplock_break *)data->data;
+ opb = *p;
+ } else {
+ DEBUG(0,("%s: ignore oplock break with length[%u]\n",
+ __location__, (unsigned)data->length));
+ return;
+ }
+ if (opb.file_handle != opl->handle) {
+ return;
+ }
+
+ /*
+ * maybe we should use ntvfs_setup_async()
+ */
+ pvfs_oplock_break(opl, opb.level);
+}
+
+static int pvfs_oplock_destructor(struct pvfs_oplock *opl)
+{
+ imessaging_deregister(opl->msg_ctx, MSG_NTVFS_OPLOCK_BREAK, opl);
+ return 0;
+}
+
+NTSTATUS pvfs_setup_oplock(struct pvfs_file *f, uint32_t oplock_granted)
+{
+ NTSTATUS status;
+ struct pvfs_oplock *opl;
+ uint32_t level = OPLOCK_NONE;
+
+ f->handle->oplock = NULL;
+
+ switch (oplock_granted) {
+ case EXCLUSIVE_OPLOCK_RETURN:
+ level = OPLOCK_EXCLUSIVE;
+ break;
+ case BATCH_OPLOCK_RETURN:
+ level = OPLOCK_BATCH;
+ break;
+ case LEVEL_II_OPLOCK_RETURN:
+ level = OPLOCK_LEVEL_II;
+ break;
+ }
+
+ if (level == OPLOCK_NONE) {
+ return NT_STATUS_OK;
+ }
+
+ opl = talloc_zero(f->handle, struct pvfs_oplock);
+ NT_STATUS_HAVE_NO_MEMORY(opl);
+
+ opl->handle = f->handle;
+ opl->file = f;
+ opl->level = level;
+ opl->msg_ctx = f->pvfs->ntvfs->ctx->msg_ctx;
+
+ status = imessaging_register(opl->msg_ctx,
+ opl,
+ MSG_NTVFS_OPLOCK_BREAK,
+ pvfs_oplock_break_dispatch);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ /* destructor */
+ talloc_set_destructor(opl, pvfs_oplock_destructor);
+
+ f->handle->oplock = opl;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pvfs_oplock_release(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_lock *lck)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+ uint8_t oplock_break;
+ NTSTATUS status;
+
+ f = pvfs_find_fd(pvfs, req, lck->lockx.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ oplock_break = (lck->lockx.in.mode >> 8) & 0xFF;
+
+ status = pvfs_oplock_release_internal(f->handle, oplock_break);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s: failed to release the oplock[0x%02X]: %s\n",
+ __FUNCTION__, oplock_break, nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pvfs_break_level2_oplocks(struct pvfs_file *f)
+{
+ struct pvfs_file_handle *h = f->handle;
+ struct odb_lock *olck;
+ NTSTATUS status;
+
+ if (h->oplock && h->oplock->level != OPLOCK_LEVEL_II) {
+ return NT_STATUS_OK;
+ }
+
+ olck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+ if (olck == NULL) {
+ DEBUG(0,("Unable to lock opendb for oplock update\n"));
+ return NT_STATUS_FOOBAR;
+ }
+
+ status = odb_break_oplocks(olck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to break level2 oplocks to none for '%s' - %s\n",
+ h->name->full_name, nt_errstr(status)));
+ talloc_free(olck);
+ return status;
+ }
+
+ talloc_free(olck);
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/ntvfs/posix/pvfs_qfileinfo.c b/source4/ntvfs/posix/pvfs_qfileinfo.c
new file mode 100644
index 0000000..be601a2
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_qfileinfo.c
@@ -0,0 +1,468 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - read
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/xattr.h"
+
+
+/*
+ determine what access bits are needed for a call
+*/
+static uint32_t pvfs_fileinfo_access(union smb_fileinfo *info)
+{
+ uint32_t needed;
+
+ switch (info->generic.level) {
+ case RAW_FILEINFO_EA_LIST:
+ case RAW_FILEINFO_ALL_EAS:
+ needed = SEC_FILE_READ_EA;
+ break;
+
+ case RAW_FILEINFO_IS_NAME_VALID:
+ needed = 0;
+ break;
+
+ case RAW_FILEINFO_ACCESS_INFORMATION:
+ needed = 0;
+ break;
+
+ case RAW_FILEINFO_STREAM_INFO:
+ case RAW_FILEINFO_STREAM_INFORMATION:
+ needed = 0;
+ break;
+
+ case RAW_FILEINFO_SEC_DESC:
+ needed = 0;
+ if (info->query_secdesc.in.secinfo_flags & (SECINFO_OWNER|SECINFO_GROUP)) {
+ needed |= SEC_STD_READ_CONTROL;
+ }
+ if (info->query_secdesc.in.secinfo_flags & SECINFO_DACL) {
+ needed |= SEC_STD_READ_CONTROL;
+ }
+ if (info->query_secdesc.in.secinfo_flags & SECINFO_SACL) {
+ needed |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ break;
+
+ default:
+ needed = SEC_FILE_READ_ATTRIBUTE;
+ break;
+ }
+
+ return needed;
+}
+
+/*
+ reply to a RAW_FILEINFO_EA_LIST call
+*/
+NTSTATUS pvfs_query_ea_list(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ struct pvfs_filename *name, int fd,
+ unsigned int num_names,
+ struct ea_name *names,
+ struct smb_ea_list *eas)
+{
+ NTSTATUS status;
+ int i;
+ struct xattr_DosEAs *ealist = talloc(mem_ctx, struct xattr_DosEAs);
+
+ ZERO_STRUCTP(eas);
+ status = pvfs_doseas_load(pvfs, name, fd, ealist);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ eas->eas = talloc_array(mem_ctx, struct ea_struct, num_names);
+ if (eas->eas == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ eas->num_eas = num_names;
+ for (i=0;i<num_names;i++) {
+ int j;
+ eas->eas[i].flags = 0;
+ eas->eas[i].name.s = names[i].name.s;
+ eas->eas[i].value = data_blob(NULL, 0);
+ for (j=0;j<ealist->num_eas;j++) {
+ if (strcasecmp_m(eas->eas[i].name.s,
+ ealist->eas[j].name) == 0) {
+ if (ealist->eas[j].value.length == 0) {
+ continue;
+ }
+ eas->eas[i].value = ealist->eas[j].value;
+ break;
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ reply to a RAW_FILEINFO_ALL_EAS call
+*/
+static NTSTATUS pvfs_query_all_eas(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ struct pvfs_filename *name, int fd,
+ struct smb_ea_list *eas)
+{
+ NTSTATUS status;
+ int i;
+ struct xattr_DosEAs *ealist = talloc(mem_ctx, struct xattr_DosEAs);
+
+ ZERO_STRUCTP(eas);
+ status = pvfs_doseas_load(pvfs, name, fd, ealist);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ eas->eas = talloc_array(mem_ctx, struct ea_struct, ealist->num_eas);
+ if (eas->eas == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ eas->num_eas = 0;
+ for (i=0;i<ealist->num_eas;i++) {
+ eas->eas[eas->num_eas].flags = 0;
+ eas->eas[eas->num_eas].name.s = ealist->eas[i].name;
+ if (ealist->eas[i].value.length == 0) {
+ continue;
+ }
+ eas->eas[eas->num_eas].value = ealist->eas[i].value;
+ eas->num_eas++;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ approximately map a struct pvfs_filename to a generic fileinfo struct
+*/
+static NTSTATUS pvfs_map_fileinfo(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name, union smb_fileinfo *info,
+ int fd)
+{
+ switch (info->generic.level) {
+ case RAW_FILEINFO_GETATTR:
+ info->getattr.out.attrib = name->dos.attrib;
+ info->getattr.out.size = name->st.st_size;
+ info->getattr.out.write_time = nt_time_to_unix(name->dos.write_time);
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_GETATTRE:
+ case RAW_FILEINFO_STANDARD:
+ info->standard.out.create_time = nt_time_to_unix(name->dos.create_time);
+ info->standard.out.access_time = nt_time_to_unix(name->dos.access_time);
+ info->standard.out.write_time = nt_time_to_unix(name->dos.write_time);
+ info->standard.out.size = name->st.st_size;
+ info->standard.out.alloc_size = name->dos.alloc_size;
+ info->standard.out.attrib = name->dos.attrib;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_EA_SIZE:
+ info->ea_size.out.create_time = nt_time_to_unix(name->dos.create_time);
+ info->ea_size.out.access_time = nt_time_to_unix(name->dos.access_time);
+ info->ea_size.out.write_time = nt_time_to_unix(name->dos.write_time);
+ info->ea_size.out.size = name->st.st_size;
+ info->ea_size.out.alloc_size = name->dos.alloc_size;
+ info->ea_size.out.attrib = name->dos.attrib;
+ info->ea_size.out.ea_size = name->dos.ea_size;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_EA_LIST:
+ return pvfs_query_ea_list(pvfs, req, name, fd,
+ info->ea_list.in.num_names,
+ info->ea_list.in.ea_names,
+ &info->ea_list.out);
+
+ case RAW_FILEINFO_ALL_EAS:
+ return pvfs_query_all_eas(pvfs, req, name, fd, &info->all_eas.out);
+
+ case RAW_FILEINFO_SMB2_ALL_EAS: {
+ NTSTATUS status = pvfs_query_all_eas(pvfs, req, name, fd, &info->all_eas.out);
+ if (NT_STATUS_IS_OK(status) &&
+ info->all_eas.out.num_eas == 0) {
+ return NT_STATUS_NO_EAS_ON_FILE;
+ }
+ return status;
+ }
+
+ case RAW_FILEINFO_IS_NAME_VALID:
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_BASIC_INFO:
+ case RAW_FILEINFO_BASIC_INFORMATION:
+ info->basic_info.out.create_time = name->dos.create_time;
+ info->basic_info.out.access_time = name->dos.access_time;
+ info->basic_info.out.write_time = name->dos.write_time;
+ info->basic_info.out.change_time = name->dos.change_time;
+ info->basic_info.out.attrib = name->dos.attrib;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_STANDARD_INFO:
+ case RAW_FILEINFO_STANDARD_INFORMATION:
+ info->standard_info.out.alloc_size = name->dos.alloc_size;
+ info->standard_info.out.size = name->st.st_size;
+ info->standard_info.out.nlink = name->dos.nlink;
+ info->standard_info.out.delete_pending = 0; /* only for qfileinfo */
+ info->standard_info.out.directory =
+ (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)? 1 : 0;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_EA_INFO:
+ case RAW_FILEINFO_EA_INFORMATION:
+ info->ea_info.out.ea_size = name->dos.ea_size;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_NAME_INFO:
+ case RAW_FILEINFO_NAME_INFORMATION:
+ if (req->ctx->protocol >= PROTOCOL_SMB2_02) {
+ /* strange that SMB2 doesn't have this */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ info->name_info.out.fname.s = name->original_name;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_ALL_INFO:
+ case RAW_FILEINFO_ALL_INFORMATION:
+ info->all_info.out.create_time = name->dos.create_time;
+ info->all_info.out.access_time = name->dos.access_time;
+ info->all_info.out.write_time = name->dos.write_time;
+ info->all_info.out.change_time = name->dos.change_time;
+ info->all_info.out.attrib = name->dos.attrib;
+ info->all_info.out.alloc_size = name->dos.alloc_size;
+ info->all_info.out.size = name->st.st_size;
+ info->all_info.out.nlink = name->dos.nlink;
+ info->all_info.out.delete_pending = 0; /* only set by qfileinfo */
+ info->all_info.out.directory =
+ (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)? 1 : 0;
+ info->all_info.out.ea_size = name->dos.ea_size;
+ info->all_info.out.fname.s = name->original_name;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_ALT_NAME_INFO:
+ case RAW_FILEINFO_ALT_NAME_INFORMATION:
+ case RAW_FILEINFO_SMB2_ALT_NAME_INFORMATION:
+ info->name_info.out.fname.s = pvfs_short_name(pvfs, name, name);
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_STREAM_INFO:
+ case RAW_FILEINFO_STREAM_INFORMATION:
+ return pvfs_stream_information(pvfs, req, name, fd, &info->stream_info.out);
+
+ case RAW_FILEINFO_COMPRESSION_INFO:
+ case RAW_FILEINFO_COMPRESSION_INFORMATION:
+ info->compression_info.out.compressed_size = name->st.st_size;
+ info->compression_info.out.format = 0;
+ info->compression_info.out.unit_shift = 0;
+ info->compression_info.out.chunk_shift = 0;
+ info->compression_info.out.cluster_shift = 0;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_INTERNAL_INFORMATION:
+ info->internal_information.out.file_id = name->dos.file_id;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_ACCESS_INFORMATION:
+ info->access_information.out.access_flags = 0; /* only set by qfileinfo */
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_POSITION_INFORMATION:
+ info->position_information.out.position = 0; /* only set by qfileinfo */
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_MODE_INFORMATION:
+ info->mode_information.out.mode = 0; /* only set by qfileinfo */
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_ALIGNMENT_INFORMATION:
+ info->alignment_information.out.alignment_requirement = 0;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_NETWORK_OPEN_INFORMATION:
+ info->network_open_information.out.create_time = name->dos.create_time;
+ info->network_open_information.out.access_time = name->dos.access_time;
+ info->network_open_information.out.write_time = name->dos.write_time;
+ info->network_open_information.out.change_time = name->dos.change_time;
+ info->network_open_information.out.alloc_size = name->dos.alloc_size;
+ info->network_open_information.out.size = name->st.st_size;
+ info->network_open_information.out.attrib = name->dos.attrib;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_ATTRIBUTE_TAG_INFORMATION:
+ info->attribute_tag_information.out.attrib = name->dos.attrib;
+ info->attribute_tag_information.out.reparse_tag = 0;
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_SEC_DESC:
+ return pvfs_acl_query(pvfs, req, name, fd, info);
+
+ case RAW_FILEINFO_SMB2_ALL_INFORMATION:
+ info->all_info2.out.create_time = name->dos.create_time;
+ info->all_info2.out.access_time = name->dos.access_time;
+ info->all_info2.out.write_time = name->dos.write_time;
+ info->all_info2.out.change_time = name->dos.change_time;
+ info->all_info2.out.attrib = name->dos.attrib;
+ info->all_info2.out.unknown1 = 0;
+ info->all_info2.out.alloc_size = name->dos.alloc_size;
+ info->all_info2.out.size = name->st.st_size;
+ info->all_info2.out.nlink = name->dos.nlink;
+ info->all_info2.out.delete_pending = 0; /* only set by qfileinfo */
+ info->all_info2.out.directory =
+ (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)? 1 : 0;
+ info->all_info2.out.file_id = name->dos.file_id;
+ info->all_info2.out.ea_size = name->dos.ea_size;
+ info->all_info2.out.access_mask = 0; /* only set by qfileinfo */
+ info->all_info2.out.position = 0; /* only set by qfileinfo */
+ info->all_info2.out.mode = 0; /* only set by qfileinfo */
+ info->all_info2.out.alignment_requirement = 0;
+ /* windows wants the full path on disk for this
+ result, but I really don't want to expose that on
+ the wire, so I'll give the path with a share
+ prefix, which is a good approximation */
+ info->all_info2.out.fname.s = talloc_asprintf(req, "\\%s\\%s",
+ pvfs->share_name,
+ name->original_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->all_info2.out.fname.s);
+ return NT_STATUS_OK;
+
+ case RAW_FILEINFO_GENERIC:
+ case RAW_FILEINFO_UNIX_BASIC:
+ case RAW_FILEINFO_UNIX_INFO2:
+ case RAW_FILEINFO_UNIX_LINK:
+ return NT_STATUS_INVALID_LEVEL;
+ case RAW_FILEINFO_NORMALIZED_NAME_INFORMATION:
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
+
+/*
+ return info on a pathname
+*/
+NTSTATUS pvfs_qpathinfo(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_fileinfo *info)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_filename *name;
+ NTSTATUS status;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, info->generic.in.file.path, PVFS_RESOLVE_STREAMS, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!name->stream_exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = pvfs_can_stat(pvfs, req, name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_access_check_simple(pvfs, req, name,
+ pvfs_fileinfo_access(info));
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_map_fileinfo(pvfs, req, name, info, -1);
+
+ return status;
+}
+
+/*
+ query info on a open file
+*/
+NTSTATUS pvfs_qfileinfo(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_fileinfo *info)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+ struct pvfs_file_handle *h;
+ NTSTATUS status;
+ uint32_t access_needed;
+
+ f = pvfs_find_fd(pvfs, req, info->generic.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ h = f->handle;
+
+ access_needed = pvfs_fileinfo_access(info);
+ if ((f->access_mask & access_needed) != access_needed) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* update the file information */
+ status = pvfs_resolve_name_handle(pvfs, h);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_map_fileinfo(pvfs, req, h->name, info, h->fd);
+
+ /* a qfileinfo can fill in a bit more info than a qpathinfo -
+ now modify the levels that need to be fixed up */
+ switch (info->generic.level) {
+ case RAW_FILEINFO_STANDARD_INFO:
+ case RAW_FILEINFO_STANDARD_INFORMATION:
+ if (pvfs_delete_on_close_set(pvfs, h)) {
+ info->standard_info.out.delete_pending = 1;
+ info->standard_info.out.nlink--;
+ }
+ break;
+
+ case RAW_FILEINFO_ALL_INFO:
+ case RAW_FILEINFO_ALL_INFORMATION:
+ if (pvfs_delete_on_close_set(pvfs, h)) {
+ info->all_info.out.delete_pending = 1;
+ info->all_info.out.nlink--;
+ }
+ break;
+
+ case RAW_FILEINFO_POSITION_INFORMATION:
+ info->position_information.out.position = h->position;
+ break;
+
+ case RAW_FILEINFO_ACCESS_INFORMATION:
+ info->access_information.out.access_flags = f->access_mask;
+ break;
+
+ case RAW_FILEINFO_MODE_INFORMATION:
+ info->mode_information.out.mode = h->mode;
+ break;
+
+ case RAW_FILEINFO_SMB2_ALL_INFORMATION:
+ if (pvfs_delete_on_close_set(pvfs, h)) {
+ info->all_info2.out.delete_pending = 1;
+ info->all_info2.out.nlink--;
+ }
+ info->all_info2.out.position = h->position;
+ info->all_info2.out.access_mask = f->access_mask;
+ info->all_info2.out.mode = h->mode;
+ break;
+
+ default:
+ break;
+ }
+
+ return status;
+}
diff --git a/source4/ntvfs/posix/pvfs_read.c b/source4/ntvfs/posix/pvfs_read.c
new file mode 100644
index 0000000..09eedd5
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_read.c
@@ -0,0 +1,103 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - read
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "lib/events/events.h"
+
+/*
+ read from a file
+*/
+NTSTATUS pvfs_read(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_read *rd)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ ssize_t ret;
+ struct pvfs_file *f;
+ NTSTATUS status;
+ uint32_t maxcnt;
+ uint32_t mask;
+
+ if (rd->generic.level != RAW_READ_READX) {
+ return ntvfs_map_read(ntvfs, req, rd);
+ }
+
+ f = pvfs_find_fd(pvfs, req, rd->readx.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (f->handle->fd == -1) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ mask = SEC_FILE_READ_DATA;
+ if (rd->readx.in.read_for_execute) {
+ mask |= SEC_FILE_EXECUTE;
+ }
+ if (!(f->access_mask & mask)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ maxcnt = rd->readx.in.maxcnt;
+ if (maxcnt > 2*UINT16_MAX && req->ctx->protocol < PROTOCOL_SMB2_02) {
+ DEBUG(3,(__location__ ": Invalid SMB maxcnt 0x%x\n", maxcnt));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = pvfs_check_lock(pvfs, f, req->smbpid,
+ rd->readx.in.offset,
+ maxcnt,
+ READ_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (f->handle->name->stream_name) {
+ ret = pvfs_stream_read(pvfs, f->handle,
+ rd->readx.out.data, maxcnt, rd->readx.in.offset);
+ } else {
+ ret = pread(f->handle->fd,
+ rd->readx.out.data,
+ maxcnt,
+ rd->readx.in.offset);
+ }
+ if (ret == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ /* only SMB2 honors mincnt */
+ if (req->ctx->protocol >= PROTOCOL_SMB2_02) {
+ if (rd->readx.in.mincnt > ret ||
+ (ret == 0 && maxcnt > 0)) {
+ return NT_STATUS_END_OF_FILE;
+ }
+ }
+
+ f->handle->position = f->handle->seek_offset = rd->readx.in.offset + ret;
+
+ rd->readx.out.nread = ret;
+ rd->readx.out.remaining = 0xFFFF;
+ rd->readx.out.compaction_mode = 0;
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/ntvfs/posix/pvfs_rename.c b/source4/ntvfs/posix/pvfs_rename.c
new file mode 100644
index 0000000..4327161
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_rename.c
@@ -0,0 +1,675 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - rename
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/security.h"
+#include "param/param.h"
+
+
+/*
+ do a file rename, and send any notify triggers
+*/
+NTSTATUS pvfs_do_rename(struct pvfs_state *pvfs,
+ struct odb_lock *lck,
+ const struct pvfs_filename *name1,
+ const char *name2)
+{
+ const char *r1, *r2;
+ uint32_t mask;
+ NTSTATUS status;
+
+ if (pvfs_sys_rename(pvfs, name1->full_name, name2,
+ name1->allow_override) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ status = odb_rename(lck, name2);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ mask = FILE_NOTIFY_CHANGE_DIR_NAME;
+ } else {
+ mask = FILE_NOTIFY_CHANGE_FILE_NAME;
+ }
+ /*
+ renames to the same directory cause a OLD_NAME->NEW_NAME notify.
+ renames to a different directory are considered a remove/add
+ */
+ r1 = strrchr_m(name1->full_name, '/');
+ r2 = strrchr_m(name2, '/');
+
+ if ((r1-name1->full_name) != (r2-name2) ||
+ strncmp(name1->full_name, name2, r1-name1->full_name) != 0) {
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_REMOVED,
+ mask,
+ name1->full_name);
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_ADDED,
+ mask,
+ name2);
+ } else {
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_OLD_NAME,
+ mask,
+ name1->full_name);
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_NEW_NAME,
+ mask,
+ name2);
+ }
+
+ /* this is a strange one. w2k3 gives an additional event for CHANGE_ATTRIBUTES
+ and CHANGE_CREATION on the new file when renaming files, but not
+ directories */
+ if ((name1->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES|FILE_NOTIFY_CHANGE_CREATION,
+ name2);
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ resolve a wildcard rename pattern. This works on one component of the name
+*/
+static const char *pvfs_resolve_wildcard_component(TALLOC_CTX *mem_ctx,
+ const char *fname,
+ const char *pattern)
+{
+ const char *p1, *p2;
+ char *dest, *d;
+
+ /* the length is bounded by the length of the two strings combined */
+ dest = talloc_array(mem_ctx, char, strlen(fname) + strlen(pattern) + 1);
+ if (dest == NULL) {
+ return NULL;
+ }
+
+ p1 = fname;
+ p2 = pattern;
+ d = dest;
+
+ while (*p2) {
+ codepoint_t c1, c2;
+ size_t c_size1, c_size2;
+ c1 = next_codepoint(p1, &c_size1);
+ c2 = next_codepoint(p2, &c_size2);
+ if (c2 == '?') {
+ d += push_codepoint(d, c1);
+ } else if (c2 == '*') {
+ memcpy(d, p1, strlen(p1));
+ d += strlen(p1);
+ break;
+ } else {
+ d += push_codepoint(d, c2);
+ }
+
+ p1 += c_size1;
+ p2 += c_size2;
+ }
+
+ *d = 0;
+
+ talloc_set_name_const(dest, dest);
+
+ return dest;
+}
+
+/*
+ resolve a wildcard rename pattern.
+*/
+static const char *pvfs_resolve_wildcard(TALLOC_CTX *mem_ctx,
+ const char *fname,
+ const char *pattern)
+{
+ const char *base1, *base2;
+ const char *ext1, *ext2;
+ char *p;
+
+ /* break into base part plus extension */
+ p = strrchr_m(fname, '.');
+ if (p == NULL) {
+ ext1 = "";
+ base1 = fname;
+ } else {
+ ext1 = talloc_strdup(mem_ctx, p+1);
+ base1 = talloc_strndup(mem_ctx, fname, p-fname);
+ }
+ if (ext1 == NULL || base1 == NULL) {
+ return NULL;
+ }
+
+ p = strrchr_m(pattern, '.');
+ if (p == NULL) {
+ ext2 = "";
+ base2 = fname;
+ } else {
+ ext2 = talloc_strdup(mem_ctx, p+1);
+ base2 = talloc_strndup(mem_ctx, pattern, p-pattern);
+ }
+ if (ext2 == NULL || base2 == NULL) {
+ return NULL;
+ }
+
+ base1 = pvfs_resolve_wildcard_component(mem_ctx, base1, base2);
+ ext1 = pvfs_resolve_wildcard_component(mem_ctx, ext1, ext2);
+ if (base1 == NULL || ext1 == NULL) {
+ return NULL;
+ }
+
+ if (*ext1 == 0) {
+ return base1;
+ }
+
+ return talloc_asprintf(mem_ctx, "%s.%s", base1, ext1);
+}
+
+/*
+ retry an rename after a sharing violation
+*/
+static void pvfs_retry_rename(struct pvfs_odb_retry *r,
+ struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ void *_io,
+ void *private_data,
+ enum pvfs_wait_notice reason)
+{
+ union smb_rename *io = talloc_get_type(_io, union smb_rename);
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+
+ talloc_free(r);
+
+ switch (reason) {
+ case PVFS_WAIT_CANCEL:
+/*TODO*/
+ status = NT_STATUS_CANCELLED;
+ break;
+ case PVFS_WAIT_TIMEOUT:
+ /* if it timed out, then give the failure
+ immediately */
+/*TODO*/
+ status = NT_STATUS_SHARING_VIOLATION;
+ break;
+ case PVFS_WAIT_EVENT:
+
+ /* try the open again, which could trigger another retry setup
+ if it wants to, so we have to unmark the async flag so we
+ will know if it does a second async reply */
+ req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC;
+
+ status = pvfs_rename(ntvfs, req, io);
+ if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) {
+ /* the 2nd try also replied async, so we don't send
+ the reply yet */
+ return;
+ }
+
+ /* re-mark it async, just in case someone up the chain does
+ paranoid checking */
+ req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+ break;
+ }
+
+ /* send the reply up the chain */
+ req->async_states->status = status;
+ req->async_states->send_fn(req);
+}
+
+/*
+ setup for a rename retry after a sharing violation
+ or a non granted oplock
+*/
+static NTSTATUS pvfs_rename_setup_retry(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_rename *io,
+ struct odb_lock *lck,
+ NTSTATUS status)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct timeval end_time;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ 0, pvfs->sharing_violation_delay);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ pvfs->oplock_break_timeout, 0);
+ } else {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL,
+ pvfs_retry_rename);
+}
+
+/*
+ rename one file from a wildcard set
+*/
+static NTSTATUS pvfs_rename_one(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ const char *dir_path,
+ const char *fname1,
+ const char *fname2,
+ uint16_t attrib)
+{
+ struct pvfs_filename *name1, *name2;
+ TALLOC_CTX *mem_ctx = talloc_new(req);
+ struct odb_lock *lck = NULL;
+ NTSTATUS status;
+
+ /* resolve the wildcard pattern for this name */
+ fname2 = pvfs_resolve_wildcard(mem_ctx, fname1, fname2);
+ if (fname2 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get a pvfs_filename source object */
+ status = pvfs_resolve_partial(pvfs, mem_ctx,
+ dir_path, fname1,
+ PVFS_RESOLVE_NO_OPENDB,
+ &name1);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+
+ /* make sure its matches the given attributes */
+ status = pvfs_match_attrib(pvfs, name1, attrib, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+
+ status = pvfs_can_rename(pvfs, req, name1, &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(lck);
+ goto failed;
+ }
+
+ /* get a pvfs_filename dest object */
+ status = pvfs_resolve_partial(pvfs, mem_ctx,
+ dir_path, fname2,
+ PVFS_RESOLVE_NO_OPENDB,
+ &name2);
+ if (NT_STATUS_IS_OK(status)) {
+ status = pvfs_can_delete(pvfs, req, name2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+ fname2 = talloc_asprintf(mem_ctx, "%s/%s", dir_path, fname2);
+ if (fname2 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_do_rename(pvfs, lck, name1, fname2);
+
+failed:
+ talloc_free(mem_ctx);
+ return status;
+}
+
+
+/*
+ rename a set of files with wildcards
+*/
+static NTSTATUS pvfs_rename_wildcard(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ union smb_rename *ren,
+ struct pvfs_filename *name1,
+ struct pvfs_filename *name2)
+{
+ struct pvfs_dir *dir;
+ NTSTATUS status;
+ off_t ofs = 0;
+ const char *fname, *fname2, *dir_path;
+ uint16_t attrib = ren->rename.in.attrib;
+ int total_renamed = 0;
+
+ /* get list of matching files */
+ status = pvfs_list_start(pvfs, name1, req, &dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = NT_STATUS_NO_SUCH_FILE;
+
+ dir_path = pvfs_list_unix_path(dir);
+
+ /* only allow wildcard renames within a directory */
+ if (strncmp(dir_path, name2->full_name, strlen(dir_path)) != 0 ||
+ name2->full_name[strlen(dir_path)] != '/' ||
+ strchr(name2->full_name + strlen(dir_path) + 1, '/')) {
+ DEBUG(3,(__location__ ": Invalid rename for %s -> %s\n",
+ name1->original_name, name2->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ fname2 = talloc_strdup(name2, name2->full_name + strlen(dir_path) + 1);
+ if (fname2 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ while ((fname = pvfs_list_next(dir, &ofs))) {
+ status = pvfs_rename_one(pvfs, req,
+ dir_path,
+ fname, fname2, attrib);
+ if (NT_STATUS_IS_OK(status)) {
+ total_renamed++;
+ }
+ }
+
+ if (total_renamed == 0) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ rename a set of files - SMBmv interface
+*/
+static NTSTATUS pvfs_rename_mv(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_rename *ren)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ NTSTATUS status;
+ struct pvfs_filename *name1, *name2;
+ struct odb_lock *lck = NULL;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern1,
+ PVFS_RESOLVE_WILDCARD, &name1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_resolve_name(pvfs, req, ren->rename.in.pattern2,
+ PVFS_RESOLVE_WILDCARD, &name2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (name1->has_wildcard || name2->has_wildcard) {
+ return pvfs_rename_wildcard(pvfs, req, ren, name1, name2);
+ }
+
+ if (!name1->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (strcmp(name1->full_name, name2->full_name) == 0) {
+ return NT_STATUS_OK;
+ }
+
+ if (name2->exists) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = pvfs_match_attrib(pvfs, name1, ren->rename.in.attrib, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_can_rename(pvfs, req, name1, &lck);
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ rename a stream
+*/
+static NTSTATUS pvfs_rename_stream(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_rename *ren,
+ struct pvfs_filename *name1)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ NTSTATUS status;
+ struct odb_lock *lck = NULL;
+
+ if (name1->has_wildcard) {
+ DEBUG(3,(__location__ ": Invalid wildcard rename for %s\n",
+ name1->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (ren->ntrename.in.new_name[0] != ':') {
+ DEBUG(3,(__location__ ": Invalid rename for %s\n",
+ ren->ntrename.in.new_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!name1->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (ren->ntrename.in.flags != RENAME_FLAG_RENAME) {
+ DEBUG(3,(__location__ ": Invalid rename flags 0x%x for %s\n",
+ ren->ntrename.in.flags, ren->ntrename.in.new_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = pvfs_can_rename(pvfs, req, name1, &lck);
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_access_check_simple(pvfs, req, name1, SEC_FILE_WRITE_ATTRIBUTE);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = pvfs_stream_rename(pvfs, name1, -1,
+ ren->ntrename.in.new_name+1,
+ true);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ rename a set of files - ntrename interface
+*/
+static NTSTATUS pvfs_rename_nt(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_rename *ren)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ NTSTATUS status;
+ struct pvfs_filename *name1, *name2;
+ struct odb_lock *lck = NULL;
+
+ switch (ren->ntrename.in.flags) {
+ case RENAME_FLAG_RENAME:
+ case RENAME_FLAG_HARD_LINK:
+ case RENAME_FLAG_COPY:
+ case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
+ break;
+ default:
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.old_name,
+ PVFS_RESOLVE_WILDCARD | PVFS_RESOLVE_STREAMS, &name1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (name1->stream_name) {
+ /* stream renames need to be handled separately */
+ return pvfs_rename_stream(ntvfs, req, ren, name1);
+ }
+
+ status = pvfs_resolve_name(pvfs, req, ren->ntrename.in.new_name,
+ PVFS_RESOLVE_WILDCARD, &name2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (name1->has_wildcard || name2->has_wildcard) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+
+ if (!name1->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (strcmp(name1->full_name, name2->full_name) == 0) {
+ return NT_STATUS_OK;
+ }
+
+ if (name2->exists) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = pvfs_match_attrib(pvfs, name1, ren->ntrename.in.attrib, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_can_rename(pvfs, req, name1, &lck);
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_rename_setup_retry(pvfs->ntvfs, req, ren, lck, status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ switch (ren->ntrename.in.flags) {
+ case RENAME_FLAG_RENAME:
+ status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+ NT_STATUS_NOT_OK_RETURN(status);
+ status = pvfs_do_rename(pvfs, lck, name1, name2->full_name);
+ NT_STATUS_NOT_OK_RETURN(status);
+ break;
+
+ case RENAME_FLAG_HARD_LINK:
+ status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+ NT_STATUS_NOT_OK_RETURN(status);
+ if (link(name1->full_name, name2->full_name) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ break;
+
+ case RENAME_FLAG_COPY:
+ status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+ NT_STATUS_NOT_OK_RETURN(status);
+ return pvfs_copy_file(pvfs, name1, name2, name1->allow_override && name2->allow_override);
+
+ case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
+ DEBUG(3,(__location__ ": Invalid rename cluster for %s\n",
+ name1->original_name));
+ return NT_STATUS_INVALID_PARAMETER;
+
+ default:
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+
+ return NT_STATUS_OK;
+}
+
+/*
+ rename a set of files - ntrename interface
+*/
+NTSTATUS pvfs_rename(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_rename *ren)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+
+ switch (ren->generic.level) {
+ case RAW_RENAME_RENAME:
+ return pvfs_rename_mv(ntvfs, req, ren);
+
+ case RAW_RENAME_NTRENAME:
+ return pvfs_rename_nt(ntvfs, req, ren);
+
+ case RAW_RENAME_NTTRANS:
+ f = pvfs_find_fd(pvfs, req, ren->nttrans.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* wk23 ignores the request */
+ return NT_STATUS_OK;
+
+ default:
+ break;
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
+
diff --git a/source4/ntvfs/posix/pvfs_resolve.c b/source4/ntvfs/posix/pvfs_resolve.c
new file mode 100644
index 0000000..fbd8c7d
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_resolve.c
@@ -0,0 +1,826 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - filename resolution
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+/*
+ this is the core code for converting a filename from the format as
+ given by a client to a posix filename, including any case-matching
+ required, and checks for legal characters
+*/
+
+
+#include "includes.h"
+#include "vfs_posix.h"
+#include "system/dir.h"
+#include "param/param.h"
+
+/**
+ compare two filename components. This is where the name mangling hook will go
+*/
+static int component_compare(struct pvfs_state *pvfs, const char *comp, const char *name)
+{
+ int ret;
+
+ ret = strcasecmp_m(comp, name);
+
+ if (ret != 0) {
+ char *shortname = pvfs_short_name_component(pvfs, name);
+ if (shortname) {
+ ret = strcasecmp_m(comp, shortname);
+ talloc_free(shortname);
+ }
+ }
+
+ return ret;
+}
+
+/*
+ search for a filename in a case insensitive fashion
+
+ TODO: add a cache for previously resolved case-insensitive names
+ TODO: add mangled name support
+*/
+static NTSTATUS pvfs_case_search(struct pvfs_state *pvfs,
+ struct pvfs_filename *name,
+ unsigned int flags)
+{
+ /* break into a series of components */
+ size_t num_components;
+ char **components;
+ char *p, *partial_name;
+ size_t i;
+
+ /* break up the full name into pathname components */
+ num_components=2;
+ p = name->full_name + strlen(pvfs->base_directory) + 1;
+
+ for (;*p;p++) {
+ if (*p == '/') {
+ num_components++;
+ }
+ }
+
+ components = talloc_array(name, char *, num_components);
+ p = name->full_name + strlen(pvfs->base_directory);
+ *p++ = 0;
+
+ components[0] = name->full_name;
+
+ for (i=1;i<num_components;i++) {
+ components[i] = p;
+ p = strchr(p, '/');
+ if (p) *p++ = 0;
+ if (pvfs_is_reserved_name(pvfs, components[i])) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ partial_name = talloc_strdup(name, components[0]);
+ if (!partial_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* for each component, check if it exists as-is, and if not then
+ do a directory scan */
+ for (i=1;i<num_components;i++) {
+ char *test_name;
+ DIR *dir;
+ struct dirent *de;
+ char *long_component;
+
+ /* possibly remap from the short name cache */
+ long_component = pvfs_mangled_lookup(pvfs, name, components[i]);
+ if (long_component) {
+ components[i] = long_component;
+ }
+
+ test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
+ if (!test_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* check if this component exists as-is */
+ if (stat(test_name, &name->st) == 0) {
+ if (i<num_components-1 && !S_ISDIR(name->st.st_mode)) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ talloc_free(partial_name);
+ partial_name = test_name;
+ if (i == num_components - 1) {
+ name->exists = true;
+ }
+ continue;
+ }
+
+ /* the filesystem might be case insensitive, in which
+ case a search is pointless unless the name is
+ mangled */
+ if ((pvfs->flags & PVFS_FLAG_CI_FILESYSTEM) &&
+ !pvfs_is_mangled_component(pvfs, components[i])) {
+ if (i < num_components-1) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ partial_name = test_name;
+ continue;
+ }
+
+ dir = opendir(partial_name);
+ if (!dir) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ while ((de = readdir(dir))) {
+ if (component_compare(pvfs, components[i], de->d_name) == 0) {
+ break;
+ }
+ }
+
+ if (!de) {
+ if (i < num_components-1) {
+ closedir(dir);
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ } else {
+ components[i] = talloc_strdup(name, de->d_name);
+ }
+ test_name = talloc_asprintf(name, "%s/%s", partial_name, components[i]);
+ talloc_free(partial_name);
+ partial_name = test_name;
+
+ closedir(dir);
+ }
+
+ if (!name->exists) {
+ if (stat(partial_name, &name->st) == 0) {
+ name->exists = true;
+ }
+ }
+
+ talloc_free(name->full_name);
+ name->full_name = partial_name;
+
+ if (name->exists) {
+ return pvfs_fill_dos_info(pvfs, name, flags, -1);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ parse a alternate data stream name
+*/
+static NTSTATUS parse_stream_name(struct pvfs_filename *name,
+ const char *s)
+{
+ char *p, *stream_name;
+ if (s[1] == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ name->stream_name = stream_name = talloc_strdup(name, s+1);
+ if (name->stream_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ p = stream_name;
+
+ while (*p) {
+ size_t c_size;
+ codepoint_t c = next_codepoint(p, &c_size);
+
+ switch (c) {
+ case '/':
+ case '\\':
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ case ':':
+ *p= 0;
+ p++;
+ if (*p == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (strcasecmp_m(p, "$DATA") != 0) {
+ if (strchr_m(p, ':')) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ c_size = 0;
+ p--;
+ break;
+ }
+
+ p += c_size;
+ }
+
+ if (strcmp(name->stream_name, "") == 0) {
+ /*
+ * we don't set stream_name to NULL, here
+ * as this would be wrong for directories
+ *
+ * pvfs_fill_dos_info() will set it to NULL
+ * if it's not a directory.
+ */
+ name->stream_id = 0;
+ } else {
+ name->stream_id = pvfs_name_hash(name->stream_name,
+ strlen(name->stream_name));
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ convert a CIFS pathname to a unix pathname. Note that this does NOT
+ take into account case insensitivity, and in fact does not access
+ the filesystem at all. It is merely a reformatting and charset
+ checking routine.
+
+ errors are returned if the filename is illegal given the flags
+*/
+static NTSTATUS pvfs_unix_path(struct pvfs_state *pvfs, const char *cifs_name,
+ unsigned int flags, struct pvfs_filename *name)
+{
+ char *ret, *p, *p_start;
+ NTSTATUS status;
+
+ name->original_name = talloc_strdup(name, cifs_name);
+
+ /* remove any :$DATA */
+ p = strrchr(name->original_name, ':');
+ if (p && strcasecmp_m(p, ":$DATA") == 0) {
+ if (p > name->original_name && p[-1] == ':') {
+ p--;
+ }
+ *p = 0;
+ }
+
+ name->stream_name = NULL;
+ name->stream_id = 0;
+ name->has_wildcard = false;
+
+ while (*cifs_name == '\\') {
+ cifs_name++;
+ }
+
+ if (*cifs_name == 0) {
+ name->full_name = talloc_asprintf(name, "%s/.", pvfs->base_directory);
+ if (name->full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
+ ret = talloc_asprintf(name, "%s/%s", pvfs->base_directory, cifs_name);
+ if (ret == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ p = ret + strlen(pvfs->base_directory) + 1;
+
+ /* now do an in-place conversion of '\' to '/', checking
+ for legal characters */
+ p_start = p;
+
+ while (*p) {
+ size_t c_size;
+ codepoint_t c = next_codepoint(p, &c_size);
+
+ if (c <= 0x1F) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ switch (c) {
+ case '\\':
+ if (name->has_wildcard) {
+ /* wildcards are only allowed in the last part
+ of a name */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (p > p_start && (p[1] == '\\' || p[1] == '\0')) {
+ /* see if it is definitely a "\\" or
+ * a trailing "\". If it is then fail here,
+ * and let the next layer up try again after
+ * pvfs_reduce_name() if it wants to. This is
+ * much more efficient on average than always
+ * scanning for these separately
+ */
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ } else {
+ *p = '/';
+ }
+ break;
+ case ':':
+ if (!(flags & PVFS_RESOLVE_STREAMS)) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (name->has_wildcard) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ status = parse_stream_name(name, p);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *p-- = 0;
+ break;
+ case '*':
+ case '>':
+ case '<':
+ case '?':
+ case '"':
+ if (!(flags & PVFS_RESOLVE_WILDCARD)) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ name->has_wildcard = true;
+ break;
+ case '/':
+ case '|':
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ case '.':
+ /* see if it is definitely a .. or
+ . component. If it is then fail here, and
+ let the next layer up try again after
+ pvfs_reduce_name() if it wants to. This is
+ much more efficient on average than always
+ scanning for these separately */
+ if (p[1] == '.' &&
+ (p[2] == 0 || p[2] == '\\') &&
+ (p == p_start || p[-1] == '/')) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+ if ((p[1] == 0 || p[1] == '\\') &&
+ (p == p_start || p[-1] == '/')) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+ break;
+ }
+
+ p += c_size;
+ }
+
+ name->full_name = ret;
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ reduce a name that contains .. components or repeated \ separators
+ return NULL if it can't be reduced
+*/
+static NTSTATUS pvfs_reduce_name(TALLOC_CTX *mem_ctx,
+ const char **fname, unsigned int flags)
+{
+ codepoint_t c;
+ size_t c_size, len;
+ size_t i, num_components, err_count;
+ char **components;
+ char *p, *s, *ret;
+
+ s = talloc_strdup(mem_ctx, *fname);
+ if (s == NULL) return NT_STATUS_NO_MEMORY;
+
+ for (num_components=1, p=s; *p; p += c_size) {
+ c = next_codepoint(p, &c_size);
+ if (c == '\\') num_components++;
+ }
+
+ components = talloc_array(s, char *, num_components+1);
+ if (components == NULL) {
+ talloc_free(s);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ components[0] = s;
+ for (i=0, p=s; *p; p += c_size) {
+ c = next_codepoint(p, &c_size);
+ if (c == '\\') {
+ *p = 0;
+ components[++i] = p+1;
+ }
+ }
+ components[i+1] = NULL;
+
+ /*
+ rather bizarre!
+
+ '.' components are not allowed, but the rules for what error
+ code to give don't seem to make sense. This is a close
+ approximation.
+ */
+ for (err_count=i=0;components[i];i++) {
+ if (strcmp(components[i], "") == 0) {
+ continue;
+ }
+ if (ISDOT(components[i]) || err_count) {
+ err_count++;
+ }
+ }
+ if (err_count > 0) {
+ if (flags & PVFS_RESOLVE_WILDCARD) err_count--;
+
+ if (err_count==1) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+
+ /* remove any null components */
+ for (i=0;components[i];i++) {
+ if (strcmp(components[i], "") == 0) {
+ memmove(&components[i], &components[i+1],
+ sizeof(char *)*(num_components-i));
+ i--;
+ continue;
+ }
+ if (ISDOTDOT(components[i])) {
+ if (i < 1) return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ memmove(&components[i-1], &components[i+1],
+ sizeof(char *)*(num_components-i));
+ i -= 2;
+ continue;
+ }
+ }
+
+ if (components[0] == NULL) {
+ talloc_free(s);
+ *fname = talloc_strdup(mem_ctx, "\\");
+ return NT_STATUS_OK;
+ }
+
+ for (len=i=0;components[i];i++) {
+ len += strlen(components[i]) + 1;
+ }
+
+ /* rebuild the name */
+ ret = talloc_array(mem_ctx, char, len+1);
+ if (ret == NULL) {
+ talloc_free(s);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (len=0,i=0;components[i];i++) {
+ size_t len1 = strlen(components[i]);
+ ret[len] = '\\';
+ memcpy(ret+len+1, components[i], len1);
+ len += len1 + 1;
+ }
+ ret[len] = 0;
+
+ talloc_set_name_const(ret, ret);
+
+ talloc_free(s);
+
+ *fname = ret;
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ resolve a name from relative client format to a struct pvfs_filename
+ the memory for the filename is made as a talloc child of 'name'
+
+ flags include:
+ PVFS_RESOLVE_NO_WILDCARD = wildcards are considered illegal characters
+ PVFS_RESOLVE_STREAMS = stream names are allowed
+
+ TODO: ../ collapsing, and outside share checking
+*/
+NTSTATUS pvfs_resolve_name(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ const char *cifs_name,
+ unsigned int flags, struct pvfs_filename **name)
+{
+ NTSTATUS status;
+
+ *name = talloc(req, struct pvfs_filename);
+ if (*name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*name)->exists = false;
+ (*name)->stream_exists = false;
+ (*name)->allow_override = false;
+
+ if (!(pvfs->fs_attribs & FS_ATTR_NAMED_STREAMS)) {
+ flags &= ~PVFS_RESOLVE_STREAMS;
+ }
+
+ /* SMB2 doesn't allow a leading slash */
+ if (req->ctx->protocol >= PROTOCOL_SMB2_02 &&
+ *cifs_name == '\\') {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* do the basic conversion to a unix formatted path,
+ also checking for allowable characters */
+ status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
+ /* it might contain .. components which need to be reduced */
+ status = pvfs_reduce_name(*name, &cifs_name, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = pvfs_unix_path(pvfs, cifs_name, flags, *name);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* if it has a wildcard then no point doing a stat() of the
+ full name. Instead We need check if the directory exists
+ */
+ if ((*name)->has_wildcard) {
+ const char *p;
+ char *dir_name, *saved_name;
+ p = strrchr((*name)->full_name, '/');
+ if (p == NULL) {
+ /* root directory wildcard is OK */
+ return NT_STATUS_OK;
+ }
+ dir_name = talloc_strndup(*name, (*name)->full_name, (p-(*name)->full_name));
+ if (stat(dir_name, &(*name)->st) == 0) {
+ talloc_free(dir_name);
+ return NT_STATUS_OK;
+ }
+ /* we need to search for a matching name */
+ saved_name = (*name)->full_name;
+ (*name)->full_name = dir_name;
+ status = pvfs_case_search(pvfs, *name, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* the directory doesn't exist */
+ (*name)->full_name = saved_name;
+ return status;
+ }
+ /* it does exist, but might need a case change */
+ if (dir_name != (*name)->full_name) {
+ (*name)->full_name = talloc_asprintf(*name, "%s%s",
+ (*name)->full_name, p);
+ NT_STATUS_HAVE_NO_MEMORY((*name)->full_name);
+ } else {
+ (*name)->full_name = saved_name;
+ talloc_free(dir_name);
+ }
+ return NT_STATUS_OK;
+ }
+
+ /* if we can stat() the full name now then we are done */
+ if (stat((*name)->full_name, &(*name)->st) == 0) {
+ (*name)->exists = true;
+ return pvfs_fill_dos_info(pvfs, *name, flags, -1);
+ }
+
+ /* search for a matching filename */
+ status = pvfs_case_search(pvfs, *name, flags);
+
+ return status;
+}
+
+
+/*
+ do a partial resolve, returning a pvfs_filename structure given a
+ base path and a relative component. It is an error if the file does
+ not exist. No case-insensitive matching is done.
+
+ this is used in places like directory searching where we need a pvfs_filename
+ to pass to a function, but already know the unix base directory and component
+*/
+NTSTATUS pvfs_resolve_partial(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ const char *unix_dir, const char *fname,
+ unsigned int flags, struct pvfs_filename **name)
+{
+ NTSTATUS status;
+
+ *name = talloc(mem_ctx, struct pvfs_filename);
+ if (*name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*name)->full_name = talloc_asprintf(*name, "%s/%s", unix_dir, fname);
+ if ((*name)->full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (stat((*name)->full_name, &(*name)->st) == -1) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ (*name)->exists = true;
+ (*name)->stream_exists = true;
+ (*name)->has_wildcard = false;
+ (*name)->original_name = talloc_strdup(*name, fname);
+ (*name)->stream_name = NULL;
+ (*name)->stream_id = 0;
+ (*name)->allow_override = false;
+
+ status = pvfs_fill_dos_info(pvfs, *name, flags, -1);
+
+ return status;
+}
+
+
+/*
+ fill in the pvfs_filename info for an open file, given the current
+ info for a (possibly) non-open file. This is used by places that need
+ to update the pvfs_filename stat information, and by pvfs_open()
+*/
+NTSTATUS pvfs_resolve_name_fd(struct pvfs_state *pvfs, int fd,
+ struct pvfs_filename *name, unsigned int flags)
+{
+ dev_t device = (dev_t)0;
+ ino_t inode = 0;
+
+ if (name->exists) {
+ device = name->st.st_dev;
+ inode = name->st.st_ino;
+ }
+
+ if (fd == -1) {
+ if (stat(name->full_name, &name->st) == -1) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ } else {
+ if (fstat(fd, &name->st) == -1) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ }
+
+ if (name->exists &&
+ (device != name->st.st_dev || inode != name->st.st_ino)) {
+ /* the file we are looking at has changed! this could
+ be someone trying to exploit a race
+ condition. Certainly we don't want to continue
+ operating on this file */
+ DEBUG(0,("pvfs: WARNING: file '%s' changed during resolve - failing\n",
+ name->full_name));
+ return NT_STATUS_UNEXPECTED_IO_ERROR;
+ }
+
+ name->exists = true;
+
+ return pvfs_fill_dos_info(pvfs, name, flags, fd);
+}
+
+/*
+ fill in the pvfs_filename info for an open file, given the current
+ info for a (possibly) non-open file. This is used by places that need
+ to update the pvfs_filename stat information, and the path
+ after a possible rename on a different handle.
+*/
+NTSTATUS pvfs_resolve_name_handle(struct pvfs_state *pvfs,
+ struct pvfs_file_handle *h)
+{
+ NTSTATUS status;
+
+ if (h->have_opendb_entry) {
+ struct odb_lock *lck;
+ const char *name = NULL;
+
+ lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("%s: failed to lock file '%s' in opendb\n",
+ __FUNCTION__, h->name->full_name));
+ /* we were supposed to do a blocking lock, so something
+ is badly wrong! */
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = odb_get_path(lck, &name);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * This relies on the fact that
+ * renames of open files are only
+ * allowed by setpathinfo() and setfileinfo()
+ * and there're only renames within the same
+ * directory supported
+ */
+ if (strcmp(h->name->full_name, name) != 0) {
+ const char *orig_dir;
+ const char *new_file;
+ char *new_orig;
+ char *delim;
+ char *full_name = discard_const_p(char, name);
+
+ delim = strrchr(name, '/');
+ if (!delim) {
+ talloc_free(lck);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ new_file = delim + 1;
+ delim = strrchr(h->name->original_name, '\\');
+ if (delim) {
+ delim[0] = '\0';
+ orig_dir = h->name->original_name;
+ new_orig = talloc_asprintf(h->name, "%s\\%s",
+ orig_dir, new_file);
+ if (!new_orig) {
+ talloc_free(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ new_orig = talloc_strdup(h->name, new_file);
+ if (!new_orig) {
+ talloc_free(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ talloc_free(h->name->original_name);
+ talloc_free(h->name->full_name);
+ h->name->full_name = talloc_steal(h->name, full_name);
+ h->name->original_name = new_orig;
+ }
+ }
+
+ talloc_free(lck);
+ }
+
+ /*
+ * TODO: pass PVFS_RESOLVE_NO_OPENDB and get
+ * the write time from odb_lock() above.
+ */
+ status = pvfs_resolve_name_fd(pvfs, h->fd, h->name, 0);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (!null_nttime(h->write_time.close_time)) {
+ h->name->dos.write_time = h->write_time.close_time;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ resolve the parent of a given name
+*/
+NTSTATUS pvfs_resolve_parent(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ const struct pvfs_filename *child,
+ struct pvfs_filename **name)
+{
+ NTSTATUS status;
+ char *p;
+
+ *name = talloc(mem_ctx, struct pvfs_filename);
+ if (*name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*name)->full_name = talloc_strdup(*name, child->full_name);
+ if ((*name)->full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ p = strrchr_m((*name)->full_name, '/');
+ if (p == NULL) {
+ return NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ }
+
+ /* this handles the root directory */
+ if (p == (*name)->full_name) {
+ p[1] = 0;
+ } else {
+ p[0] = 0;
+ }
+
+ if (stat((*name)->full_name, &(*name)->st) == -1) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ (*name)->exists = true;
+ (*name)->stream_exists = true;
+ (*name)->has_wildcard = false;
+ /* we can't get the correct 'original_name', but for the purposes
+ of this call this is close enough */
+ (*name)->original_name = talloc_strdup(*name, child->original_name);
+ if ((*name)->original_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ (*name)->stream_name = NULL;
+ (*name)->stream_id = 0;
+ (*name)->allow_override = false;
+
+ status = pvfs_fill_dos_info(pvfs, *name, PVFS_RESOLVE_NO_OPENDB, -1);
+
+ return status;
+}
diff --git a/source4/ntvfs/posix/pvfs_search.c b/source4/ntvfs/posix/pvfs_search.c
new file mode 100644
index 0000000..f352a6a
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_search.c
@@ -0,0 +1,865 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - directory search functions
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "system/time.h"
+#include "librpc/gen_ndr/security.h"
+#include "samba/service_stream.h"
+#include "lib/events/events.h"
+#include "../lib/util/dlinklist.h"
+#include "lib/util/idtree.h"
+
+/* place a reasonable limit on old-style searches as clients tend to
+ not send search close requests */
+#define MAX_OLD_SEARCHES 2000
+#define MAX_SEARCH_HANDLES (UINT16_MAX - 1)
+#define INVALID_SEARCH_HANDLE UINT16_MAX
+
+/*
+ destroy an open search
+*/
+static int pvfs_search_destructor(struct pvfs_search_state *search)
+{
+ DLIST_REMOVE(search->pvfs->search.list, search);
+ idr_remove(search->pvfs->search.idtree, search->handle);
+ return 0;
+}
+
+/*
+ called when a search timer goes off
+*/
+static void pvfs_search_timer(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval t, void *ptr)
+{
+ struct pvfs_search_state *search = talloc_get_type(ptr, struct pvfs_search_state);
+ talloc_free(search);
+}
+
+/*
+ setup a timer to destroy a open search after a inactivity period
+*/
+static void pvfs_search_setup_timer(struct pvfs_search_state *search)
+{
+ struct tevent_context *ev = search->pvfs->ntvfs->ctx->event_ctx;
+ if (search->handle == INVALID_SEARCH_HANDLE) return;
+ talloc_free(search->te);
+ search->te = tevent_add_timer(ev, search,
+ timeval_current_ofs(search->pvfs->search.inactivity_time, 0),
+ pvfs_search_timer, search);
+}
+
+/*
+ fill in a single search result for a given info level
+*/
+static NTSTATUS fill_search_info(struct pvfs_state *pvfs,
+ enum smb_search_data_level level,
+ const char *unix_path,
+ const char *fname,
+ struct pvfs_search_state *search,
+ off_t dir_offset,
+ union smb_search_data *file)
+{
+ struct pvfs_filename *name;
+ NTSTATUS status;
+ const char *shortname;
+ uint32_t dir_index = (uint32_t)dir_offset; /* truncated - see the code
+ in pvfs_list_seek_ofs() for
+ how we cope with this */
+
+ status = pvfs_resolve_partial(pvfs, file, unix_path, fname, 0, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_match_attrib(pvfs, name, search->search_attrib, search->must_attrib);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ switch (level) {
+ case RAW_SEARCH_DATA_SEARCH:
+ shortname = pvfs_short_name(pvfs, name, name);
+ file->search.attrib = name->dos.attrib;
+ file->search.write_time = nt_time_to_unix(name->dos.write_time);
+ file->search.size = name->st.st_size;
+ file->search.name = shortname;
+ file->search.id.reserved = search->handle >> 8;
+ memset(file->search.id.name, ' ', sizeof(file->search.id.name));
+ memcpy(file->search.id.name, shortname,
+ MIN(strlen(shortname)+1, sizeof(file->search.id.name)));
+ file->search.id.handle = search->handle & 0xFF;
+ file->search.id.server_cookie = dir_index;
+ file->search.id.client_cookie = 0;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_STANDARD:
+ file->standard.resume_key = dir_index;
+ file->standard.create_time = nt_time_to_unix(name->dos.create_time);
+ file->standard.access_time = nt_time_to_unix(name->dos.access_time);
+ file->standard.write_time = nt_time_to_unix(name->dos.write_time);
+ file->standard.size = name->st.st_size;
+ file->standard.alloc_size = name->dos.alloc_size;
+ file->standard.attrib = name->dos.attrib;
+ file->standard.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_EA_SIZE:
+ file->ea_size.resume_key = dir_index;
+ file->ea_size.create_time = nt_time_to_unix(name->dos.create_time);
+ file->ea_size.access_time = nt_time_to_unix(name->dos.access_time);
+ file->ea_size.write_time = nt_time_to_unix(name->dos.write_time);
+ file->ea_size.size = name->st.st_size;
+ file->ea_size.alloc_size = name->dos.alloc_size;
+ file->ea_size.attrib = name->dos.attrib;
+ file->ea_size.ea_size = name->dos.ea_size;
+ file->ea_size.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_EA_LIST:
+ file->ea_list.resume_key = dir_index;
+ file->ea_list.create_time = nt_time_to_unix(name->dos.create_time);
+ file->ea_list.access_time = nt_time_to_unix(name->dos.access_time);
+ file->ea_list.write_time = nt_time_to_unix(name->dos.write_time);
+ file->ea_list.size = name->st.st_size;
+ file->ea_list.alloc_size = name->dos.alloc_size;
+ file->ea_list.attrib = name->dos.attrib;
+ file->ea_list.name.s = fname;
+ return pvfs_query_ea_list(pvfs, file, name, -1,
+ search->num_ea_names,
+ search->ea_names,
+ &file->ea_list.eas);
+
+ case RAW_SEARCH_DATA_DIRECTORY_INFO:
+ file->directory_info.file_index = dir_index;
+ file->directory_info.create_time = name->dos.create_time;
+ file->directory_info.access_time = name->dos.access_time;
+ file->directory_info.write_time = name->dos.write_time;
+ file->directory_info.change_time = name->dos.change_time;
+ file->directory_info.size = name->st.st_size;
+ file->directory_info.alloc_size = name->dos.alloc_size;
+ file->directory_info.attrib = name->dos.attrib;
+ file->directory_info.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_FULL_DIRECTORY_INFO:
+ file->full_directory_info.file_index = dir_index;
+ file->full_directory_info.create_time = name->dos.create_time;
+ file->full_directory_info.access_time = name->dos.access_time;
+ file->full_directory_info.write_time = name->dos.write_time;
+ file->full_directory_info.change_time = name->dos.change_time;
+ file->full_directory_info.size = name->st.st_size;
+ file->full_directory_info.alloc_size = name->dos.alloc_size;
+ file->full_directory_info.attrib = name->dos.attrib;
+ file->full_directory_info.ea_size = name->dos.ea_size;
+ file->full_directory_info.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_NAME_INFO:
+ file->name_info.file_index = dir_index;
+ file->name_info.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_BOTH_DIRECTORY_INFO:
+ file->both_directory_info.file_index = dir_index;
+ file->both_directory_info.create_time = name->dos.create_time;
+ file->both_directory_info.access_time = name->dos.access_time;
+ file->both_directory_info.write_time = name->dos.write_time;
+ file->both_directory_info.change_time = name->dos.change_time;
+ file->both_directory_info.size = name->st.st_size;
+ file->both_directory_info.alloc_size = name->dos.alloc_size;
+ file->both_directory_info.attrib = name->dos.attrib;
+ file->both_directory_info.ea_size = name->dos.ea_size;
+ file->both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name);
+ file->both_directory_info.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_ID_FULL_DIRECTORY_INFO:
+ file->id_full_directory_info.file_index = dir_index;
+ file->id_full_directory_info.create_time = name->dos.create_time;
+ file->id_full_directory_info.access_time = name->dos.access_time;
+ file->id_full_directory_info.write_time = name->dos.write_time;
+ file->id_full_directory_info.change_time = name->dos.change_time;
+ file->id_full_directory_info.size = name->st.st_size;
+ file->id_full_directory_info.alloc_size = name->dos.alloc_size;
+ file->id_full_directory_info.attrib = name->dos.attrib;
+ file->id_full_directory_info.ea_size = name->dos.ea_size;
+ file->id_full_directory_info.file_id = name->dos.file_id;
+ file->id_full_directory_info.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_ID_BOTH_DIRECTORY_INFO:
+ file->id_both_directory_info.file_index = dir_index;
+ file->id_both_directory_info.create_time = name->dos.create_time;
+ file->id_both_directory_info.access_time = name->dos.access_time;
+ file->id_both_directory_info.write_time = name->dos.write_time;
+ file->id_both_directory_info.change_time = name->dos.change_time;
+ file->id_both_directory_info.size = name->st.st_size;
+ file->id_both_directory_info.alloc_size = name->dos.alloc_size;
+ file->id_both_directory_info.attrib = name->dos.attrib;
+ file->id_both_directory_info.ea_size = name->dos.ea_size;
+ file->id_both_directory_info.file_id = name->dos.file_id;
+ file->id_both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name);
+ file->id_both_directory_info.name.s = fname;
+ return NT_STATUS_OK;
+
+ case RAW_SEARCH_DATA_GENERIC:
+ case RAW_SEARCH_DATA_UNIX_INFO:
+ case RAW_SEARCH_DATA_UNIX_INFO2:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
+
+
+/*
+ the search fill loop
+*/
+static NTSTATUS pvfs_search_fill(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ unsigned int max_count,
+ struct pvfs_search_state *search,
+ enum smb_search_data_level level,
+ unsigned int *reply_count,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_dir *dir = search->dir;
+ NTSTATUS status;
+
+ *reply_count = 0;
+
+ if (max_count == 0) {
+ max_count = 1;
+ }
+
+ while ((*reply_count) < max_count) {
+ union smb_search_data *file;
+ const char *name;
+ off_t ofs = search->current_index;
+
+ name = pvfs_list_next(dir, &search->current_index);
+ if (name == NULL) break;
+
+ file = talloc(mem_ctx, union smb_search_data);
+ if (!file) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fill_search_info(pvfs, level,
+ pvfs_list_unix_path(dir), name,
+ search, search->current_index, file);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(file);
+ continue;
+ }
+
+ if (!callback(search_private, file)) {
+ talloc_free(file);
+ search->current_index = ofs;
+ break;
+ }
+
+ (*reply_count)++;
+ talloc_free(file);
+ }
+
+ pvfs_search_setup_timer(search);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ we've run out of search handles - cleanup those that the client forgot
+ to close
+*/
+static void pvfs_search_cleanup(struct pvfs_state *pvfs)
+{
+ int i;
+ time_t t = time_mono(NULL);
+
+ for (i=0;i<MAX_OLD_SEARCHES;i++) {
+ struct pvfs_search_state *search;
+ void *p = idr_find(pvfs->search.idtree, i);
+
+ if (p == NULL) return;
+
+ search = talloc_get_type(p, struct pvfs_search_state);
+ if (pvfs_list_eos(search->dir, search->current_index) &&
+ search->last_used != 0 &&
+ t > search->last_used + 30) {
+ /* its almost certainly been forgotten
+ about */
+ talloc_free(search);
+ }
+ }
+}
+
+
+/*
+ list files in a directory matching a wildcard pattern - old SMBsearch interface
+*/
+static NTSTATUS pvfs_search_first_old(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_first *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_dir *dir;
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_search_state *search;
+ unsigned int reply_count;
+ uint16_t search_attrib;
+ const char *pattern;
+ NTSTATUS status;
+ struct pvfs_filename *name;
+ int id;
+
+ search_attrib = io->search_first.in.search_attrib;
+ pattern = io->search_first.in.pattern;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!name->has_wildcard && !name->exists) {
+ return STATUS_NO_MORE_FILES;
+ }
+
+ status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_TRAVERSE | SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* we initially make search a child of the request, then if we
+ need to keep it long term we steal it for the private
+ structure */
+ search = talloc(req, struct pvfs_search_state);
+ if (!search) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* do the actual directory listing */
+ status = pvfs_list_start(pvfs, name, search, &dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* we need to give a handle back to the client so it
+ can continue a search */
+ id = idr_get_new(pvfs->search.idtree, search, MAX_OLD_SEARCHES);
+ if (id == -1) {
+ pvfs_search_cleanup(pvfs);
+ id = idr_get_new(pvfs->search.idtree, search, MAX_OLD_SEARCHES);
+ }
+ if (id == -1) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ search->pvfs = pvfs;
+ search->handle = id;
+ search->dir = dir;
+ search->current_index = 0;
+ search->search_attrib = search_attrib & 0xFF;
+ search->must_attrib = (search_attrib>>8) & 0xFF;
+ search->last_used = time_mono(NULL);
+ search->te = NULL;
+
+ DLIST_ADD(pvfs->search.list, search);
+
+ talloc_set_destructor(search, pvfs_search_destructor);
+
+ status = pvfs_search_fill(pvfs, req, io->search_first.in.max_count, search, io->generic.data_level,
+ &reply_count, search_private, callback);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ io->search_first.out.count = reply_count;
+
+ /* not matching any entries is an error */
+ if (reply_count == 0) {
+ return STATUS_NO_MORE_FILES;
+ }
+
+ talloc_steal(pvfs, search);
+
+ return NT_STATUS_OK;
+}
+
+/* continue a old style search */
+static NTSTATUS pvfs_search_next_old(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_next *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ void *p;
+ struct pvfs_search_state *search;
+ struct pvfs_dir *dir;
+ unsigned int reply_count, max_count;
+ uint16_t handle;
+ NTSTATUS status;
+
+ handle = io->search_next.in.id.handle | (io->search_next.in.id.reserved<<8);
+ max_count = io->search_next.in.max_count;
+
+ p = idr_find(pvfs->search.idtree, handle);
+ if (p == NULL) {
+ /* we didn't find the search handle */
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ search = talloc_get_type(p, struct pvfs_search_state);
+
+ dir = search->dir;
+
+ status = pvfs_list_seek_ofs(dir, io->search_next.in.id.server_cookie,
+ &search->current_index);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ search->last_used = time_mono(NULL);
+
+ status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.data_level,
+ &reply_count, search_private, callback);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ io->search_next.out.count = reply_count;
+
+ /* not matching any entries means end of search */
+ if (reply_count == 0) {
+ talloc_free(search);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ list files in a directory matching a wildcard pattern
+*/
+static NTSTATUS pvfs_search_first_trans2(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_first *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_dir *dir;
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_search_state *search;
+ unsigned int reply_count;
+ uint16_t search_attrib, max_count;
+ const char *pattern;
+ NTSTATUS status;
+ struct pvfs_filename *name;
+ int id;
+
+ search_attrib = io->t2ffirst.in.search_attrib;
+ pattern = io->t2ffirst.in.pattern;
+ max_count = io->t2ffirst.in.max_count;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!name->has_wildcard && !name->exists) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_TRAVERSE | SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* we initially make search a child of the request, then if we
+ need to keep it long term we steal it for the private
+ structure */
+ search = talloc(req, struct pvfs_search_state);
+ if (!search) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* do the actual directory listing */
+ status = pvfs_list_start(pvfs, name, search, &dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ id = idr_get_new(pvfs->search.idtree, search, MAX_SEARCH_HANDLES);
+ if (id == -1) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ search->pvfs = pvfs;
+ search->handle = id;
+ search->dir = dir;
+ search->current_index = 0;
+ search->search_attrib = search_attrib;
+ search->must_attrib = 0;
+ search->last_used = 0;
+ search->num_ea_names = io->t2ffirst.in.num_names;
+ search->ea_names = io->t2ffirst.in.ea_names;
+ search->te = NULL;
+
+ DLIST_ADD(pvfs->search.list, search);
+ talloc_set_destructor(search, pvfs_search_destructor);
+
+ status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.data_level,
+ &reply_count, search_private, callback);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* not matching any entries is an error */
+ if (reply_count == 0) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ io->t2ffirst.out.count = reply_count;
+ io->t2ffirst.out.handle = search->handle;
+ io->t2ffirst.out.end_of_search = pvfs_list_eos(dir, search->current_index) ? 1 : 0;
+
+ /* work out if we are going to keep the search state
+ and allow for a search continue */
+ if ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE) ||
+ ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) &&
+ io->t2ffirst.out.end_of_search)) {
+ talloc_free(search);
+ } else {
+ talloc_steal(pvfs, search);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* continue a search */
+static NTSTATUS pvfs_search_next_trans2(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_next *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ void *p;
+ struct pvfs_search_state *search;
+ struct pvfs_dir *dir;
+ unsigned int reply_count;
+ uint16_t handle;
+ NTSTATUS status;
+
+ handle = io->t2fnext.in.handle;
+
+ p = idr_find(pvfs->search.idtree, handle);
+ if (p == NULL) {
+ /* we didn't find the search handle */
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ search = talloc_get_type(p, struct pvfs_search_state);
+
+ dir = search->dir;
+
+ status = NT_STATUS_OK;
+
+ /* work out what type of continuation is being used */
+ if (io->t2fnext.in.last_name && *io->t2fnext.in.last_name) {
+ status = pvfs_list_seek(dir, io->t2fnext.in.last_name, &search->current_index);
+ if (!NT_STATUS_IS_OK(status) && io->t2fnext.in.resume_key) {
+ status = pvfs_list_seek_ofs(dir, io->t2fnext.in.resume_key,
+ &search->current_index);
+ }
+ } else if (!(io->t2fnext.in.flags & FLAG_TRANS2_FIND_CONTINUE)) {
+ status = pvfs_list_seek_ofs(dir, io->t2fnext.in.resume_key,
+ &search->current_index);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ search->num_ea_names = io->t2fnext.in.num_names;
+ search->ea_names = io->t2fnext.in.ea_names;
+
+ status = pvfs_search_fill(pvfs, req, io->t2fnext.in.max_count, search, io->generic.data_level,
+ &reply_count, search_private, callback);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ io->t2fnext.out.count = reply_count;
+ io->t2fnext.out.end_of_search = pvfs_list_eos(dir, search->current_index) ? 1 : 0;
+
+ /* work out if we are going to keep the search state */
+ if ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE) ||
+ ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) &&
+ io->t2fnext.out.end_of_search)) {
+ talloc_free(search);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pvfs_search_first_smb2(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, const struct smb2_find *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_dir *dir;
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_search_state *search;
+ unsigned int reply_count;
+ uint16_t max_count;
+ const char *pattern;
+ NTSTATUS status;
+ struct pvfs_filename *name;
+ struct pvfs_file *f;
+
+ f = pvfs_find_fd(pvfs, req, io->in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* its only valid for directories */
+ if (f->handle->fd != -1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!(f->access_mask & SEC_DIR_LIST)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (f->search) {
+ talloc_free(f->search);
+ f->search = NULL;
+ }
+
+ if (strequal(io->in.pattern, "")) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (strchr_m(io->in.pattern, '\\')) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (strchr_m(io->in.pattern, '/')) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ if (strequal("", f->handle->name->original_name)) {
+ pattern = talloc_asprintf(req, "%s", io->in.pattern);
+ NT_STATUS_HAVE_NO_MEMORY(pattern);
+ } else {
+ pattern = talloc_asprintf(req, "%s\\%s",
+ f->handle->name->original_name,
+ io->in.pattern);
+ NT_STATUS_HAVE_NO_MEMORY(pattern);
+ }
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (!name->has_wildcard && !name->exists) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ /* we initially make search a child of the request, then if we
+ need to keep it long term we steal it for the private
+ structure */
+ search = talloc(req, struct pvfs_search_state);
+ NT_STATUS_HAVE_NO_MEMORY(search);
+
+ /* do the actual directory listing */
+ status = pvfs_list_start(pvfs, name, search, &dir);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ search->pvfs = pvfs;
+ search->handle = INVALID_SEARCH_HANDLE;
+ search->dir = dir;
+ search->current_index = 0;
+ search->search_attrib = 0x0000FFFF;
+ search->must_attrib = 0;
+ search->last_used = 0;
+ search->num_ea_names = 0;
+ search->ea_names = NULL;
+ search->te = NULL;
+
+ if (io->in.continue_flags & SMB2_CONTINUE_FLAG_SINGLE) {
+ max_count = 1;
+ } else {
+ max_count = UINT16_MAX;
+ }
+
+ status = pvfs_search_fill(pvfs, req, max_count, search, io->data_level,
+ &reply_count, search_private, callback);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ /* not matching any entries is an error */
+ if (reply_count == 0) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ f->search = talloc_steal(f, search);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pvfs_search_next_smb2(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, const struct smb2_find *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_search_state *search;
+ unsigned int reply_count;
+ uint16_t max_count;
+ NTSTATUS status;
+ struct pvfs_file *f;
+
+ f = pvfs_find_fd(pvfs, req, io->in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* its only valid for directories */
+ if (f->handle->fd != -1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* if there's no search started on the dir handle, it's like a search_first */
+ search = f->search;
+ if (!search) {
+ return pvfs_search_first_smb2(ntvfs, req, io, search_private, callback);
+ }
+
+ if (io->in.continue_flags & SMB2_CONTINUE_FLAG_RESTART) {
+ search->current_index = 0;
+ }
+
+ if (io->in.continue_flags & SMB2_CONTINUE_FLAG_SINGLE) {
+ max_count = 1;
+ } else {
+ max_count = UINT16_MAX;
+ }
+
+ status = pvfs_search_fill(pvfs, req, max_count, search, io->data_level,
+ &reply_count, search_private, callback);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ /* not matching any entries is an error */
+ if (reply_count == 0) {
+ return STATUS_NO_MORE_FILES;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ list files in a directory matching a wildcard pattern
+*/
+NTSTATUS pvfs_search_first(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_first *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ switch (io->generic.level) {
+ case RAW_SEARCH_SEARCH:
+ case RAW_SEARCH_FFIRST:
+ case RAW_SEARCH_FUNIQUE:
+ return pvfs_search_first_old(ntvfs, req, io, search_private, callback);
+
+ case RAW_SEARCH_TRANS2:
+ return pvfs_search_first_trans2(ntvfs, req, io, search_private, callback);
+
+ case RAW_SEARCH_SMB2:
+ return pvfs_search_first_smb2(ntvfs, req, &io->smb2, search_private, callback);
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
+
+/* continue a search */
+NTSTATUS pvfs_search_next(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_next *io,
+ void *search_private,
+ bool (*callback)(void *, const union smb_search_data *))
+{
+ switch (io->generic.level) {
+ case RAW_SEARCH_SEARCH:
+ case RAW_SEARCH_FFIRST:
+ return pvfs_search_next_old(ntvfs, req, io, search_private, callback);
+
+ case RAW_SEARCH_FUNIQUE:
+ return NT_STATUS_INVALID_LEVEL;
+
+ case RAW_SEARCH_TRANS2:
+ return pvfs_search_next_trans2(ntvfs, req, io, search_private, callback);
+
+ case RAW_SEARCH_SMB2:
+ return pvfs_search_next_smb2(ntvfs, req, &io->smb2, search_private, callback);
+ }
+
+ return NT_STATUS_INVALID_LEVEL;
+}
+
+
+/* close a search */
+NTSTATUS pvfs_search_close(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_search_close *io)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ void *p;
+ struct pvfs_search_state *search;
+ uint16_t handle = INVALID_SEARCH_HANDLE;
+
+ switch (io->generic.level) {
+ case RAW_FINDCLOSE_GENERIC:
+ return NT_STATUS_INVALID_LEVEL;
+
+ case RAW_FINDCLOSE_FCLOSE:
+ handle = io->fclose.in.id.handle;
+ break;
+
+ case RAW_FINDCLOSE_FINDCLOSE:
+ handle = io->findclose.in.handle;
+ break;
+ }
+
+ p = idr_find(pvfs->search.idtree, handle);
+ if (p == NULL) {
+ /* we didn't find the search handle */
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ search = talloc_get_type(p, struct pvfs_search_state);
+
+ talloc_free(search);
+
+ return NT_STATUS_OK;
+}
+
diff --git a/source4/ntvfs/posix/pvfs_seek.c b/source4/ntvfs/posix/pvfs_seek.c
new file mode 100644
index 0000000..2cd3410
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_seek.c
@@ -0,0 +1,65 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - seek
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+
+/*
+ seek in a file
+*/
+NTSTATUS pvfs_seek(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_seek *io)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+ struct pvfs_file_handle *h;
+ NTSTATUS status;
+
+ f = pvfs_find_fd(pvfs, req, io->lseek.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ h = f->handle;
+
+ status = NT_STATUS_OK;
+
+ switch (io->lseek.in.mode) {
+ case SEEK_MODE_START:
+ h->seek_offset = io->lseek.in.offset;
+ break;
+
+ case SEEK_MODE_CURRENT:
+ h->seek_offset += io->lseek.in.offset;
+ break;
+
+ case SEEK_MODE_END:
+ status = pvfs_resolve_name_fd(pvfs, h->fd, h->name, PVFS_RESOLVE_NO_OPENDB);
+ h->seek_offset = h->name->st.st_size + io->lseek.in.offset;
+ break;
+ }
+
+ io->lseek.out.offset = h->seek_offset;
+
+ return status;
+}
+
diff --git a/source4/ntvfs/posix/pvfs_setfileinfo.c b/source4/ntvfs/posix/pvfs_setfileinfo.c
new file mode 100644
index 0000000..7fd4e35
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_setfileinfo.c
@@ -0,0 +1,884 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - setfileinfo
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "system/time.h"
+#include "librpc/gen_ndr/xattr.h"
+
+
+/*
+ determine what access bits are needed for a call
+*/
+static uint32_t pvfs_setfileinfo_access(union smb_setfileinfo *info)
+{
+ uint32_t needed;
+
+ switch (info->generic.level) {
+ case RAW_SFILEINFO_EA_SET:
+ needed = SEC_FILE_WRITE_EA;
+ break;
+
+ case RAW_SFILEINFO_DISPOSITION_INFO:
+ case RAW_SFILEINFO_DISPOSITION_INFORMATION:
+ needed = SEC_STD_DELETE;
+ break;
+
+ case RAW_SFILEINFO_END_OF_FILE_INFO:
+ needed = SEC_FILE_WRITE_DATA;
+ break;
+
+ case RAW_SFILEINFO_POSITION_INFORMATION:
+ needed = 0;
+ break;
+
+ case RAW_SFILEINFO_SEC_DESC:
+ needed = 0;
+ if (info->set_secdesc.in.secinfo_flags & (SECINFO_OWNER|SECINFO_GROUP)) {
+ needed |= SEC_STD_WRITE_OWNER;
+ }
+ if (info->set_secdesc.in.secinfo_flags & SECINFO_DACL) {
+ needed |= SEC_STD_WRITE_DAC;
+ }
+ if (info->set_secdesc.in.secinfo_flags & SECINFO_SACL) {
+ needed |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ break;
+
+ case RAW_SFILEINFO_RENAME_INFORMATION:
+ case RAW_SFILEINFO_RENAME_INFORMATION_SMB2:
+ needed = SEC_STD_DELETE;
+ break;
+
+ default:
+ needed = SEC_FILE_WRITE_ATTRIBUTE;
+ break;
+ }
+
+ return needed;
+}
+
+/*
+ rename_information level for streams
+*/
+static NTSTATUS pvfs_setfileinfo_rename_stream(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ int fd,
+ DATA_BLOB *odb_locking_key,
+ union smb_setfileinfo *info)
+{
+ NTSTATUS status;
+ struct odb_lock *lck = NULL;
+
+ /* strangely, this gives a sharing violation, not invalid
+ parameter */
+ if (info->rename_information.in.new_name[0] != ':') {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ status = pvfs_access_check_simple(pvfs, req, name, SEC_FILE_WRITE_ATTRIBUTE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for can_stat\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+
+ status = pvfs_stream_rename(pvfs, name, fd,
+ info->rename_information.in.new_name+1,
+ info->rename_information.in.overwrite);
+ return status;
+}
+
+/*
+ rename_information level
+*/
+static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ struct pvfs_filename *name,
+ int fd,
+ DATA_BLOB *odb_locking_key,
+ union smb_setfileinfo *info)
+{
+ NTSTATUS status;
+ struct pvfs_filename *name2;
+ char *new_name, *p;
+ struct odb_lock *lck = NULL;
+
+ /* renames are only allowed within a directory */
+ if (strchr_m(info->rename_information.in.new_name, '\\') &&
+ (req->ctx->protocol < PROTOCOL_SMB2_02)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* handle stream renames specially */
+ if (name->stream_name) {
+ return pvfs_setfileinfo_rename_stream(pvfs, req, name, fd,
+ odb_locking_key, info);
+ }
+
+ /* w2k3 does not appear to allow relative rename. On SMB2, vista sends it sometimes,
+ but I suspect it is just uninitialised memory */
+ if (info->rename_information.in.root_fid != 0 &&
+ (req->ctx->protocol < PROTOCOL_SMB2_02)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* construct the fully qualified windows name for the new file name */
+ if (req->ctx->protocol >= PROTOCOL_SMB2_02) {
+ /* SMB2 sends the full path of the new name */
+ new_name = talloc_asprintf(req, "\\%s", info->rename_information.in.new_name);
+ } else {
+ new_name = talloc_strdup(req, name->original_name);
+ if (new_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strrchr_m(new_name, '\\');
+ if (p == NULL) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ } else {
+ *p = 0;
+ }
+
+ new_name = talloc_asprintf(req, "%s\\%s", new_name,
+ info->rename_information.in.new_name);
+ }
+ if (new_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* resolve the new name */
+ status = pvfs_resolve_name(pvfs, req, new_name, 0, &name2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* if the destination exists, then check the rename is allowed */
+ if (name2->exists) {
+ if (strcmp(name2->full_name, name->full_name) == 0) {
+ /* rename to same name is null-op */
+ return NT_STATUS_OK;
+ }
+
+ if (!info->rename_information.in.overwrite) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = pvfs_can_delete(pvfs, req, name2, NULL);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DELETE_PENDING)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ status = pvfs_access_check_parent(pvfs, req, name2, SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ lck = odb_lock(req, pvfs->odb_context, odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for can_stat\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = pvfs_do_rename(pvfs, lck, name, name2->full_name);
+ talloc_free(lck);
+ NT_STATUS_NOT_OK_RETURN(status);
+ if (NT_STATUS_IS_OK(status)) {
+ name->full_name = talloc_steal(name, name2->full_name);
+ name->original_name = talloc_steal(name, name2->original_name);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ add a single DOS EA
+*/
+NTSTATUS pvfs_setfileinfo_ea_set(struct pvfs_state *pvfs,
+ struct pvfs_filename *name,
+ int fd, uint16_t num_eas,
+ struct ea_struct *eas)
+{
+ struct xattr_DosEAs *ealist;
+ int i, j;
+ NTSTATUS status;
+
+ if (num_eas == 0) {
+ return NT_STATUS_OK;
+ }
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ealist = talloc(name, struct xattr_DosEAs);
+
+ /* load the current list */
+ status = pvfs_doseas_load(pvfs, name, fd, ealist);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (j=0;j<num_eas;j++) {
+ struct ea_struct *ea = &eas[j];
+ /* see if its already there */
+ for (i=0;i<ealist->num_eas;i++) {
+ if (strcasecmp_m(ealist->eas[i].name, ea->name.s) == 0) {
+ ealist->eas[i].value = ea->value;
+ break;
+ }
+ }
+
+ if (i==ealist->num_eas) {
+ /* add it */
+ ealist->eas = talloc_realloc(ealist, ealist->eas,
+ struct xattr_EA,
+ ealist->num_eas+1);
+ if (ealist->eas == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ealist->eas[i].name = ea->name.s;
+ ealist->eas[i].value = ea->value;
+ ealist->num_eas++;
+ }
+ }
+
+ /* pull out any null EAs */
+ for (i=0;i<ealist->num_eas;i++) {
+ if (ealist->eas[i].value.length == 0) {
+ memmove(&ealist->eas[i],
+ &ealist->eas[i+1],
+ (ealist->num_eas-(i+1)) * sizeof(ealist->eas[i]));
+ ealist->num_eas--;
+ i--;
+ }
+ }
+
+ status = pvfs_doseas_save(pvfs, name, fd, ealist);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_EA,
+ name->full_name);
+
+ name->dos.ea_size = 4;
+ for (i=0;i<ealist->num_eas;i++) {
+ name->dos.ea_size += 4 + strlen(ealist->eas[i].name)+1 +
+ ealist->eas[i].value.length;
+ }
+
+ /* update the ea_size attrib */
+ return pvfs_dosattrib_save(pvfs, name, fd);
+}
+
+/*
+ set info on a open file
+*/
+NTSTATUS pvfs_setfileinfo(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_setfileinfo *info)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_file *f;
+ struct pvfs_file_handle *h;
+ struct pvfs_filename newstats;
+ NTSTATUS status;
+ uint32_t access_needed;
+ uint32_t change_mask = 0;
+
+ f = pvfs_find_fd(pvfs, req, info->generic.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ h = f->handle;
+
+ access_needed = pvfs_setfileinfo_access(info);
+ if ((f->access_mask & access_needed) != access_needed) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* update the file information */
+ status = pvfs_resolve_name_handle(pvfs, h);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* we take a copy of the current file stats, then update
+ newstats in each of the elements below. At the end we
+ compare, and make any changes needed */
+ newstats = *h->name;
+
+ switch (info->generic.level) {
+ case RAW_SFILEINFO_SETATTR:
+ if (!null_time(info->setattr.in.write_time)) {
+ unix_to_nt_time(&newstats.dos.write_time, info->setattr.in.write_time);
+ }
+ if (info->setattr.in.attrib != FILE_ATTRIBUTE_NORMAL) {
+ newstats.dos.attrib = info->setattr.in.attrib;
+ }
+ break;
+
+ case RAW_SFILEINFO_SETATTRE:
+ case RAW_SFILEINFO_STANDARD:
+ if (!null_time(info->setattre.in.create_time)) {
+ unix_to_nt_time(&newstats.dos.create_time, info->setattre.in.create_time);
+ }
+ if (!null_time(info->setattre.in.access_time)) {
+ unix_to_nt_time(&newstats.dos.access_time, info->setattre.in.access_time);
+ }
+ if (!null_time(info->setattre.in.write_time)) {
+ unix_to_nt_time(&newstats.dos.write_time, info->setattre.in.write_time);
+ }
+ break;
+
+ case RAW_SFILEINFO_EA_SET:
+ return pvfs_setfileinfo_ea_set(pvfs, h->name, h->fd,
+ info->ea_set.in.num_eas,
+ info->ea_set.in.eas);
+
+ case RAW_SFILEINFO_BASIC_INFO:
+ case RAW_SFILEINFO_BASIC_INFORMATION:
+ if (!null_nttime(info->basic_info.in.create_time)) {
+ newstats.dos.create_time = info->basic_info.in.create_time;
+ }
+ if (!null_nttime(info->basic_info.in.access_time)) {
+ newstats.dos.access_time = info->basic_info.in.access_time;
+ }
+ if (!null_nttime(info->basic_info.in.write_time)) {
+ newstats.dos.write_time = info->basic_info.in.write_time;
+ }
+ if (!null_nttime(info->basic_info.in.change_time)) {
+ newstats.dos.change_time = info->basic_info.in.change_time;
+ }
+ if (info->basic_info.in.attrib != 0) {
+ newstats.dos.attrib = info->basic_info.in.attrib;
+ }
+ break;
+
+ case RAW_SFILEINFO_DISPOSITION_INFO:
+ case RAW_SFILEINFO_DISPOSITION_INFORMATION:
+ return pvfs_set_delete_on_close(pvfs, req, f,
+ info->disposition_info.in.delete_on_close);
+
+ case RAW_SFILEINFO_ALLOCATION_INFO:
+ case RAW_SFILEINFO_ALLOCATION_INFORMATION:
+ status = pvfs_break_level2_oplocks(f);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ newstats.dos.alloc_size = info->allocation_info.in.alloc_size;
+ if (newstats.dos.alloc_size < newstats.st.st_size) {
+ newstats.st.st_size = newstats.dos.alloc_size;
+ }
+ newstats.dos.alloc_size = pvfs_round_alloc_size(pvfs,
+ newstats.dos.alloc_size);
+ break;
+
+ case RAW_SFILEINFO_END_OF_FILE_INFO:
+ case RAW_SFILEINFO_END_OF_FILE_INFORMATION:
+ status = pvfs_break_level2_oplocks(f);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ newstats.st.st_size = info->end_of_file_info.in.size;
+ break;
+
+ case RAW_SFILEINFO_POSITION_INFORMATION:
+ h->position = info->position_information.in.position;
+ break;
+
+ case RAW_SFILEINFO_FULL_EA_INFORMATION:
+ return pvfs_setfileinfo_ea_set(pvfs, h->name, h->fd,
+ info->full_ea_information.in.eas.num_eas,
+ info->full_ea_information.in.eas.eas);
+
+ case RAW_SFILEINFO_MODE_INFORMATION:
+ /* this one is a puzzle */
+ if (info->mode_information.in.mode != 0 &&
+ info->mode_information.in.mode != 2 &&
+ info->mode_information.in.mode != 4 &&
+ info->mode_information.in.mode != 6) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ h->mode = info->mode_information.in.mode;
+ break;
+
+ case RAW_SFILEINFO_RENAME_INFORMATION:
+ case RAW_SFILEINFO_RENAME_INFORMATION_SMB2:
+ return pvfs_setfileinfo_rename(pvfs, req, h->name, f->handle->fd,
+ &h->odb_locking_key,
+ info);
+
+ case RAW_SFILEINFO_SEC_DESC:
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_SECURITY,
+ h->name->full_name);
+ return pvfs_acl_set(pvfs, req, h->name, h->fd, f->access_mask, info);
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ /* possibly change the file size */
+ if (newstats.st.st_size != h->name->st.st_size) {
+ if (h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ if (h->name->stream_name) {
+ status = pvfs_stream_truncate(pvfs, h->name, h->fd, newstats.st.st_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ change_mask |= FILE_NOTIFY_CHANGE_STREAM_SIZE;
+ } else {
+ int ret;
+ if (f->access_mask &
+ (SEC_FILE_WRITE_DATA|SEC_FILE_APPEND_DATA)) {
+ ret = ftruncate(h->fd, newstats.st.st_size);
+ } else {
+ ret = truncate(h->name->full_name, newstats.st.st_size);
+ }
+ if (ret == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ change_mask |= FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_ATTRIBUTES;
+ }
+ }
+
+ /* possibly change the file timestamps */
+ if (newstats.dos.create_time != h->name->dos.create_time) {
+ change_mask |= FILE_NOTIFY_CHANGE_CREATION;
+ }
+ if (newstats.dos.access_time != h->name->dos.access_time) {
+ change_mask |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
+ }
+ if (newstats.dos.write_time != h->name->dos.write_time) {
+ change_mask |= FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+ if ((change_mask & FILE_NOTIFY_CHANGE_LAST_ACCESS) ||
+ (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE)) {
+ struct timeval tv[2];
+
+ nttime_to_timeval(&tv[0], newstats.dos.access_time);
+ nttime_to_timeval(&tv[1], newstats.dos.write_time);
+
+ if (!timeval_is_zero(&tv[0]) || !timeval_is_zero(&tv[1])) {
+ if (utimes(h->name->full_name, tv) == -1) {
+ DEBUG(0,("pvfs_setfileinfo: utimes() failed '%s' - %s\n",
+ h->name->full_name, strerror(errno)));
+ return pvfs_map_errno(pvfs, errno);
+ }
+ }
+ }
+ if (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE) {
+ struct odb_lock *lck;
+
+ lck = odb_lock(req, h->pvfs->odb_context, &h->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for write time update\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = odb_set_write_time(lck, newstats.dos.write_time, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to update write time: %s\n",
+ nt_errstr(status)));
+ talloc_free(lck);
+ return status;
+ }
+
+ talloc_free(lck);
+
+ h->write_time.update_forced = true;
+ h->write_time.update_on_close = false;
+ talloc_free(h->write_time.update_event);
+ h->write_time.update_event = NULL;
+ }
+
+ /* possibly change the attribute */
+ if (newstats.dos.attrib != h->name->dos.attrib) {
+ mode_t mode;
+ if ((newstats.dos.attrib & FILE_ATTRIBUTE_DIRECTORY) &&
+ !(h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ mode = pvfs_fileperms(pvfs, newstats.dos.attrib);
+ if (!(h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+ if (pvfs_sys_fchmod(pvfs, h->fd, mode, h->name->allow_override) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ }
+ change_mask |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
+ }
+
+ *h->name = newstats;
+
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_MODIFIED,
+ change_mask,
+ h->name->full_name);
+
+ return pvfs_dosattrib_save(pvfs, h->name, h->fd);
+}
+
+/*
+ retry an open after a sharing violation
+*/
+static void pvfs_retry_setpathinfo(struct pvfs_odb_retry *r,
+ struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ void *_info,
+ void *private_data,
+ enum pvfs_wait_notice reason)
+{
+ union smb_setfileinfo *info = talloc_get_type(_info,
+ union smb_setfileinfo);
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+
+ talloc_free(r);
+
+ switch (reason) {
+ case PVFS_WAIT_CANCEL:
+/*TODO*/
+ status = NT_STATUS_CANCELLED;
+ break;
+ case PVFS_WAIT_TIMEOUT:
+ /* if it timed out, then give the failure
+ immediately */
+/*TODO*/
+ status = NT_STATUS_SHARING_VIOLATION;
+ break;
+ case PVFS_WAIT_EVENT:
+
+ /* try the open again, which could trigger another retry setup
+ if it wants to, so we have to unmark the async flag so we
+ will know if it does a second async reply */
+ req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC;
+
+ status = pvfs_setpathinfo(ntvfs, req, info);
+ if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) {
+ /* the 2nd try also replied async, so we don't send
+ the reply yet */
+ return;
+ }
+
+ /* re-mark it async, just in case someone up the chain does
+ paranoid checking */
+ req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+ break;
+ }
+
+ /* send the reply up the chain */
+ req->async_states->status = status;
+ req->async_states->send_fn(req);
+}
+
+/*
+ setup for a unlink retry after a sharing violation
+ or a non granted oplock
+*/
+static NTSTATUS pvfs_setpathinfo_setup_retry(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_setfileinfo *info,
+ struct odb_lock *lck,
+ NTSTATUS status)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct timeval end_time;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ 0, pvfs->sharing_violation_delay);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ pvfs->oplock_break_timeout, 0);
+ } else {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, info, NULL,
+ pvfs_retry_setpathinfo);
+}
+
+/*
+ set info on a pathname
+*/
+NTSTATUS pvfs_setpathinfo(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_setfileinfo *info)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_filename *name;
+ struct pvfs_filename newstats;
+ NTSTATUS status;
+ uint32_t access_needed;
+ uint32_t change_mask = 0;
+ struct odb_lock *lck = NULL;
+ DATA_BLOB odb_locking_key;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, info->generic.in.file.path,
+ PVFS_RESOLVE_STREAMS, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!name->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ access_needed = pvfs_setfileinfo_access(info);
+ status = pvfs_access_check_simple(pvfs, req, name, access_needed);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* we take a copy of the current file stats, then update
+ newstats in each of the elements below. At the end we
+ compare, and make any changes needed */
+ newstats = *name;
+
+ switch (info->generic.level) {
+ case RAW_SFILEINFO_SETATTR:
+ if (!null_time(info->setattr.in.write_time)) {
+ unix_to_nt_time(&newstats.dos.write_time, info->setattr.in.write_time);
+ }
+ if (info->setattr.in.attrib == 0) {
+ newstats.dos.attrib = FILE_ATTRIBUTE_NORMAL;
+ } else if (info->setattr.in.attrib != FILE_ATTRIBUTE_NORMAL) {
+ newstats.dos.attrib = info->setattr.in.attrib;
+ }
+ break;
+
+ case RAW_SFILEINFO_SETATTRE:
+ case RAW_SFILEINFO_STANDARD:
+ if (!null_time(info->setattre.in.create_time)) {
+ unix_to_nt_time(&newstats.dos.create_time, info->setattre.in.create_time);
+ }
+ if (!null_time(info->setattre.in.access_time)) {
+ unix_to_nt_time(&newstats.dos.access_time, info->setattre.in.access_time);
+ }
+ if (!null_time(info->setattre.in.write_time)) {
+ unix_to_nt_time(&newstats.dos.write_time, info->setattre.in.write_time);
+ }
+ break;
+
+ case RAW_SFILEINFO_EA_SET:
+ return pvfs_setfileinfo_ea_set(pvfs, name, -1,
+ info->ea_set.in.num_eas,
+ info->ea_set.in.eas);
+
+ case RAW_SFILEINFO_BASIC_INFO:
+ case RAW_SFILEINFO_BASIC_INFORMATION:
+ if (!null_nttime(info->basic_info.in.create_time)) {
+ newstats.dos.create_time = info->basic_info.in.create_time;
+ }
+ if (!null_nttime(info->basic_info.in.access_time)) {
+ newstats.dos.access_time = info->basic_info.in.access_time;
+ }
+ if (!null_nttime(info->basic_info.in.write_time)) {
+ newstats.dos.write_time = info->basic_info.in.write_time;
+ }
+ if (!null_nttime(info->basic_info.in.change_time)) {
+ newstats.dos.change_time = info->basic_info.in.change_time;
+ }
+ if (info->basic_info.in.attrib != 0) {
+ newstats.dos.attrib = info->basic_info.in.attrib;
+ }
+ break;
+
+ case RAW_SFILEINFO_ALLOCATION_INFO:
+ case RAW_SFILEINFO_ALLOCATION_INFORMATION:
+ status = pvfs_can_update_file_size(pvfs, req, name, &lck);
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_setpathinfo_setup_retry(pvfs->ntvfs, req, info, lck, status);
+ }
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (info->allocation_info.in.alloc_size > newstats.dos.alloc_size) {
+ /* strange. Increasing the allocation size via setpathinfo
+ should be silently ignored */
+ break;
+ }
+ newstats.dos.alloc_size = info->allocation_info.in.alloc_size;
+ if (newstats.dos.alloc_size < newstats.st.st_size) {
+ newstats.st.st_size = newstats.dos.alloc_size;
+ }
+ newstats.dos.alloc_size = pvfs_round_alloc_size(pvfs,
+ newstats.dos.alloc_size);
+ break;
+
+ case RAW_SFILEINFO_END_OF_FILE_INFO:
+ case RAW_SFILEINFO_END_OF_FILE_INFORMATION:
+ status = pvfs_can_update_file_size(pvfs, req, name, &lck);
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_setpathinfo_setup_retry(pvfs->ntvfs, req, info, lck, status);
+ }
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ newstats.st.st_size = info->end_of_file_info.in.size;
+ break;
+
+ case RAW_SFILEINFO_MODE_INFORMATION:
+ if (info->mode_information.in.mode != 0 &&
+ info->mode_information.in.mode != 2 &&
+ info->mode_information.in.mode != 4 &&
+ info->mode_information.in.mode != 6) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+
+ case RAW_SFILEINFO_RENAME_INFORMATION:
+ case RAW_SFILEINFO_RENAME_INFORMATION_SMB2:
+ status = pvfs_locking_key(name, name, &odb_locking_key);
+ NT_STATUS_NOT_OK_RETURN(status);
+ status = pvfs_setfileinfo_rename(pvfs, req, name, -1,
+ &odb_locking_key, info);
+ NT_STATUS_NOT_OK_RETURN(status);
+ return NT_STATUS_OK;
+
+ case RAW_SFILEINFO_DISPOSITION_INFO:
+ case RAW_SFILEINFO_DISPOSITION_INFORMATION:
+ case RAW_SFILEINFO_POSITION_INFORMATION:
+ return NT_STATUS_OK;
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ /* possibly change the file size */
+ if (newstats.st.st_size != name->st.st_size) {
+ if (name->stream_name) {
+ status = pvfs_stream_truncate(pvfs, name, -1, newstats.st.st_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (truncate(name->full_name, newstats.st.st_size) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ change_mask |= FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_ATTRIBUTES;
+ }
+
+ /* possibly change the file timestamps */
+ if (newstats.dos.create_time != name->dos.create_time) {
+ change_mask |= FILE_NOTIFY_CHANGE_CREATION;
+ }
+ if (newstats.dos.access_time != name->dos.access_time) {
+ change_mask |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
+ }
+ if (newstats.dos.write_time != name->dos.write_time) {
+ change_mask |= FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+ if ((change_mask & FILE_NOTIFY_CHANGE_LAST_ACCESS) ||
+ (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE)) {
+ struct timeval tv[2];
+
+ nttime_to_timeval(&tv[0], newstats.dos.access_time);
+ nttime_to_timeval(&tv[1], newstats.dos.write_time);
+
+ if (!timeval_is_zero(&tv[0]) || !timeval_is_zero(&tv[1])) {
+ if (utimes(name->full_name, tv) == -1) {
+ DEBUG(0,("pvfs_setpathinfo: utimes() failed '%s' - %s\n",
+ name->full_name, strerror(errno)));
+ return pvfs_map_errno(pvfs, errno);
+ }
+ }
+ }
+ if (change_mask & FILE_NOTIFY_CHANGE_LAST_WRITE) {
+ if (lck == NULL) {
+ DATA_BLOB lkey;
+ status = pvfs_locking_key(name, name, &lkey);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ lck = odb_lock(req, pvfs->odb_context, &lkey);
+ data_blob_free(&lkey);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for write time update\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ status = odb_set_write_time(lck, newstats.dos.write_time, true);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /* it could be that nobody has opened the file */
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to update write time: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ /* possibly change the attribute */
+ newstats.dos.attrib |= (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY);
+ if (newstats.dos.attrib != name->dos.attrib) {
+ mode_t mode = pvfs_fileperms(pvfs, newstats.dos.attrib);
+ if (pvfs_sys_chmod(pvfs, name->full_name, mode, name->allow_override) == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ change_mask |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
+ }
+
+ *name = newstats;
+
+ if (change_mask != 0) {
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_MODIFIED,
+ change_mask,
+ name->full_name);
+ }
+
+ return pvfs_dosattrib_save(pvfs, name, -1);
+}
+
diff --git a/source4/ntvfs/posix/pvfs_shortname.c b/source4/ntvfs/posix/pvfs_shortname.c
new file mode 100644
index 0000000..9e3cf5f
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_shortname.c
@@ -0,0 +1,699 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - 8.3 name routines
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "system/locale.h"
+#include "vfs_posix.h"
+#include "param/param.h"
+
+#undef strcasecmp
+
+/*
+ this mangling scheme uses the following format
+
+ Annnn~n.AAA
+
+ where nnnnn is a base 36 hash, and A represents characters from the original string
+
+ The hash is taken of the leading part of the long filename, in uppercase
+
+ for simplicity, we only allow ascii characters in 8.3 names
+*/
+
+/*
+ ===============================================================================
+ NOTE NOTE NOTE!!!
+
+ This file deliberately uses non-multibyte string functions in many places. This
+ is *not* a mistake. This code is multi-byte safe, but it gets this property
+ through some very subtle knowledge of the way multi-byte strings are encoded
+ and the fact that this mangling algorithm only supports ascii characters in
+ 8.3 names.
+
+ please don't convert this file to use the *_m() functions!!
+ ===============================================================================
+*/
+
+
+#if 1
+#define M_DEBUG(level, x) DEBUG(level, x)
+#else
+#define M_DEBUG(level, x)
+#endif
+
+/* these flags are used to mark characters in as having particular
+ properties */
+#define FLAG_BASECHAR 1
+#define FLAG_ASCII 2
+#define FLAG_ILLEGAL 4
+#define FLAG_WILDCARD 8
+
+/* the "possible" flags are used as a fast way to find possible DOS
+ reserved filenames */
+#define FLAG_POSSIBLE1 16
+#define FLAG_POSSIBLE2 32
+#define FLAG_POSSIBLE3 64
+#define FLAG_POSSIBLE4 128
+
+#define DEFAULT_MANGLE_PREFIX 4
+
+#define MANGLE_BASECHARS "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+#define FLAG_CHECK(c, flag) (ctx->char_flags[(uint8_t)(c)] & (flag))
+
+static const char *reserved_names[] =
+{ "AUX", "CON",
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+ "NUL", "PRN", NULL };
+
+
+struct pvfs_mangle_context {
+ uint8_t char_flags[256];
+ /*
+ this determines how many characters are used from the original
+ filename in the 8.3 mangled name. A larger value leads to a weaker
+ hash and more collisions. The largest possible value is 6.
+ */
+ int mangle_prefix;
+ uint32_t mangle_modulus;
+
+ /* we will use a very simple direct mapped prefix cache. The big
+ advantage of this cache structure is speed and low memory usage
+
+ The cache is indexed by the low-order bits of the hash, and confirmed by
+ hashing the resulting cache entry to match the known hash
+ */
+ uint32_t cache_size;
+ char **prefix_cache;
+ uint32_t *prefix_cache_hashes;
+
+ /* this is used to reverse the base 36 mapping */
+ unsigned char base_reverse[256];
+};
+
+
+/*
+ hash a string of the specified length. The string does not need to be
+ null terminated
+
+ this hash needs to be fast with a low collision rate (what hash doesn't?)
+*/
+static uint32_t mangle_hash(struct pvfs_mangle_context *ctx,
+ const char *key, size_t length)
+{
+ return pvfs_name_hash(key, length) % ctx->mangle_modulus;
+}
+
+/*
+ insert an entry into the prefix cache. The string might not be null
+ terminated */
+static void cache_insert(struct pvfs_mangle_context *ctx,
+ const char *prefix, int length, uint32_t hash)
+{
+ int i = hash % ctx->cache_size;
+
+ if (ctx->prefix_cache[i]) {
+ talloc_free(ctx->prefix_cache[i]);
+ }
+
+ ctx->prefix_cache[i] = talloc_strndup(ctx->prefix_cache, prefix, length);
+ ctx->prefix_cache_hashes[i] = hash;
+}
+
+/*
+ lookup an entry in the prefix cache. Return NULL if not found.
+*/
+static const char *cache_lookup(struct pvfs_mangle_context *ctx, uint32_t hash)
+{
+ int i = hash % ctx->cache_size;
+
+
+ if (!ctx->prefix_cache[i] || hash != ctx->prefix_cache_hashes[i]) {
+ return NULL;
+ }
+
+ /* yep, it matched */
+ return ctx->prefix_cache[i];
+}
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+ */
+static bool is_mangled_component(struct pvfs_mangle_context *ctx,
+ const char *name, size_t len)
+{
+ unsigned int i;
+
+ M_DEBUG(10,("is_mangled_component %s (len %u) ?\n", name, (unsigned int)len));
+
+ /* check the length */
+ if (len > 12 || len < 8)
+ return false;
+
+ /* the best distinguishing characteristic is the ~ */
+ if (name[6] != '~')
+ return false;
+
+ /* check extension */
+ if (len > 8) {
+ if (name[8] != '.')
+ return false;
+ for (i=9; name[i] && i < len; i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return false;
+ }
+ }
+ }
+
+ /* check lead characters */
+ for (i=0;i<ctx->mangle_prefix;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return false;
+ }
+ }
+
+ /* check rest of hash */
+ if (! FLAG_CHECK(name[7], FLAG_BASECHAR)) {
+ return false;
+ }
+ for (i=ctx->mangle_prefix;i<6;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_BASECHAR)) {
+ return false;
+ }
+ }
+
+ M_DEBUG(10,("is_mangled_component %s (len %u) -> yes\n", name, (unsigned int)len));
+
+ return true;
+}
+
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+
+ NOTE! This interface must be able to handle a path with unix
+ directory separators. It should return true if any component is
+ mangled
+ */
+static bool is_mangled(struct pvfs_mangle_context *ctx, const char *name)
+{
+ const char *p;
+ const char *s;
+
+ M_DEBUG(10,("is_mangled %s ?\n", name));
+
+ for (s=name; (p=strchr(s, '/')); s=p+1) {
+ if (is_mangled_component(ctx, s, PTR_DIFF(p, s))) {
+ return true;
+ }
+ }
+
+ /* and the last part ... */
+ return is_mangled_component(ctx, s, strlen(s));
+}
+
+
+/*
+ see if a filename is an allowable 8.3 name.
+
+ we are only going to allow ascii characters in 8.3 names, as this
+ simplifies things greatly (it means that we know the string won't
+ get larger when converted from UNIX to DOS formats)
+*/
+static bool is_8_3(struct pvfs_mangle_context *ctx,
+ const char *name, bool check_case, bool allow_wildcards)
+{
+ int len, i;
+ char *dot_p;
+
+ /* as a special case, the names '.' and '..' are allowable 8.3 names */
+ if (name[0] == '.') {
+ if (!name[1] || (name[1] == '.' && !name[2])) {
+ return true;
+ }
+ }
+
+ /* the simplest test is on the overall length of the
+ filename. Note that we deliberately use the ascii string
+ length (not the multi-byte one) as it is faster, and gives us
+ the result we need in this case. Using strlen_m would not
+ only be slower, it would be incorrect */
+ len = strlen(name);
+ if (len > 12)
+ return false;
+
+ /* find the '.'. Note that once again we use the non-multibyte
+ function */
+ dot_p = strchr(name, '.');
+
+ if (!dot_p) {
+ /* if the name doesn't contain a '.' then its length
+ must be less than 8 */
+ if (len > 8) {
+ return false;
+ }
+ } else {
+ int prefix_len, suffix_len;
+
+ /* if it does contain a dot then the prefix must be <=
+ 8 and the suffix <= 3 in length */
+ prefix_len = PTR_DIFF(dot_p, name);
+ suffix_len = len - (prefix_len+1);
+
+ if (prefix_len > 8 || suffix_len > 3 || suffix_len == 0) {
+ return false;
+ }
+
+ /* a 8.3 name cannot contain more than 1 '.' */
+ if (strchr(dot_p+1, '.')) {
+ return false;
+ }
+ }
+
+ /* the length are all OK. Now check to see if the characters themselves are OK */
+ for (i=0; name[i]; i++) {
+ /* note that we may allow wildcard petterns! */
+ if (!FLAG_CHECK(name[i], FLAG_ASCII|(allow_wildcards ? FLAG_WILDCARD : 0)) &&
+ name[i] != '.') {
+ return false;
+ }
+ }
+
+ /* it is a good 8.3 name */
+ return true;
+}
+
+
+/*
+ try to find a 8.3 name in the cache, and if found then
+ return the original long name.
+*/
+static char *check_cache(struct pvfs_mangle_context *ctx,
+ TALLOC_CTX *mem_ctx, const char *name)
+{
+ uint32_t hash, multiplier;
+ unsigned int i;
+ const char *prefix;
+ char extension[4];
+
+ /* make sure that this is a mangled name from this cache */
+ if (!is_mangled(ctx, name)) {
+ M_DEBUG(10,("check_cache: %s -> not mangled\n", name));
+ return NULL;
+ }
+
+ /* we need to extract the hash from the 8.3 name */
+ hash = ctx->base_reverse[(unsigned char)name[7]];
+ for (multiplier=36, i=5;i>=ctx->mangle_prefix;i--) {
+ uint32_t v = ctx->base_reverse[(unsigned char)name[i]];
+ hash += multiplier * v;
+ multiplier *= 36;
+ }
+
+ /* now look in the prefix cache for that hash */
+ prefix = cache_lookup(ctx, hash);
+ if (!prefix) {
+ M_DEBUG(10,("check_cache: %s -> %08X -> not found\n", name, hash));
+ return NULL;
+ }
+
+ /* we found it - construct the full name */
+ if (name[8] == '.') {
+ strncpy(extension, name+9, 3);
+ extension[3] = 0;
+ } else {
+ extension[0] = 0;
+ }
+
+ if (extension[0]) {
+ return talloc_asprintf(mem_ctx, "%s.%s", prefix, extension);
+ }
+
+ return talloc_strdup(mem_ctx, prefix);
+}
+
+
+/*
+ look for a DOS reserved name
+*/
+static bool is_reserved_name(struct pvfs_mangle_context *ctx, const char *name)
+{
+ if (FLAG_CHECK(name[0], FLAG_POSSIBLE1) &&
+ FLAG_CHECK(name[1], FLAG_POSSIBLE2) &&
+ FLAG_CHECK(name[2], FLAG_POSSIBLE3) &&
+ FLAG_CHECK(name[3], FLAG_POSSIBLE4)) {
+ /* a likely match, scan the lot */
+ int i;
+ for (i=0; reserved_names[i]; i++) {
+ if (strcasecmp(name, reserved_names[i]) == 0) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+/*
+ See if a filename is a legal long filename.
+ A filename ending in a '.' is not legal unless it's "." or "..". JRA.
+*/
+static bool is_legal_name(struct pvfs_mangle_context *ctx, const char *name)
+{
+ while (*name) {
+ size_t c_size;
+ codepoint_t c = next_codepoint(name, &c_size);
+ if (c == INVALID_CODEPOINT) {
+ return false;
+ }
+ /* all high chars are OK */
+ if (c >= 128) {
+ name += c_size;
+ continue;
+ }
+ if (FLAG_CHECK(c, FLAG_ILLEGAL)) {
+ return false;
+ }
+ name += c_size;
+ }
+
+ return true;
+}
+
+/*
+ the main forward mapping function, which converts a long filename to
+ a 8.3 name
+
+ if need83 is not set then we only do the mangling if the name is illegal
+ as a long name
+
+ if cache83 is not set then we don't cache the result
+
+ return NULL if we don't need to do any conversion
+*/
+static char *name_map(struct pvfs_mangle_context *ctx,
+ const char *name, bool need83, bool cache83)
+{
+ char *dot_p;
+ char lead_chars[7];
+ char extension[4];
+ unsigned int extension_length, i;
+ unsigned int prefix_len;
+ uint32_t hash, v;
+ char *new_name;
+ const char *basechars = MANGLE_BASECHARS;
+
+ /* reserved names are handled specially */
+ if (!is_reserved_name(ctx, name)) {
+ /* if the name is already a valid 8.3 name then we don't need to
+ do anything */
+ if (is_8_3(ctx, name, false, false)) {
+ return NULL;
+ }
+
+ /* if the caller doesn't strictly need 8.3 then just check for illegal
+ filenames */
+ if (!need83 && is_legal_name(ctx, name)) {
+ return NULL;
+ }
+ }
+
+ /* find the '.' if any */
+ dot_p = strrchr(name, '.');
+
+ if (dot_p) {
+ /* if the extension contains any illegal characters or
+ is too long or zero length then we treat it as part
+ of the prefix */
+ for (i=0; i<4 && dot_p[i+1]; i++) {
+ if (! FLAG_CHECK(dot_p[i+1], FLAG_ASCII)) {
+ dot_p = NULL;
+ break;
+ }
+ }
+ if (i == 0 || i == 4) dot_p = NULL;
+ }
+
+ /* the leading characters in the mangled name is taken from
+ the first characters of the name, if they are ascii otherwise
+ '_' is used
+ */
+ for (i=0;i<ctx->mangle_prefix && name[i];i++) {
+ lead_chars[i] = name[i];
+ if (! FLAG_CHECK(lead_chars[i], FLAG_ASCII)) {
+ lead_chars[i] = '_';
+ }
+ lead_chars[i] = toupper((unsigned char)lead_chars[i]);
+ }
+ for (;i<ctx->mangle_prefix;i++) {
+ lead_chars[i] = '_';
+ }
+
+ /* the prefix is anything up to the first dot */
+ if (dot_p) {
+ prefix_len = PTR_DIFF(dot_p, name);
+ } else {
+ prefix_len = strlen(name);
+ }
+
+ /* the extension of the mangled name is taken from the first 3
+ ascii chars after the dot */
+ extension_length = 0;
+ if (dot_p) {
+ for (i=1; extension_length < 3 && dot_p[i]; i++) {
+ unsigned char c = dot_p[i];
+ if (FLAG_CHECK(c, FLAG_ASCII)) {
+ extension[extension_length++] = toupper(c);
+ }
+ }
+ }
+
+ /* find the hash for this prefix */
+ v = hash = mangle_hash(ctx, name, prefix_len);
+
+ new_name = talloc_array(ctx, char, 13);
+ if (new_name == NULL) {
+ return NULL;
+ }
+
+ /* now form the mangled name. */
+ for (i=0;i<ctx->mangle_prefix;i++) {
+ new_name[i] = lead_chars[i];
+ }
+ new_name[7] = basechars[v % 36];
+ new_name[6] = '~';
+ for (i=5; i>=ctx->mangle_prefix; i--) {
+ v = v / 36;
+ new_name[i] = basechars[v % 36];
+ }
+
+ /* add the extension */
+ if (extension_length) {
+ new_name[8] = '.';
+ memcpy(&new_name[9], extension, extension_length);
+ new_name[9+extension_length] = 0;
+ } else {
+ new_name[8] = 0;
+ }
+
+ if (cache83) {
+ /* put it in the cache */
+ cache_insert(ctx, name, prefix_len, hash);
+ }
+
+ M_DEBUG(10,("name_map: %s -> %08X -> %s (cache=%d)\n",
+ name, hash, new_name, cache83));
+
+ return new_name;
+}
+
+
+/* initialise the flags table
+
+ we allow only a very restricted set of characters as 'ascii' in this
+ mangling backend. This isn't a significant problem as modern clients
+ use the 'long' filenames anyway, and those don't have these
+ restrictions.
+*/
+static void init_tables(struct pvfs_mangle_context *ctx)
+{
+ const char *basechars = MANGLE_BASECHARS;
+ int i;
+ /* the list of reserved dos names - all of these are illegal */
+
+ ZERO_STRUCT(ctx->char_flags);
+
+ for (i=1;i<128;i++) {
+ if ((i >= '0' && i <= '9') ||
+ (i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z')) {
+ ctx->char_flags[i] |= (FLAG_ASCII | FLAG_BASECHAR);
+ }
+ if (strchr("_-$~", i)) {
+ ctx->char_flags[i] |= FLAG_ASCII;
+ }
+
+ if (strchr("*\\/?<>|\":", i)) {
+ ctx->char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if (strchr("*?\"<>", i)) {
+ ctx->char_flags[i] |= FLAG_WILDCARD;
+ }
+ }
+
+ ZERO_STRUCT(ctx->base_reverse);
+ for (i=0;i<36;i++) {
+ ctx->base_reverse[(uint8_t)basechars[i]] = i;
+ }
+
+ /* fill in the reserved names flags. These are used as a very
+ fast filter for finding possible DOS reserved filenames */
+ for (i=0; reserved_names[i]; i++) {
+ unsigned char c1, c2, c3, c4;
+
+ c1 = (unsigned char)reserved_names[i][0];
+ c2 = (unsigned char)reserved_names[i][1];
+ c3 = (unsigned char)reserved_names[i][2];
+ c4 = (unsigned char)reserved_names[i][3];
+
+ ctx->char_flags[c1] |= FLAG_POSSIBLE1;
+ ctx->char_flags[c2] |= FLAG_POSSIBLE2;
+ ctx->char_flags[c3] |= FLAG_POSSIBLE3;
+ ctx->char_flags[c4] |= FLAG_POSSIBLE4;
+ ctx->char_flags[tolower(c1)] |= FLAG_POSSIBLE1;
+ ctx->char_flags[tolower(c2)] |= FLAG_POSSIBLE2;
+ ctx->char_flags[tolower(c3)] |= FLAG_POSSIBLE3;
+ ctx->char_flags[tolower(c4)] |= FLAG_POSSIBLE4;
+
+ ctx->char_flags[(unsigned char)'.'] |= FLAG_POSSIBLE4;
+ }
+
+ ctx->mangle_modulus = 1;
+ for (i=0;i<(7-ctx->mangle_prefix);i++) {
+ ctx->mangle_modulus *= 36;
+ }
+}
+
+/*
+ initialise the mangling code
+ */
+NTSTATUS pvfs_mangle_init(struct pvfs_state *pvfs)
+{
+ struct pvfs_mangle_context *ctx;
+
+ ctx = talloc(pvfs, struct pvfs_mangle_context);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* by default have a max of 512 entries in the cache. */
+ ctx->cache_size = lpcfg_parm_int(pvfs->ntvfs->ctx->lp_ctx, NULL, "mangle", "cachesize", 512);
+
+ ctx->prefix_cache = talloc_array(ctx, char *, ctx->cache_size);
+ if (ctx->prefix_cache == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ctx->prefix_cache_hashes = talloc_array(ctx, uint32_t, ctx->cache_size);
+ if (ctx->prefix_cache_hashes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memset(ctx->prefix_cache, 0, sizeof(char *) * ctx->cache_size);
+ memset(ctx->prefix_cache_hashes, 0, sizeof(uint32_t) * ctx->cache_size);
+
+ ctx->mangle_prefix = lpcfg_parm_int(pvfs->ntvfs->ctx->lp_ctx, NULL, "mangle", "prefix", -1);
+ if (ctx->mangle_prefix < 0 || ctx->mangle_prefix > 6) {
+ ctx->mangle_prefix = DEFAULT_MANGLE_PREFIX;
+ }
+
+ init_tables(ctx);
+
+ pvfs->mangle_ctx = ctx;
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ return the short name for a component of a full name
+*/
+char *pvfs_short_name_component(struct pvfs_state *pvfs, const char *name)
+{
+ return name_map(pvfs->mangle_ctx, name, true, true);
+}
+
+
+/*
+ return the short name for a given entry in a directory
+*/
+const char *pvfs_short_name(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ struct pvfs_filename *name)
+{
+ char *p = strrchr(name->full_name, '/');
+ char *ret = pvfs_short_name_component(pvfs, p+1);
+ if (ret == NULL) {
+ return p+1;
+ }
+ talloc_steal(mem_ctx, ret);
+ return ret;
+}
+
+/*
+ lookup a mangled name, returning the original long name if present
+ in the cache
+*/
+char *pvfs_mangled_lookup(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
+ const char *name)
+{
+ return check_cache(pvfs->mangle_ctx, mem_ctx, name);
+}
+
+
+/*
+ look for a DOS reserved name
+*/
+bool pvfs_is_reserved_name(struct pvfs_state *pvfs, const char *name)
+{
+ return is_reserved_name(pvfs->mangle_ctx, name);
+}
+
+
+/*
+ see if a component of a filename could be a mangled name from our
+ mangling code
+*/
+bool pvfs_is_mangled_component(struct pvfs_state *pvfs, const char *name)
+{
+ return is_mangled_component(pvfs->mangle_ctx, name, strlen(name));
+}
diff --git a/source4/ntvfs/posix/pvfs_streams.c b/source4/ntvfs/posix/pvfs_streams.c
new file mode 100644
index 0000000..9210237
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_streams.c
@@ -0,0 +1,556 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - alternate data streams
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/xattr.h"
+
+/*
+ normalise a stream name, removing a :$DATA suffix if there is one
+ Note: this returns the existing pointer to the name if the name does
+ not need normalising
+ */
+static const char *stream_name_normalise(TALLOC_CTX *ctx, const char *name)
+{
+ const char *c = strchr_m(name, ':');
+ if (c == NULL || strcasecmp_m(c, ":$DATA") != 0) {
+ return name;
+ }
+ return talloc_strndup(ctx, name, c-name);
+}
+
+/*
+ compare two stream names, taking account of the default $DATA extension
+ */
+static int stream_name_cmp(const char *name1, const char *name2)
+{
+ const char *c1, *c2;
+ int l1, l2, ret;
+ c1 = strchr_m(name1, ':');
+ c2 = strchr_m(name2, ':');
+
+ /* check the first part is the same */
+ l1 = c1?(c1 - name1):strlen(name1);
+ l2 = c2?(c2 - name2):strlen(name2);
+ if (l1 != l2) {
+ return l1 - l2;
+ }
+ ret = strncasecmp_m(name1, name2, l1);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* the first parts are the same, check the suffix */
+ if (c1 && c2) {
+ return strcasecmp_m(c1, c2);
+ }
+
+ if (c1) {
+ return strcasecmp_m(c1, ":$DATA");
+ }
+ if (c2) {
+ return strcasecmp_m(c2, ":$DATA");
+ }
+
+ /* neither names have a suffix */
+ return 0;
+}
+
+
+/*
+ return the list of file streams for RAW_FILEINFO_STREAM_INFORMATION
+*/
+NTSTATUS pvfs_stream_information(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ struct pvfs_filename *name, int fd,
+ struct stream_information *info)
+{
+ struct xattr_DosStreams *streams;
+ int i;
+ NTSTATUS status;
+
+ /* directories don't have streams */
+ if (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ info->num_streams = 0;
+ info->streams = NULL;
+ return NT_STATUS_OK;
+ }
+
+ streams = talloc(mem_ctx, struct xattr_DosStreams);
+ if (streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_streams_load(pvfs, name, fd, streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCTP(streams);
+ }
+
+ info->num_streams = streams->num_streams+1;
+ info->streams = talloc_array(mem_ctx, struct stream_struct, info->num_streams);
+ if (!info->streams) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->streams[0].size = name->st.st_size;
+ info->streams[0].alloc_size = name->dos.alloc_size;
+ info->streams[0].stream_name.s = talloc_strdup(info->streams, "::$DATA");
+
+ for (i=0;i<streams->num_streams;i++) {
+ info->streams[i+1].size = streams->streams[i].size;
+ info->streams[i+1].alloc_size = streams->streams[i].alloc_size;
+ if (strchr(streams->streams[i].name, ':') == NULL) {
+ info->streams[i+1].stream_name.s = talloc_asprintf(streams->streams,
+ ":%s:$DATA",
+ streams->streams[i].name);
+ } else {
+ info->streams[i+1].stream_name.s = talloc_strdup(streams->streams,
+ streams->streams[i].name);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ fill in the stream information for a name
+*/
+NTSTATUS pvfs_stream_info(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd)
+{
+ struct xattr_DosStreams *streams;
+ int i;
+ NTSTATUS status;
+
+ /* the NULL stream always exists */
+ if (name->stream_name == NULL) {
+ name->stream_exists = true;
+ return NT_STATUS_OK;
+ }
+
+ streams = talloc(name, struct xattr_DosStreams);
+ if (streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_streams_load(pvfs, name, fd, streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(streams);
+ return status;
+ }
+
+ for (i=0;i<streams->num_streams;i++) {
+ struct xattr_DosStream *s = &streams->streams[i];
+ if (stream_name_cmp(s->name, name->stream_name) == 0) {
+ name->dos.alloc_size = pvfs_round_alloc_size(pvfs, s->alloc_size);
+ name->st.st_size = s->size;
+ name->stream_exists = true;
+ talloc_free(streams);
+ return NT_STATUS_OK;
+ }
+ }
+
+ talloc_free(streams);
+
+ name->dos.alloc_size = 0;
+ name->st.st_size = 0;
+ name->stream_exists = false;
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ update size information for a stream
+*/
+static NTSTATUS pvfs_stream_update_size(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ off_t size)
+{
+ struct xattr_DosStreams *streams;
+ int i;
+ NTSTATUS status;
+
+ streams = talloc(name, struct xattr_DosStreams);
+ if (streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_streams_load(pvfs, name, fd, streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCTP(streams);
+ }
+
+ for (i=0;i<streams->num_streams;i++) {
+ struct xattr_DosStream *s = &streams->streams[i];
+ if (stream_name_cmp(s->name, name->stream_name) == 0) {
+ s->size = size;
+ s->alloc_size = pvfs_round_alloc_size(pvfs, size);
+ break;
+ }
+ }
+
+ if (i == streams->num_streams) {
+ struct xattr_DosStream *s;
+ streams->streams = talloc_realloc(streams, streams->streams,
+ struct xattr_DosStream,
+ streams->num_streams+1);
+ if (streams->streams == NULL) {
+ talloc_free(streams);
+ return NT_STATUS_NO_MEMORY;
+ }
+ streams->num_streams++;
+ s = &streams->streams[i];
+
+ s->flags = XATTR_STREAM_FLAG_INTERNAL;
+ s->size = size;
+ s->alloc_size = pvfs_round_alloc_size(pvfs, size);
+ s->name = stream_name_normalise(streams, name->stream_name);
+ if (s->name == NULL) {
+ talloc_free(streams);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = pvfs_streams_save(pvfs, name, fd, streams);
+ talloc_free(streams);
+
+ return status;
+}
+
+
+/*
+ rename a stream
+*/
+NTSTATUS pvfs_stream_rename(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ const char *new_name, bool overwrite)
+{
+ struct xattr_DosStreams *streams;
+ int i, found_old, found_new;
+ NTSTATUS status;
+
+ streams = talloc(name, struct xattr_DosStreams);
+ if (streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ new_name = stream_name_normalise(streams, new_name);
+ if (new_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_streams_load(pvfs, name, fd, streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCTP(streams);
+ }
+
+ /* the default stream always exists */
+ if (strcmp(new_name, "") == 0 ||
+ strcasecmp_m(new_name, ":$DATA") == 0) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ /* try to find the old/new names in the list */
+ found_old = found_new = -1;
+ for (i=0;i<streams->num_streams;i++) {
+ struct xattr_DosStream *s = &streams->streams[i];
+ if (stream_name_cmp(s->name, new_name) == 0) {
+ found_new = i;
+ }
+ if (stream_name_cmp(s->name, name->stream_name) == 0) {
+ found_old = i;
+ }
+ }
+
+ if (found_old == -1) {
+ talloc_free(streams);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (found_new == -1) {
+ /* a simple rename */
+ struct xattr_DosStream *s = &streams->streams[found_old];
+ s->name = new_name;
+ } else {
+ if (!overwrite) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+ if (found_old != found_new) {
+ /* remove the old one and replace with the new one */
+ streams->streams[found_old].name = new_name;
+ memmove(&streams->streams[found_new],
+ &streams->streams[found_new+1],
+ sizeof(streams->streams[0]) *
+ (streams->num_streams - (found_new+1)));
+ streams->num_streams--;
+ }
+ }
+
+ status = pvfs_streams_save(pvfs, name, fd, streams);
+
+ if (NT_STATUS_IS_OK(status)) {
+
+ /* update the in-memory copy of the name of the open file */
+ talloc_free(name->stream_name);
+ name->stream_name = talloc_strdup(name, new_name);
+
+ talloc_free(streams);
+ }
+
+ return status;
+}
+
+
+/*
+ create the xattr for a alternate data stream
+*/
+NTSTATUS pvfs_stream_create(struct pvfs_state *pvfs,
+ struct pvfs_filename *name,
+ int fd)
+{
+ NTSTATUS status;
+ status = pvfs_xattr_create(pvfs, name->full_name, fd,
+ XATTR_DOSSTREAM_PREFIX, name->stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return pvfs_stream_update_size(pvfs, name, fd, 0);
+}
+
+/*
+ delete the xattr for a alternate data stream
+*/
+NTSTATUS pvfs_stream_delete(struct pvfs_state *pvfs,
+ struct pvfs_filename *name,
+ int fd)
+{
+ NTSTATUS status;
+ struct xattr_DosStreams *streams;
+ int i;
+
+ status = pvfs_xattr_delete(pvfs, name->full_name, fd,
+ XATTR_DOSSTREAM_PREFIX, name->stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ streams = talloc(name, struct xattr_DosStreams);
+ if (streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_streams_load(pvfs, name, fd, streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(streams);
+ return status;
+ }
+
+ for (i=0;i<streams->num_streams;i++) {
+ struct xattr_DosStream *s = &streams->streams[i];
+ if (stream_name_cmp(s->name, name->stream_name) == 0) {
+ memmove(s, s+1, (streams->num_streams - (i+1)) * sizeof(*s));
+ streams->num_streams--;
+ break;
+ }
+ }
+
+ status = pvfs_streams_save(pvfs, name, fd, streams);
+ talloc_free(streams);
+
+ return status;
+}
+
+/*
+ load a stream into a blob
+*/
+static NTSTATUS pvfs_stream_load(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ struct pvfs_filename *name,
+ int fd,
+ size_t estimated_size,
+ DATA_BLOB *blob)
+{
+ NTSTATUS status;
+
+ status = pvfs_xattr_load(pvfs, mem_ctx, name->full_name, fd,
+ XATTR_DOSSTREAM_PREFIX,
+ name->stream_name, estimated_size, blob);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ /* try with a case insensitive match */
+ struct xattr_DosStreams *streams;
+ int i;
+
+ streams = talloc(mem_ctx, struct xattr_DosStreams);
+ if (streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pvfs_streams_load(pvfs, name, fd, streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(streams);
+ return NT_STATUS_NOT_FOUND;
+ }
+ for (i=0;i<streams->num_streams;i++) {
+ struct xattr_DosStream *s = &streams->streams[i];
+ if (stream_name_cmp(s->name, name->stream_name) == 0) {
+ status = pvfs_xattr_load(pvfs, mem_ctx, name->full_name, fd,
+ XATTR_DOSSTREAM_PREFIX,
+ s->name, estimated_size, blob);
+ talloc_free(streams);
+ return status;
+ }
+ }
+ talloc_free(streams);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ return status;
+}
+
+/*
+ the equivalent of pread() on a stream
+*/
+ssize_t pvfs_stream_read(struct pvfs_state *pvfs,
+ struct pvfs_file_handle *h, void *data, size_t count, off_t offset)
+{
+ NTSTATUS status;
+ DATA_BLOB blob;
+ if (count == 0) {
+ return 0;
+ }
+ status = pvfs_stream_load(pvfs, h, h->name, h->fd, offset+count, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = EIO;
+ return -1;
+ }
+ if (offset >= blob.length) {
+ data_blob_free(&blob);
+ return 0;
+ }
+ if (count > blob.length - offset) {
+ count = blob.length - offset;
+ }
+ memcpy(data, blob.data + offset, count);
+ data_blob_free(&blob);
+ return count;
+}
+
+
+/*
+ the equivalent of pwrite() on a stream
+*/
+ssize_t pvfs_stream_write(struct pvfs_state *pvfs,
+ struct pvfs_file_handle *h, const void *data, size_t count, off_t offset)
+{
+ NTSTATUS status;
+ DATA_BLOB blob;
+ if (count == 0) {
+ return 0;
+ }
+
+ if (count+offset > XATTR_MAX_STREAM_SIZE) {
+ if (!pvfs->ea_db || count+offset > XATTR_MAX_STREAM_SIZE_TDB) {
+ errno = ENOSPC;
+ return -1;
+ }
+ }
+
+ /* we have to load the existing stream, then modify, then save */
+ status = pvfs_stream_load(pvfs, h, h->name, h->fd, offset+count, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ blob = data_blob(NULL, 0);
+ }
+ if (count+offset > blob.length) {
+ blob.data = talloc_realloc(blob.data, blob.data, uint8_t, count+offset);
+ if (blob.data == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ if (offset > blob.length) {
+ memset(blob.data+blob.length, 0, offset - blob.length);
+ }
+ blob.length = count+offset;
+ }
+ memcpy(blob.data + offset, data, count);
+
+ status = pvfs_xattr_save(pvfs, h->name->full_name, h->fd, XATTR_DOSSTREAM_PREFIX,
+ h->name->stream_name, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(&blob);
+ /* getting this error mapping right is probably
+ not worth it */
+ errno = ENOSPC;
+ return -1;
+ }
+
+ status = pvfs_stream_update_size(pvfs, h->name, h->fd, blob.length);
+
+ data_blob_free(&blob);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = EIO;
+ return -1;
+ }
+
+ return count;
+}
+
+/*
+ the equivalent of truncate() on a stream
+*/
+NTSTATUS pvfs_stream_truncate(struct pvfs_state *pvfs,
+ struct pvfs_filename *name, int fd, off_t length)
+{
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ if (length > XATTR_MAX_STREAM_SIZE) {
+ if (!pvfs->ea_db || length > XATTR_MAX_STREAM_SIZE_TDB) {
+ return NT_STATUS_DISK_FULL;
+ }
+ }
+
+ /* we have to load the existing stream, then modify, then save */
+ status = pvfs_stream_load(pvfs, name, name, fd, length, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (length <= blob.length) {
+ blob.length = length;
+ } else if (length > blob.length) {
+ blob.data = talloc_realloc(blob.data, blob.data, uint8_t, length);
+ if (blob.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memset(blob.data+blob.length, 0, length - blob.length);
+ blob.length = length;
+ }
+
+ status = pvfs_xattr_save(pvfs, name->full_name, fd, XATTR_DOSSTREAM_PREFIX,
+ name->stream_name, &blob);
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = pvfs_stream_update_size(pvfs, name, fd, blob.length);
+ }
+ data_blob_free(&blob);
+
+ return status;
+}
diff --git a/source4/ntvfs/posix/pvfs_sys.c b/source4/ntvfs/posix/pvfs_sys.c
new file mode 100644
index 0000000..32770c1
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_sys.c
@@ -0,0 +1,662 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - pvfs_sys wrappers
+
+ Copyright (C) Andrew Tridgell 2010
+ Copyright (C) Andrew Bartlett 2010
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "../lib/util/unix_privs.h"
+
+/*
+ these wrapper functions must only be called when the appropriate ACL
+ has already been checked. The wrappers will override a EACCES result
+ by gaining root privileges if the 'pvfs:perm override' is set on the
+ share (it is enabled by default)
+
+ Careful use of O_NOFOLLOW and O_DIRECTORY is used to prevent
+ security attacks via symlinks
+ */
+
+
+struct pvfs_sys_ctx {
+ struct pvfs_state *pvfs;
+ void *privs;
+ const char *old_wd;
+ struct stat st_orig;
+};
+
+
+/*
+ we create PVFS_NOFOLLOW and PVFS_DIRECTORY as aliases for O_NOFOLLOW
+ and O_DIRECTORY on systems that have them. On systems that don't
+ have O_NOFOLLOW we are less safe, but the root override code is off
+ by default.
+ */
+#ifdef O_NOFOLLOW
+#define PVFS_NOFOLLOW O_NOFOLLOW
+#else
+#define PVFS_NOFOLLOW 0
+#endif
+#ifdef O_DIRECTORY
+#define PVFS_DIRECTORY O_DIRECTORY
+#else
+#define PVFS_DIRECTORY 0
+#endif
+
+/*
+ return to original directory when context is destroyed
+ */
+static int pvfs_sys_pushdir_destructor(struct pvfs_sys_ctx *ctx)
+{
+ struct stat st;
+
+ if (ctx->old_wd == NULL) {
+ return 0;
+ }
+
+ if (chdir(ctx->old_wd) != 0) {
+ smb_panic("Failed to restore working directory");
+ }
+ if (stat(".", &st) != 0) {
+ smb_panic("Failed to stat working directory");
+ }
+ if (st.st_ino != ctx->st_orig.st_ino ||
+ st.st_dev != ctx->st_orig.st_dev) {
+ smb_panic("Working directory changed during call");
+ }
+
+ return 0;
+}
+
+
+/*
+ chdir() to the directory part of a pathname, but disallow any
+ component with a symlink
+
+ Note that we can't use O_NOFOLLOW on the whole path as that only
+ prevents links in the final component of the path
+ */
+static int pvfs_sys_chdir_nosymlink(struct pvfs_sys_ctx *ctx, const char *pathname)
+{
+ char *p, *path;
+ size_t base_len = strlen(ctx->pvfs->base_directory);
+
+ /* don't check for symlinks in the base directory of the share */
+ if (strncmp(ctx->pvfs->base_directory, pathname, base_len) == 0 &&
+ pathname[base_len] == '/') {
+ if (chdir(ctx->pvfs->base_directory) != 0) {
+ return -1;
+ }
+ pathname += base_len + 1;
+ }
+
+ path = talloc_strdup(ctx, pathname);
+ if (path == NULL) {
+ return -1;
+ }
+ while ((p = strchr(path, '/'))) {
+ int fd;
+ struct stat st1, st2;
+ *p = 0;
+ fd = open(path, PVFS_NOFOLLOW | PVFS_DIRECTORY | O_RDONLY);
+ if (fd == -1) {
+ return -1;
+ }
+ if (chdir(path) != 0) {
+ close(fd);
+ return -1;
+ }
+ if (stat(".", &st1) != 0 ||
+ fstat(fd, &st2) != 0) {
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ if (st1.st_ino != st2.st_ino ||
+ st1.st_dev != st2.st_dev) {
+ DEBUG(0,(__location__ ": Inode changed during chdir in '%s' - symlink attack?\n",
+ pathname));
+ return -1;
+ }
+ path = p + 1;
+ }
+
+ return 0;
+}
+
+
+/*
+ become root, and change directory to the directory component of a
+ path. Return a talloc context which when freed will move us back
+ to the original directory, and return us to the original uid
+
+ change the pathname argument to contain just the base component of
+ the path
+
+ return NULL on error, which could include an attempt to subvert
+ security using symlink tricks
+ */
+static struct pvfs_sys_ctx *pvfs_sys_pushdir(struct pvfs_state *pvfs,
+ const char **pathname)
+{
+ struct pvfs_sys_ctx *ctx;
+ char *cwd, *p, *dirname;
+ int ret;
+
+ ctx = talloc_zero(pvfs, struct pvfs_sys_ctx);
+ if (ctx == NULL) {
+ return NULL;
+ }
+ ctx->pvfs = pvfs;
+ ctx->privs = root_privileges();
+ if (ctx->privs == NULL) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ talloc_steal(ctx, ctx->privs);
+
+ if (!pathname) {
+ /* no pathname needed */
+ return ctx;
+ }
+
+ p = strrchr(*pathname, '/');
+ if (p == NULL) {
+ /* we don't need to change directory */
+ return ctx;
+ }
+
+ /* we keep the old st around, so we can tell that
+ we have come back to the right directory */
+ if (stat(".", &ctx->st_orig) != 0) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ cwd = get_current_dir_name();
+ if (cwd == NULL) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ ctx->old_wd = talloc_strdup(ctx, cwd);
+ free(cwd);
+
+ if (ctx->old_wd == NULL) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ dirname = talloc_strndup(ctx, *pathname, (p - *pathname));
+ if (dirname == NULL) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ ret = pvfs_sys_chdir_nosymlink(ctx, *pathname);
+ if (ret == -1) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ talloc_set_destructor(ctx, pvfs_sys_pushdir_destructor);
+
+ /* return the basename as the filename that should be operated on */
+ (*pathname) = talloc_strdup(ctx, p+1);
+ if (! *pathname) {
+ talloc_free(ctx);
+ return NULL;
+ }
+
+ return ctx;
+}
+
+
+/*
+ chown a file that we created with a root privileges override
+ */
+static int pvfs_sys_fchown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, int fd)
+{
+ return fchown(fd, root_privileges_original_uid(ctx->privs), -1);
+}
+
+/*
+ chown a directory that we created with a root privileges override
+ */
+static int pvfs_sys_chown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, const char *name)
+{
+ /* to avoid symlink hacks, we need to use fchown() on a directory fd */
+ int ret, fd;
+ fd = open(name, PVFS_DIRECTORY | PVFS_NOFOLLOW | O_RDONLY);
+ if (fd == -1) {
+ return -1;
+ }
+ ret = pvfs_sys_fchown(pvfs, ctx, fd);
+ close(fd);
+ return ret;
+}
+
+
+/*
+ wrap open for system override
+*/
+int pvfs_sys_open(struct pvfs_state *pvfs, const char *filename, int flags, mode_t mode, bool allow_override)
+{
+ int fd, ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+ int retries = 5;
+
+ orig_errno = errno;
+
+ fd = open(filename, flags, mode);
+ if (fd != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return fd;
+ }
+
+ saved_errno = errno;
+ ctx = pvfs_sys_pushdir(pvfs, &filename);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ /* don't allow permission overrides to follow links */
+ flags |= PVFS_NOFOLLOW;
+
+ /*
+ if O_CREAT was specified and O_EXCL was not specified
+ then initially do the open without O_CREAT, as in that case
+ we know that we did not create the file, so we don't have
+ to fchown it
+ */
+ if ((flags & O_CREAT) && !(flags & O_EXCL)) {
+ try_again:
+ fd = open(filename, flags & ~O_CREAT, mode);
+ /* if this open succeeded, or if it failed
+ with anything other than ENOENT, then we return the
+ open result, with the original errno */
+ if (fd == -1 && errno != ENOENT) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+ if (fd != -1) {
+ /* the file already existed and we opened it */
+ talloc_free(ctx);
+ errno = orig_errno;
+ return fd;
+ }
+
+ fd = open(filename, flags | O_EXCL, mode);
+ if (fd == -1 && errno != EEXIST) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+ if (fd != -1) {
+ /* we created the file, we need to set the
+ right ownership on it */
+ ret = pvfs_sys_fchown(pvfs, ctx, fd);
+ if (ret == -1) {
+ close(fd);
+ unlink(filename);
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+ talloc_free(ctx);
+ errno = orig_errno;
+ return fd;
+ }
+
+ /* the file got created between the two times
+ we tried to open it! Try again */
+ if (retries-- > 0) {
+ goto try_again;
+ }
+
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ fd = open(filename, flags, mode);
+ if (fd == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ /* if we have created a file then fchown it */
+ if (flags & O_CREAT) {
+ ret = pvfs_sys_fchown(pvfs, ctx, fd);
+ if (ret == -1) {
+ close(fd);
+ unlink(filename);
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ talloc_free(ctx);
+ return fd;
+}
+
+
+/*
+ wrap unlink for system override
+*/
+int pvfs_sys_unlink(struct pvfs_state *pvfs, const char *filename, bool allow_override)
+{
+ int ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+
+ orig_errno = errno;
+
+ ret = unlink(filename);
+ if (ret != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return ret;
+ }
+
+ saved_errno = errno;
+
+ ctx = pvfs_sys_pushdir(pvfs, &filename);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = unlink(filename);
+ if (ret == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ talloc_free(ctx);
+ errno = orig_errno;
+ return ret;
+}
+
+
+static bool contains_symlink(const char *path)
+{
+ int fd = open(path, PVFS_NOFOLLOW | O_RDONLY);
+ int posix_errno = errno;
+ if (fd != -1) {
+ close(fd);
+ return false;
+ }
+
+#if defined(ENOTSUP) && defined(OSF1)
+ /* handle special Tru64 errno */
+ if (errno == ENOTSUP) {
+ posix_errno = ELOOP;
+ }
+#endif /* ENOTSUP */
+
+#ifdef EFTYPE
+ /* fix broken NetBSD errno */
+ if (errno == EFTYPE) {
+ posix_errno = ELOOP;
+ }
+#endif /* EFTYPE */
+
+ /* fix broken FreeBSD errno */
+ if (errno == EMLINK) {
+ posix_errno = ELOOP;
+ }
+
+ return (posix_errno == ELOOP);
+}
+
+/*
+ wrap rename for system override
+*/
+int pvfs_sys_rename(struct pvfs_state *pvfs, const char *name1, const char *name2, bool allow_override)
+{
+ int ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+
+ orig_errno = errno;
+
+ ret = rename(name1, name2);
+ if (ret != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return ret;
+ }
+
+ saved_errno = errno;
+
+ ctx = pvfs_sys_pushdir(pvfs, &name1);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ /* we need the destination as an absolute path */
+ if (name2[0] != '/') {
+ name2 = talloc_asprintf(ctx, "%s/%s", ctx->old_wd, name2);
+ if (name2 == NULL) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ /* make sure the destination isn't a symlink beforehand */
+ if (contains_symlink(name2)) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = rename(name1, name2);
+ if (ret == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ /* make sure the destination isn't a symlink afterwards */
+ if (contains_symlink(name2)) {
+ DEBUG(0,(__location__ ": Possible symlink attack in rename to '%s' - unlinking\n", name2));
+ unlink(name2);
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ talloc_free(ctx);
+ errno = orig_errno;
+ return ret;
+}
+
+
+/*
+ wrap mkdir for system override
+*/
+int pvfs_sys_mkdir(struct pvfs_state *pvfs, const char *dirname, mode_t mode, bool allow_override)
+{
+ int ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+
+ orig_errno = errno;
+
+ ret = mkdir(dirname, mode);
+ if (ret != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return ret;
+ }
+
+ saved_errno = errno;
+ ctx = pvfs_sys_pushdir(pvfs, &dirname);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = mkdir(dirname, mode);
+ if (ret == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = pvfs_sys_chown(pvfs, ctx, dirname);
+ if (ret == -1) {
+ rmdir(dirname);
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ talloc_free(ctx);
+ errno = orig_errno;
+ return ret;
+}
+
+
+/*
+ wrap rmdir for system override
+*/
+int pvfs_sys_rmdir(struct pvfs_state *pvfs, const char *dirname, bool allow_override)
+{
+ int ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+
+ orig_errno = errno;
+
+ ret = rmdir(dirname);
+ if (ret != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return ret;
+ }
+
+ saved_errno = errno;
+
+ ctx = pvfs_sys_pushdir(pvfs, &dirname);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = rmdir(dirname);
+ if (ret == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ talloc_free(ctx);
+ errno = orig_errno;
+ return ret;
+}
+
+/*
+ wrap fchmod for system override
+*/
+int pvfs_sys_fchmod(struct pvfs_state *pvfs, int fd, mode_t mode, bool allow_override)
+{
+ int ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+
+ orig_errno = errno;
+
+ ret = fchmod(fd, mode);
+ if (ret != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return ret;
+ }
+
+ saved_errno = errno;
+
+ ctx = pvfs_sys_pushdir(pvfs, NULL);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = fchmod(fd, mode);
+ if (ret == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ talloc_free(ctx);
+ errno = orig_errno;
+ return ret;
+}
+
+
+/*
+ wrap chmod for system override
+*/
+int pvfs_sys_chmod(struct pvfs_state *pvfs, const char *filename, mode_t mode, bool allow_override)
+{
+ int ret;
+ struct pvfs_sys_ctx *ctx;
+ int saved_errno, orig_errno;
+
+ orig_errno = errno;
+
+ ret = chmod(filename, mode);
+ if (ret != -1 ||
+ !allow_override ||
+ errno != EACCES) {
+ return ret;
+ }
+
+ saved_errno = errno;
+
+ ctx = pvfs_sys_pushdir(pvfs, &filename);
+ if (ctx == NULL) {
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = chmod(filename, mode);
+ if (ret == -1) {
+ talloc_free(ctx);
+ errno = saved_errno;
+ return -1;
+ }
+
+ talloc_free(ctx);
+ errno = orig_errno;
+ return ret;
+}
diff --git a/source4/ntvfs/posix/pvfs_unlink.c b/source4/ntvfs/posix/pvfs_unlink.c
new file mode 100644
index 0000000..605ec10
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_unlink.c
@@ -0,0 +1,276 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - unlink
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "system/dir.h"
+
+/*
+ retry an open after a sharing violation
+*/
+static void pvfs_retry_unlink(struct pvfs_odb_retry *r,
+ struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ void *_io,
+ void *private_data,
+ enum pvfs_wait_notice reason)
+{
+ union smb_unlink *io = talloc_get_type(_io, union smb_unlink);
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+
+ talloc_free(r);
+
+ switch (reason) {
+ case PVFS_WAIT_CANCEL:
+/*TODO*/
+ status = NT_STATUS_CANCELLED;
+ break;
+ case PVFS_WAIT_TIMEOUT:
+ /* if it timed out, then give the failure
+ immediately */
+/*TODO*/
+ status = NT_STATUS_SHARING_VIOLATION;
+ break;
+ case PVFS_WAIT_EVENT:
+
+ /* try the open again, which could trigger another retry setup
+ if it wants to, so we have to unmark the async flag so we
+ will know if it does a second async reply */
+ req->async_states->state &= ~NTVFS_ASYNC_STATE_ASYNC;
+
+ status = pvfs_unlink(ntvfs, req, io);
+ if (req->async_states->state & NTVFS_ASYNC_STATE_ASYNC) {
+ /* the 2nd try also replied async, so we don't send
+ the reply yet */
+ return;
+ }
+
+ /* re-mark it async, just in case someone up the chain does
+ paranoid checking */
+ req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+ break;
+ }
+
+ /* send the reply up the chain */
+ req->async_states->status = status;
+ req->async_states->send_fn(req);
+}
+
+/*
+ setup for a unlink retry after a sharing violation
+ or a non granted oplock
+*/
+static NTSTATUS pvfs_unlink_setup_retry(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_unlink *io,
+ struct odb_lock *lck,
+ NTSTATUS status)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct timeval end_time;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ 0, pvfs->sharing_violation_delay);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ end_time = timeval_add(&req->statistics.request_time,
+ pvfs->oplock_break_timeout, 0);
+ } else {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return pvfs_odb_retry_setup(ntvfs, req, lck, end_time, io, NULL,
+ pvfs_retry_unlink);
+}
+
+
+/*
+ unlink a file
+*/
+static NTSTATUS pvfs_unlink_file(struct pvfs_state *pvfs,
+ struct pvfs_filename *name)
+{
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if (name->st.st_nlink == 1) {
+ status = pvfs_xattr_unlink_hook(pvfs, name->full_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* finally try the actual unlink */
+ if (pvfs_sys_unlink(pvfs, name->full_name, name->allow_override) == -1) {
+ status = pvfs_map_errno(pvfs, errno);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ notify_trigger(pvfs->notify_context,
+ NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ name->full_name);
+ }
+
+ return status;
+}
+
+/*
+ unlink one file
+*/
+static NTSTATUS pvfs_unlink_one(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ union smb_unlink *unl,
+ struct pvfs_filename *name)
+{
+ NTSTATUS status;
+ struct odb_lock *lck = NULL;
+
+ /* make sure its matches the given attributes */
+ status = pvfs_match_attrib(pvfs, name,
+ unl->unlink.in.attrib, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_can_delete(pvfs, req, name, &lck);
+
+ /*
+ * on a sharing violation we need to retry when the file is closed by
+ * the other user, or after 1 second
+ * on a non granted oplock we need to retry when the file is closed by
+ * the other user, or after 30 seconds
+ */
+ if ((NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) &&
+ (req->async_states->state & NTVFS_ASYNC_STATE_MAY_ASYNC)) {
+ return pvfs_unlink_setup_retry(pvfs->ntvfs, req, unl, lck, status);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (name->stream_name) {
+ if (!name->stream_exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return pvfs_stream_delete(pvfs, name, -1);
+ }
+
+ return pvfs_unlink_file(pvfs, name);
+}
+
+/*
+ delete a file - the dirtype specifies the file types to include in the search.
+ The name can contain CIFS wildcards, but rarely does (except with OS/2 clients)
+*/
+NTSTATUS pvfs_unlink(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_unlink *unl)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_dir *dir;
+ NTSTATUS status;
+ uint32_t total_deleted=0;
+ struct pvfs_filename *name;
+ const char *fname;
+ off_t ofs;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, unl->unlink.in.pattern,
+ PVFS_RESOLVE_WILDCARD |
+ PVFS_RESOLVE_STREAMS |
+ PVFS_RESOLVE_NO_OPENDB,
+ &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!name->exists && !name->has_wildcard) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (name->exists &&
+ (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if (!name->has_wildcard) {
+ return pvfs_unlink_one(pvfs, req, unl, name);
+ }
+
+ /*
+ * disable async requests in the wildcard case
+ * until we have proper tests for this
+ */
+ req->async_states->state &= ~NTVFS_ASYNC_STATE_MAY_ASYNC;
+
+ /* get list of matching files */
+ status = pvfs_list_start(pvfs, name, req, &dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = NT_STATUS_NO_SUCH_FILE;
+ talloc_free(name);
+
+ ofs = 0;
+
+ while ((fname = pvfs_list_next(dir, &ofs))) {
+ /* this seems to be a special case */
+ if ((unl->unlink.in.attrib & FILE_ATTRIBUTE_DIRECTORY) &&
+ (ISDOT(fname) || ISDOTDOT(fname))) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ /* get a pvfs_filename object */
+ status = pvfs_resolve_partial(pvfs, req,
+ pvfs_list_unix_path(dir),
+ fname,
+ PVFS_RESOLVE_NO_OPENDB,
+ &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pvfs_unlink_one(pvfs, req, unl, name);
+ if (NT_STATUS_IS_OK(status)) {
+ total_deleted++;
+ }
+
+ talloc_free(name);
+ }
+
+ if (total_deleted > 0) {
+ status = NT_STATUS_OK;
+ }
+
+ return status;
+}
+
+
diff --git a/source4/ntvfs/posix/pvfs_util.c b/source4/ntvfs/posix/pvfs_util.c
new file mode 100644
index 0000000..2975e04
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_util.c
@@ -0,0 +1,206 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+/*
+ utility functions for posix backend
+*/
+
+#include "includes.h"
+#include "vfs_posix.h"
+
+/*
+ return true if a string contains one of the CIFS wildcard characters
+*/
+bool pvfs_has_wildcard(const char *str)
+{
+ if (strpbrk(str, "*?<>\"")) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ map a unix errno to a NTSTATUS
+*/
+NTSTATUS pvfs_map_errno(struct pvfs_state *pvfs, int unix_errno)
+{
+ NTSTATUS status;
+ status = map_nt_error_from_unix_common(unix_errno);
+ DEBUG(10,(__location__ " mapped unix errno %d -> %s\n", unix_errno, nt_errstr(status)));
+ return status;
+}
+
+
+/*
+ check if a filename has an attribute matching the given attribute search value
+ this is used by calls like unlink and search which take an attribute
+ and only include special files if they match the given attribute
+*/
+NTSTATUS pvfs_match_attrib(struct pvfs_state *pvfs, struct pvfs_filename *name,
+ uint32_t attrib, uint32_t must_attrib)
+{
+ if ((name->dos.attrib & ~attrib) & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ if ((name->dos.attrib & ~attrib) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+ if (must_attrib & ~name->dos.attrib) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return NT_STATUS_OK;
+}
+
+
+/*
+ normalise a file attribute
+*/
+uint32_t pvfs_attrib_normalise(uint32_t attrib, mode_t mode)
+{
+ if (attrib != FILE_ATTRIBUTE_NORMAL) {
+ attrib &= ~FILE_ATTRIBUTE_NORMAL;
+ }
+ if (S_ISDIR(mode)) {
+ attrib |= FILE_ATTRIBUTE_DIRECTORY;
+ } else {
+ attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
+ }
+ return attrib;
+}
+
+
+/*
+ copy a file. Caller is supposed to have already ensured that the
+ operation is allowed. The destination file must not exist.
+*/
+NTSTATUS pvfs_copy_file(struct pvfs_state *pvfs,
+ struct pvfs_filename *name1,
+ struct pvfs_filename *name2,
+ bool allow_override)
+{
+ int fd1, fd2;
+ mode_t mode;
+ NTSTATUS status;
+ size_t buf_size = 0x10000;
+ uint8_t *buf = talloc_array(name2, uint8_t, buf_size);
+
+ if (buf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fd1 = pvfs_sys_open(pvfs, name1->full_name, O_RDONLY, 0, allow_override);
+ if (fd1 == -1) {
+ talloc_free(buf);
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ fd2 = pvfs_sys_open(pvfs, name2->full_name, O_CREAT|O_EXCL|O_WRONLY, 0, allow_override);
+ if (fd2 == -1) {
+ close(fd1);
+ talloc_free(buf);
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ while (1) {
+ ssize_t ret2, ret = read(fd1, buf, buf_size);
+ if (ret == -1 &&
+ (errno == EINTR || errno == EAGAIN)) {
+ continue;
+ }
+ if (ret <= 0) break;
+
+ ret2 = write(fd2, buf, ret);
+ if (ret2 == -1 &&
+ (errno == EINTR || errno == EAGAIN)) {
+ continue;
+ }
+
+ if (ret2 != ret) {
+ close(fd1);
+ close(fd2);
+ talloc_free(buf);
+ pvfs_sys_unlink(pvfs, name2->full_name, allow_override);
+ if (ret2 == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+ return NT_STATUS_DISK_FULL;
+ }
+ }
+
+ talloc_free(buf);
+ close(fd1);
+
+ mode = pvfs_fileperms(pvfs, name1->dos.attrib);
+ if (pvfs_sys_fchmod(pvfs, fd2, mode, allow_override) == -1) {
+ status = pvfs_map_errno(pvfs, errno);
+ close(fd2);
+ pvfs_sys_unlink(pvfs, name2->full_name, allow_override);
+ return status;
+ }
+
+ name2->st.st_mode = mode;
+ name2->dos = name1->dos;
+
+ status = pvfs_dosattrib_save(pvfs, name2, fd2);
+ if (!NT_STATUS_IS_OK(status)) {
+ close(fd2);
+ pvfs_sys_unlink(pvfs, name2->full_name, allow_override);
+ return status;
+ }
+
+ close(fd2);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ hash a string of the specified length. The string does not need to be
+ null terminated
+
+ hash algorithm changed to FNV1 by idra@samba.org (Simo Sorce).
+ see http://www.isthe.com/chongo/tech/comp/fnv/index.html for a
+ discussion on Fowler / Noll / Vo (FNV) Hash by one of it's authors
+*/
+uint32_t pvfs_name_hash(const char *key, size_t length)
+{
+ const uint32_t fnv1_prime = 0x01000193;
+ const uint32_t fnv1_init = 0xa6b93095;
+ uint32_t value = fnv1_init;
+
+ while (*key && length--) {
+ size_t c_size;
+ codepoint_t c = next_codepoint(key, &c_size);
+ c = toupper_m(c);
+ value *= fnv1_prime;
+ value ^= (uint32_t)c;
+ key += c_size;
+ }
+
+ return value;
+}
+
+
+/*
+ file allocation size rounding. This is required to pass ifstest
+*/
+uint64_t pvfs_round_alloc_size(struct pvfs_state *pvfs, uint64_t size)
+{
+ const uint32_t round_value = pvfs->alloc_size_rounding;
+ return round_value * ((size + round_value - 1)/round_value);
+}
diff --git a/source4/ntvfs/posix/pvfs_wait.c b/source4/ntvfs/posix/pvfs_wait.c
new file mode 100644
index 0000000..21ff33e
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_wait.c
@@ -0,0 +1,212 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - async request wait routines
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "lib/events/events.h"
+#include "../lib/util/dlinklist.h"
+#include "vfs_posix.h"
+#include "samba/service_stream.h"
+#include "lib/messaging/irpc.h"
+
+/* the context for a single wait instance */
+struct pvfs_wait {
+ struct pvfs_wait *next, *prev;
+ struct pvfs_state *pvfs;
+ void (*handler)(void *, enum pvfs_wait_notice);
+ void *private_data;
+ int msg_type;
+ struct imessaging_context *msg_ctx;
+ struct tevent_context *ev;
+ struct ntvfs_request *req;
+ enum pvfs_wait_notice reason;
+};
+
+/*
+ called from the ntvfs layer when we have requested setup of an async
+ call. this ensures that async calls runs with the right state of
+ previous ntvfs handlers in the chain (such as security context)
+*/
+NTSTATUS pvfs_async_setup(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, void *private_data)
+{
+ struct pvfs_wait *pwait = talloc_get_type(private_data,
+ struct pvfs_wait);
+ pwait->handler(pwait->private_data, pwait->reason);
+ return NT_STATUS_OK;
+}
+
+/*
+ receive a completion message for a wait
+*/
+static void pvfs_wait_dispatch(struct imessaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ size_t num_fds,
+ int *fds,
+ DATA_BLOB *data)
+{
+ struct pvfs_wait *pwait = talloc_get_type(private_data,
+ struct pvfs_wait);
+ struct ntvfs_request *req;
+ void *p = NULL;
+
+ if (num_fds != 0) {
+ DBG_WARNING("Received %zu fds, ignoring message\n", num_fds);
+ return;
+ }
+
+ /* we need to check that this one is for us. See
+ imessaging_send_ptr() for the other side of this.
+ */
+ if (data->length == sizeof(void *)) {
+ void **pp;
+ pp = (void **)data->data;
+ p = *pp;
+ }
+ if (p == NULL || p != pwait->private_data) {
+ return;
+ }
+
+ pwait->reason = PVFS_WAIT_EVENT;
+
+ /* the extra reference here is to ensure that the req
+ structure is not destroyed when the async request reply is
+ sent, which would cause problems with the other ntvfs
+ modules above us */
+ req = talloc_reference(msg, pwait->req);
+ ntvfs_async_setup(pwait->req, pwait);
+ talloc_unlink(msg, req);
+}
+
+
+/*
+ receive a timeout on a message wait
+*/
+static void pvfs_wait_timeout(struct tevent_context *ev,
+ struct tevent_timer *te, struct timeval t,
+ void *private_data)
+{
+ struct pvfs_wait *pwait = talloc_get_type(private_data,
+ struct pvfs_wait);
+ struct ntvfs_request *req = pwait->req;
+
+ pwait->reason = PVFS_WAIT_TIMEOUT;
+
+ req = talloc_reference(ev, req);
+ if (req != NULL) {
+ ntvfs_async_setup(req, pwait);
+ talloc_unlink(ev, req);
+ }
+}
+
+
+/*
+ destroy a pending wait
+ */
+static int pvfs_wait_destructor(struct pvfs_wait *pwait)
+{
+ if (pwait->msg_type != -1) {
+ imessaging_deregister(pwait->msg_ctx, pwait->msg_type, pwait);
+ }
+ DLIST_REMOVE(pwait->pvfs->wait_list, pwait);
+ return 0;
+}
+
+/*
+ setup a request to wait on a message of type msg_type, with a
+ timeout (given as an expiry time)
+
+ the return value is a handle. To stop waiting talloc_free this
+ handle.
+
+ if msg_type == -1 then no message is registered, and it is assumed
+ that the caller handles any messaging setup needed
+*/
+struct pvfs_wait *pvfs_wait_message(struct pvfs_state *pvfs,
+ struct ntvfs_request *req,
+ int msg_type,
+ struct timeval end_time,
+ void (*fn)(void *, enum pvfs_wait_notice),
+ void *private_data)
+{
+ struct pvfs_wait *pwait;
+
+ pwait = talloc(pvfs, struct pvfs_wait);
+ if (pwait == NULL) {
+ return NULL;
+ }
+
+ pwait->private_data = private_data;
+ pwait->handler = fn;
+ pwait->msg_ctx = pvfs->ntvfs->ctx->msg_ctx;
+ pwait->ev = pvfs->ntvfs->ctx->event_ctx;
+ pwait->msg_type = msg_type;
+ pwait->req = talloc_reference(pwait, req);
+ pwait->pvfs = pvfs;
+
+ if (!timeval_is_zero(&end_time)) {
+ /* setup a timer */
+ tevent_add_timer(pwait->ev, pwait, end_time, pvfs_wait_timeout, pwait);
+ }
+
+ /* register with the messaging subsystem for this message
+ type */
+ if (msg_type != -1) {
+ imessaging_register(pwait->msg_ctx,
+ pwait,
+ msg_type,
+ pvfs_wait_dispatch);
+ }
+
+ /* tell the main smb server layer that we will be replying
+ asynchronously */
+ req->async_states->state |= NTVFS_ASYNC_STATE_ASYNC;
+
+ DLIST_ADD(pvfs->wait_list, pwait);
+
+ /* make sure we cleanup the timer and message handler */
+ talloc_set_destructor(pwait, pvfs_wait_destructor);
+
+ return pwait;
+}
+
+
+/*
+ cancel an outstanding async request
+*/
+NTSTATUS pvfs_cancel(struct ntvfs_module_context *ntvfs, struct ntvfs_request *req)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_wait *pwait;
+
+ for (pwait=pvfs->wait_list;pwait;pwait=pwait->next) {
+ if (pwait->req == req) {
+ /* trigger a cancel on the request */
+ pwait->reason = PVFS_WAIT_CANCEL;
+ ntvfs_async_setup(pwait->req, pwait);
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_DOS(ERRDOS, ERRcancelviolation);
+}
diff --git a/source4/ntvfs/posix/pvfs_write.c b/source4/ntvfs/posix/pvfs_write.c
new file mode 100644
index 0000000..9773325
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_write.c
@@ -0,0 +1,145 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - write
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/security.h"
+#include "lib/events/events.h"
+
+static void pvfs_write_time_update_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval tv,
+ void *private_data)
+{
+ struct pvfs_file_handle *h = talloc_get_type(private_data,
+ struct pvfs_file_handle);
+ struct odb_lock *lck;
+ NTSTATUS status;
+ NTTIME write_time;
+
+ lck = odb_lock(h, h->pvfs->odb_context, &h->odb_locking_key);
+ if (lck == NULL) {
+ DEBUG(0,("Unable to lock opendb for write time update\n"));
+ return;
+ }
+
+ write_time = timeval_to_nttime(&tv);
+
+ status = odb_set_write_time(lck, write_time, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to update write time: %s\n",
+ nt_errstr(status)));
+ return;
+ }
+
+ talloc_free(lck);
+
+ h->write_time.update_event = NULL;
+}
+
+static void pvfs_trigger_write_time_update(struct pvfs_file_handle *h)
+{
+ struct pvfs_state *pvfs = h->pvfs;
+ struct timeval tv;
+
+ if (h->write_time.update_triggered) {
+ return;
+ }
+
+ tv = timeval_current_ofs_usec(pvfs->writetime_delay);
+
+ h->write_time.update_triggered = true;
+ h->write_time.update_on_close = true;
+ h->write_time.update_event = tevent_add_timer(pvfs->ntvfs->ctx->event_ctx,
+ h, tv,
+ pvfs_write_time_update_handler,
+ h);
+ if (!h->write_time.update_event) {
+ DEBUG(0,("Failed tevent_add_timer\n"));
+ }
+}
+
+/*
+ write to a file
+*/
+NTSTATUS pvfs_write(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_write *wr)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ ssize_t ret;
+ struct pvfs_file *f;
+ NTSTATUS status;
+
+ if (wr->generic.level != RAW_WRITE_WRITEX) {
+ return ntvfs_map_write(ntvfs, req, wr);
+ }
+
+ f = pvfs_find_fd(pvfs, req, wr->writex.in.file.ntvfs);
+ if (!f) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (f->handle->fd == -1) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ if (!(f->access_mask & (SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pvfs_check_lock(pvfs, f, req->smbpid,
+ wr->writex.in.offset,
+ wr->writex.in.count,
+ WRITE_LOCK);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = pvfs_break_level2_oplocks(f);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ pvfs_trigger_write_time_update(f->handle);
+
+ if (f->handle->name->stream_name) {
+ ret = pvfs_stream_write(pvfs,
+ f->handle,
+ wr->writex.in.data,
+ wr->writex.in.count,
+ wr->writex.in.offset);
+ } else {
+ ret = pwrite(f->handle->fd,
+ wr->writex.in.data,
+ wr->writex.in.count,
+ wr->writex.in.offset);
+ }
+ if (ret == -1) {
+ if (errno == EFBIG) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ f->handle->seek_offset = wr->writex.in.offset + ret;
+
+ wr->writex.out.nwritten = ret;
+ wr->writex.out.remaining = 0; /* should fill this in? */
+
+ return NT_STATUS_OK;
+}
diff --git a/source4/ntvfs/posix/pvfs_xattr.c b/source4/ntvfs/posix/pvfs_xattr.c
new file mode 100644
index 0000000..ab88d89
--- /dev/null
+++ b/source4/ntvfs/posix/pvfs_xattr.c
@@ -0,0 +1,488 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - xattr support
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+#include "../lib/util/unix_privs.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "param/param.h"
+#include "ntvfs/posix/posix_eadb_proto.h"
+
+/*
+ pull a xattr as a blob
+*/
+static NTSTATUS pull_xattr_blob(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ size_t estimated_size,
+ DATA_BLOB *blob)
+{
+ NTSTATUS status;
+
+ if (pvfs->ea_db) {
+ return pull_xattr_blob_tdb(pvfs, mem_ctx, attr_name, fname,
+ fd, estimated_size, blob);
+ }
+
+ status = pull_xattr_blob_system(pvfs, mem_ctx, attr_name, fname,
+ fd, estimated_size, blob);
+
+ /* if the filesystem doesn't support them, then tell pvfs not to try again */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)||
+ NT_STATUS_EQUAL(status, NT_STATUS_INVALID_SYSTEM_SERVICE)) {
+ DEBUG(2,("pvfs_xattr: xattr not supported in filesystem: %s\n", nt_errstr(status)));
+ pvfs->flags &= ~PVFS_FLAG_XATTR_ENABLE;
+ status = NT_STATUS_NOT_FOUND;
+ }
+
+ return status;
+}
+
+/*
+ push a xattr as a blob
+*/
+static NTSTATUS push_xattr_blob(struct pvfs_state *pvfs,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ const DATA_BLOB *blob)
+{
+ if (pvfs->ea_db) {
+ return push_xattr_blob_tdb(pvfs, attr_name, fname, fd, blob);
+ }
+ return push_xattr_blob_system(pvfs, attr_name, fname, fd, blob);
+}
+
+
+/*
+ delete a xattr
+*/
+static NTSTATUS delete_xattr(struct pvfs_state *pvfs, const char *attr_name,
+ const char *fname, int fd)
+{
+ if (pvfs->ea_db) {
+ return delete_posix_eadb(pvfs, attr_name, fname, fd);
+ }
+ return delete_xattr_system(pvfs, attr_name, fname, fd);
+}
+
+/*
+ a hook called on unlink - allows the tdb xattr backend to cleanup
+*/
+NTSTATUS pvfs_xattr_unlink_hook(struct pvfs_state *pvfs, const char *fname)
+{
+ if (pvfs->ea_db) {
+ return unlink_posix_eadb(pvfs, fname);
+ }
+ return unlink_xattr_system(pvfs, fname);
+}
+
+
+/*
+ load a NDR structure from a xattr
+*/
+NTSTATUS pvfs_xattr_ndr_load(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ const char *fname, int fd, const char *attr_name,
+ void *p, void *pull_fn)
+{
+ NTSTATUS status;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+
+ status = pull_xattr_blob(pvfs, mem_ctx, attr_name, fname,
+ fd, XATTR_DOSATTRIB_ESTIMATED_SIZE, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* pull the blob */
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, p,
+ (ndr_pull_flags_fn_t)pull_fn);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ data_blob_free(&blob);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ save a NDR structure into a xattr
+*/
+NTSTATUS pvfs_xattr_ndr_save(struct pvfs_state *pvfs,
+ const char *fname, int fd, const char *attr_name,
+ void *p, void *push_fn)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ DATA_BLOB blob;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, p, (ndr_push_flags_fn_t)push_fn);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(mem_ctx);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ status = push_xattr_blob(pvfs, attr_name, fname, fd, &blob);
+ talloc_free(mem_ctx);
+
+ return status;
+}
+
+
+/*
+ fill in file attributes from extended attributes
+*/
+NTSTATUS pvfs_dosattrib_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd)
+{
+ NTSTATUS status;
+ struct xattr_DosAttrib attrib;
+ TALLOC_CTX *mem_ctx = talloc_new(name);
+ struct xattr_DosInfo1 *info1;
+ struct xattr_DosInfo2Old *info2;
+
+ if (name->stream_name != NULL) {
+ name->stream_exists = false;
+ } else {
+ name->stream_exists = true;
+ }
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+
+ status = pvfs_xattr_ndr_load(pvfs, mem_ctx, name->full_name,
+ fd, XATTR_DOSATTRIB_NAME,
+ &attrib,
+ (void *) ndr_pull_xattr_DosAttrib);
+
+ /* not having a DosAttrib is not an error */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ talloc_free(mem_ctx);
+ return pvfs_stream_info(pvfs, name, fd);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(mem_ctx);
+ return status;
+ }
+
+ switch (attrib.version) {
+ case 1:
+ info1 = &attrib.info.info1;
+ name->dos.attrib = pvfs_attrib_normalise(info1->attrib,
+ name->st.st_mode);
+ name->dos.ea_size = info1->ea_size;
+ if (name->st.st_size == info1->size) {
+ name->dos.alloc_size =
+ pvfs_round_alloc_size(pvfs, info1->alloc_size);
+ }
+ if (!null_nttime(info1->create_time)) {
+ name->dos.create_time = info1->create_time;
+ }
+ if (!null_nttime(info1->change_time)) {
+ name->dos.change_time = info1->change_time;
+ }
+ name->dos.flags = 0;
+ break;
+
+ case 2:
+ /*
+ * Note: This is only used to parse existing values from disk
+ * We use xattr_DosInfo1 again for storing new values
+ */
+ info2 = &attrib.info.oldinfo2;
+ name->dos.attrib = pvfs_attrib_normalise(info2->attrib,
+ name->st.st_mode);
+ name->dos.ea_size = info2->ea_size;
+ if (name->st.st_size == info2->size) {
+ name->dos.alloc_size =
+ pvfs_round_alloc_size(pvfs, info2->alloc_size);
+ }
+ if (!null_nttime(info2->create_time)) {
+ name->dos.create_time = info2->create_time;
+ }
+ if (!null_nttime(info2->change_time)) {
+ name->dos.change_time = info2->change_time;
+ }
+ name->dos.flags = info2->flags;
+ break;
+
+ default:
+ DEBUG(0,("ERROR: Unsupported xattr DosAttrib version %d on '%s'\n",
+ attrib.version, name->full_name));
+ talloc_free(mem_ctx);
+ return NT_STATUS_INVALID_LEVEL;
+ }
+ talloc_free(mem_ctx);
+
+ status = pvfs_stream_info(pvfs, name, fd);
+
+ return status;
+}
+
+
+/*
+ save the file attribute into the xattr
+*/
+NTSTATUS pvfs_dosattrib_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd)
+{
+ struct xattr_DosAttrib attrib;
+ struct xattr_DosInfo1 *info1;
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+
+ attrib.version = 1;
+ info1 = &attrib.info.info1;
+
+ name->dos.attrib = pvfs_attrib_normalise(name->dos.attrib, name->st.st_mode);
+
+ info1->attrib = name->dos.attrib;
+ info1->ea_size = name->dos.ea_size;
+ info1->size = name->st.st_size;
+ info1->alloc_size = name->dos.alloc_size;
+ info1->create_time = name->dos.create_time;
+ info1->change_time = name->dos.change_time;
+
+ return pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
+ XATTR_DOSATTRIB_NAME, &attrib,
+ (void *) ndr_push_xattr_DosAttrib);
+}
+
+
+/*
+ load the set of DOS EAs
+*/
+NTSTATUS pvfs_doseas_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct xattr_DosEAs *eas)
+{
+ NTSTATUS status;
+ ZERO_STRUCTP(eas);
+
+ if (name->stream_name) {
+ /* We don't support EAs on streams */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+ status = pvfs_xattr_ndr_load(pvfs, eas, name->full_name, fd, XATTR_DOSEAS_NAME,
+ eas, (void *) ndr_pull_xattr_DosEAs);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_OK;
+ }
+ return status;
+}
+
+/*
+ save the set of DOS EAs
+*/
+NTSTATUS pvfs_doseas_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct xattr_DosEAs *eas)
+{
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+ return pvfs_xattr_ndr_save(pvfs, name->full_name, fd, XATTR_DOSEAS_NAME, eas,
+ (void *) ndr_push_xattr_DosEAs);
+}
+
+
+/*
+ load the set of streams from extended attributes
+*/
+NTSTATUS pvfs_streams_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct xattr_DosStreams *streams)
+{
+ NTSTATUS status;
+ ZERO_STRUCTP(streams);
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+ status = pvfs_xattr_ndr_load(pvfs, streams, name->full_name, fd,
+ XATTR_DOSSTREAMS_NAME,
+ streams,
+ (void *) ndr_pull_xattr_DosStreams);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_OK;
+ }
+ return status;
+}
+
+/*
+ save the set of streams into filesystem xattr
+*/
+NTSTATUS pvfs_streams_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct xattr_DosStreams *streams)
+{
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+ return pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
+ XATTR_DOSSTREAMS_NAME,
+ streams,
+ (void *) ndr_push_xattr_DosStreams);
+}
+
+
+/*
+ load the current ACL from extended attributes
+*/
+NTSTATUS pvfs_acl_load(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct xattr_NTACL *acl)
+{
+ NTSTATUS status;
+ ZERO_STRUCTP(acl);
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ status = pvfs_xattr_ndr_load(pvfs, acl, name->full_name, fd,
+ XATTR_NTACL_NAME,
+ acl,
+ (void *) ndr_pull_xattr_NTACL);
+ return status;
+}
+
+/*
+ save the acl for a file into filesystem xattr
+*/
+NTSTATUS pvfs_acl_save(struct pvfs_state *pvfs, struct pvfs_filename *name, int fd,
+ struct xattr_NTACL *acl)
+{
+ NTSTATUS status;
+ void *privs;
+
+ if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
+ return NT_STATUS_OK;
+ }
+
+ /* this xattr is in the "system" namespace, so we need
+ admin privileges to set it */
+ privs = root_privileges();
+ status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
+ XATTR_NTACL_NAME,
+ acl,
+ (void *) ndr_push_xattr_NTACL);
+ talloc_free(privs);
+ return status;
+}
+
+/*
+ create a zero length xattr with the given name
+*/
+NTSTATUS pvfs_xattr_create(struct pvfs_state *pvfs,
+ const char *fname, int fd,
+ const char *attr_prefix,
+ const char *attr_name)
+{
+ NTSTATUS status;
+ DATA_BLOB blob = data_blob(NULL, 0);
+ char *aname = talloc_asprintf(NULL, "%s%s", attr_prefix, attr_name);
+ if (aname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = push_xattr_blob(pvfs, aname, fname, fd, &blob);
+ talloc_free(aname);
+ return status;
+}
+
+
+/*
+ delete a xattr with the given name
+*/
+NTSTATUS pvfs_xattr_delete(struct pvfs_state *pvfs,
+ const char *fname, int fd,
+ const char *attr_prefix,
+ const char *attr_name)
+{
+ NTSTATUS status;
+ char *aname = talloc_asprintf(NULL, "%s%s", attr_prefix, attr_name);
+ if (aname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = delete_xattr(pvfs, aname, fname, fd);
+ talloc_free(aname);
+ return status;
+}
+
+/*
+ load a xattr with the given name
+*/
+NTSTATUS pvfs_xattr_load(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ const char *fname, int fd,
+ const char *attr_prefix,
+ const char *attr_name,
+ size_t estimated_size,
+ DATA_BLOB *blob)
+{
+ NTSTATUS status;
+ char *aname = talloc_asprintf(mem_ctx, "%s%s", attr_prefix, attr_name);
+ if (aname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = pull_xattr_blob(pvfs, mem_ctx, aname, fname, fd, estimated_size, blob);
+ talloc_free(aname);
+ return status;
+}
+
+/*
+ save a xattr with the given name
+*/
+NTSTATUS pvfs_xattr_save(struct pvfs_state *pvfs,
+ const char *fname, int fd,
+ const char *attr_prefix,
+ const char *attr_name,
+ const DATA_BLOB *blob)
+{
+ NTSTATUS status;
+ char *aname = talloc_asprintf(NULL, "%s%s", attr_prefix, attr_name);
+ if (aname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = push_xattr_blob(pvfs, aname, fname, fd, blob);
+ talloc_free(aname);
+ return status;
+}
+
+
+/*
+ probe for system support for xattrs
+*/
+void pvfs_xattr_probe(struct pvfs_state *pvfs)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(pvfs);
+ DATA_BLOB blob;
+ pull_xattr_blob(pvfs, tmp_ctx, "user.XattrProbe", pvfs->base_directory,
+ -1, 1, &blob);
+ pull_xattr_blob(pvfs, tmp_ctx, "security.XattrProbe", pvfs->base_directory,
+ -1, 1, &blob);
+ talloc_free(tmp_ctx);
+}
diff --git a/source4/ntvfs/posix/python/pyposix_eadb.c b/source4/ntvfs/posix/python/pyposix_eadb.c
new file mode 100644
index 0000000..aca92e7
--- /dev/null
+++ b/source4/ntvfs/posix/python/pyposix_eadb.c
@@ -0,0 +1,140 @@
+/*
+ Unix SMB/CIFS implementation. Xattr manipulation bindings.
+ Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+ Base on work of pyglue.c by Jelmer Vernooij <jelmer@samba.org> 2007 and
+ Matthias Dieter Wallnöfer 2009
+
+ 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 "lib/replace/system/python.h"
+#include "python/py3compat.h"
+#include "includes.h"
+#include "system/filesys.h"
+#include <tdb.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "librpc/ndr/libndr.h"
+#include "ntvfs/posix/posix_eadb.h"
+#include "libcli/util/pyerrors.h"
+#include "param/pyparam.h"
+
+static PyObject *py_is_xattr_supported(PyObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ Py_RETURN_TRUE;
+}
+
+static PyObject *py_wrap_setxattr(PyObject *self, PyObject *args)
+{
+ char *filename, *attribute, *tdbname;
+ DATA_BLOB blob;
+ Py_ssize_t blobsize;
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx;
+ struct tdb_wrap *eadb;
+
+ if (!PyArg_ParseTuple(args, "sss"PYARG_BYTES_LEN, &tdbname, &filename, &attribute,
+ &blob.data, &blobsize))
+ return NULL;
+
+ blob.length = blobsize;
+ mem_ctx = talloc_new(NULL);
+ eadb = tdb_wrap_open(
+ mem_ctx, tdbname, 50000,
+ lpcfg_tdb_flags(py_default_loadparm_context(mem_ctx),
+ TDB_DEFAULT),
+ O_RDWR|O_CREAT, 0600);
+
+ if (eadb == NULL) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ status = push_xattr_blob_tdb_raw(eadb, attribute, filename, -1,
+ &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ talloc_free(mem_ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_wrap_getxattr(PyObject *self, PyObject *args)
+{
+ char *filename, *attribute, *tdbname;
+ TALLOC_CTX *mem_ctx;
+ DATA_BLOB blob;
+ PyObject *ret;
+ NTSTATUS status;
+ struct tdb_wrap *eadb = NULL;
+
+ if (!PyArg_ParseTuple(args, "sss", &tdbname, &filename, &attribute))
+ return NULL;
+
+ mem_ctx = talloc_new(NULL);
+ eadb = tdb_wrap_open(
+ mem_ctx, tdbname, 50000,
+ lpcfg_tdb_flags(py_default_loadparm_context(mem_ctx),
+ TDB_DEFAULT),
+ O_RDWR|O_CREAT, 0600);
+ if (eadb == NULL) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ status = pull_xattr_blob_tdb_raw(eadb, mem_ctx, attribute, filename,
+ -1, 100, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ ret = Py_BuildValue(PYARG_BYTES_LEN, blob.data, blob.length);
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static PyMethodDef py_posix_eadb_methods[] = {
+ { "wrap_getxattr", (PyCFunction)py_wrap_getxattr, METH_VARARGS,
+ "wrap_getxattr(filename,attribute) -> blob\n"
+ "Retrieve given attribute on the given file." },
+ { "wrap_setxattr", (PyCFunction)py_wrap_setxattr, METH_VARARGS,
+ "wrap_setxattr(filename,attribute,value)\n"
+ "Set the given attribute to the given value on the given file." },
+ { "is_xattr_supported", (PyCFunction)py_is_xattr_supported, METH_NOARGS,
+ "Return true if xattr are supported on this system\n"},
+ {0}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "posix_eadb",
+ .m_doc = "Python bindings for xattr manipulation.",
+ .m_size = -1,
+ .m_methods = py_posix_eadb_methods,
+};
+
+MODULE_INIT_FUNC(posix_eadb)
+{
+ PyObject *m;
+
+ m = PyModule_Create(&moduledef);
+
+ if (m == NULL)
+ return NULL;
+
+ return m;
+}
diff --git a/source4/ntvfs/posix/python/pyxattr_native.c b/source4/ntvfs/posix/python/pyxattr_native.c
new file mode 100644
index 0000000..6b237b0
--- /dev/null
+++ b/source4/ntvfs/posix/python/pyxattr_native.c
@@ -0,0 +1,129 @@
+/*
+ Unix SMB/CIFS implementation. Xattr manipulation bindings.
+ Copyright (C) Matthieu Patou <mat@matws.net> 2009
+ Base on work of pyglue.c by Jelmer Vernooij <jelmer@samba.org> 2007 and
+ Matthias Dieter Wallnöfer 2009
+
+ 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 "lib/replace/system/python.h"
+#include "python/py3compat.h"
+#include "includes.h"
+#include "librpc/ndr/libndr.h"
+#include "system/filesys.h"
+#include "lib/util/base64.h"
+
+static PyObject *py_is_xattr_supported(PyObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+#if !defined(HAVE_XATTR_SUPPORT)
+ Py_RETURN_FALSE;
+#else
+ Py_RETURN_TRUE;
+#endif
+}
+
+static PyObject *py_wrap_setxattr(PyObject *self, PyObject *args)
+{
+ char *filename, *attribute;
+ int ret = 0;
+ Py_ssize_t blobsize;
+ DATA_BLOB blob;
+
+ if (!PyArg_ParseTuple(args, "ss"PYARG_BYTES_LEN, &filename, &attribute, &blob.data,
+ &blobsize))
+ return NULL;
+
+ blob.length = blobsize;
+ ret = setxattr(filename, attribute, blob.data, blob.length, 0);
+ if( ret < 0 ) {
+ if (errno == ENOTSUP) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ } else {
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+ }
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_wrap_getxattr(PyObject *self, PyObject *args)
+{
+ char *filename, *attribute;
+ int len;
+ TALLOC_CTX *mem_ctx;
+ char *buf;
+ PyObject *ret;
+ if (!PyArg_ParseTuple(args, "ss", &filename, &attribute))
+ return NULL;
+ mem_ctx = talloc_new(NULL);
+ len = getxattr(filename,attribute,NULL,0);
+ if( len < 0 ) {
+ if (errno == ENOTSUP) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ } else {
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+ }
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ /* check length ... */
+ buf = talloc_zero_array(mem_ctx, char, len);
+ len = getxattr(filename, attribute, buf, len);
+ if( len < 0 ) {
+ if (errno == ENOTSUP) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ } else {
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
+ }
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ ret = Py_BuildValue(PYARG_BYTES_LEN, buf, len);
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static PyMethodDef py_xattr_methods[] = {
+ { "wrap_getxattr", (PyCFunction)py_wrap_getxattr, METH_VARARGS,
+ "wrap_getxattr(filename,attribute) -> blob\n"
+ "Retrieve given attribute on the given file." },
+ { "wrap_setxattr", (PyCFunction)py_wrap_setxattr, METH_VARARGS,
+ "wrap_setxattr(filename,attribute,value)\n"
+ "Set the given attribute to the given value on the given file." },
+ { "is_xattr_supported", (PyCFunction)py_is_xattr_supported, METH_NOARGS,
+ "Return true if xattr are supported on this system\n"},
+ {0}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "xattr_native",
+ .m_doc = "Python bindings for xattr manipulation.",
+ .m_size = -1,
+ .m_methods = py_xattr_methods,
+};
+
+MODULE_INIT_FUNC(xattr_native)
+{
+ PyObject *m;
+
+ m = PyModule_Create(&moduledef);
+
+ if (m == NULL)
+ return NULL;
+
+ return m;
+}
diff --git a/source4/ntvfs/posix/python/pyxattr_tdb.c b/source4/ntvfs/posix/python/pyxattr_tdb.c
new file mode 100644
index 0000000..5f07809
--- /dev/null
+++ b/source4/ntvfs/posix/python/pyxattr_tdb.c
@@ -0,0 +1,177 @@
+/*
+ Unix SMB/CIFS implementation. Xattr manipulation bindings.
+ Copyright (C) Matthieu Patou <mat@matws.net> 2009-2010
+ Base on work of pyglue.c by Jelmer Vernooij <jelmer@samba.org> 2007 and
+ Matthias Dieter Wallnöfer 2009
+
+ 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 "lib/replace/system/python.h"
+#include "python/py3compat.h"
+#include "includes.h"
+#include "system/filesys.h"
+#include <tdb.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "librpc/ndr/libndr.h"
+#include "ntvfs/posix/posix_eadb.h"
+#include "libcli/util/pyerrors.h"
+#include "param/pyparam.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/dbwrap/dbwrap_tdb.h"
+#include "source3/lib/xattr_tdb.h"
+
+static PyObject *py_is_xattr_supported(PyObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ Py_RETURN_TRUE;
+}
+
+static PyObject *py_wrap_setxattr(PyObject *self, PyObject *args)
+{
+ char *filename, *attribute, *tdbname;
+ DATA_BLOB blob;
+ Py_ssize_t blobsize;
+ int ret;
+ TALLOC_CTX *mem_ctx;
+ struct loadparm_context *lp_ctx;
+ struct db_context *eadb = NULL;
+ struct file_id id;
+ struct stat sbuf;
+
+ if (!PyArg_ParseTuple(args, "sss"PYARG_BYTES_LEN, &tdbname, &filename, &attribute,
+ &blob.data, &blobsize))
+ return NULL;
+
+ blob.length = blobsize;
+ mem_ctx = talloc_new(NULL);
+
+ lp_ctx = py_default_loadparm_context(mem_ctx);
+ eadb = db_open_tdb(mem_ctx, tdbname, 50000,
+ lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT),
+ O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_2,
+ DBWRAP_FLAG_NONE);
+
+ if (eadb == NULL) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ ret = stat(filename, &sbuf);
+ if (ret < 0) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ ZERO_STRUCT(id);
+ id.devid = sbuf.st_dev;
+ id.inode = sbuf.st_ino;
+
+ ret = xattr_tdb_setattr(eadb, &id, attribute, blob.data, blob.length, 0);
+ if (ret < 0) {
+ PyErr_SetFromErrno(PyExc_TypeError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ talloc_free(mem_ctx);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_wrap_getxattr(PyObject *self, PyObject *args)
+{
+ char *filename, *attribute, *tdbname;
+ TALLOC_CTX *mem_ctx;
+ struct loadparm_context *lp_ctx;
+ DATA_BLOB blob;
+ PyObject *ret_obj;
+ int ret;
+ ssize_t xattr_size;
+ struct db_context *eadb = NULL;
+ struct file_id id;
+ struct stat sbuf;
+
+ if (!PyArg_ParseTuple(args, "sss", &tdbname, &filename, &attribute))
+ return NULL;
+
+ mem_ctx = talloc_new(NULL);
+
+ lp_ctx = py_default_loadparm_context(mem_ctx);
+ eadb = db_open_tdb(mem_ctx, tdbname, 50000,
+ lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT),
+ O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_2,
+ DBWRAP_FLAG_NONE);
+
+ if (eadb == NULL) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ ret = stat(filename, &sbuf);
+ if (ret < 0) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ ZERO_STRUCT(id);
+ id.devid = sbuf.st_dev;
+ id.inode = sbuf.st_ino;
+
+ xattr_size = xattr_tdb_getattr(eadb, mem_ctx, &id, attribute, &blob);
+ if (xattr_size < 0) {
+ PyErr_SetFromErrno(PyExc_TypeError);
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ ret_obj = Py_BuildValue(PYARG_BYTES_LEN, blob.data, xattr_size);
+ talloc_free(mem_ctx);
+ return ret_obj;
+}
+
+static PyMethodDef py_xattr_methods[] = {
+ { "wrap_getxattr", (PyCFunction)py_wrap_getxattr, METH_VARARGS,
+ "wrap_getxattr(filename,attribute) -> blob\n"
+ "Retrieve given attribute on the given file." },
+ { "wrap_setxattr", (PyCFunction)py_wrap_setxattr, METH_VARARGS,
+ "wrap_setxattr(filename,attribute,value)\n"
+ "Set the given attribute to the given value on the given file." },
+ { "is_xattr_supported", (PyCFunction)py_is_xattr_supported, METH_NOARGS,
+ "Return true if xattr are supported on this system\n"},
+ {0}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "xattr_tdb",
+ .m_doc = "Python bindings for xattr manipulation.",
+ .m_size = -1,
+ .m_methods = py_xattr_methods,
+};
+
+MODULE_INIT_FUNC(xattr_tdb)
+{
+ PyObject *m;
+
+ m = PyModule_Create(&moduledef);
+
+ if (m == NULL)
+ return NULL;
+
+ return m;
+}
+
diff --git a/source4/ntvfs/posix/vfs_posix.c b/source4/ntvfs/posix/vfs_posix.c
new file mode 100644
index 0000000..8375def
--- /dev/null
+++ b/source4/ntvfs/posix/vfs_posix.c
@@ -0,0 +1,426 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+/*
+ this implements most of the POSIX NTVFS backend
+ This is the default backend
+*/
+
+#include "includes.h"
+#include "vfs_posix.h"
+#include "librpc/gen_ndr/security.h"
+#include <tdb.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "libcli/security/security.h"
+#include "lib/events/events.h"
+#include "param/param.h"
+#include "lib/util/idtree.h"
+
+/*
+ setup config options for a posix share
+*/
+static void pvfs_setup_options(struct pvfs_state *pvfs)
+{
+ struct share_config *scfg = pvfs->ntvfs->ctx->config;
+ char *eadb;
+ char *xattr_backend;
+ bool def_perm_override = false;
+
+ if (share_bool_option(scfg, SHARE_MAP_HIDDEN, SHARE_MAP_HIDDEN_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_MAP_HIDDEN;
+ if (share_bool_option(scfg, SHARE_MAP_ARCHIVE, SHARE_MAP_ARCHIVE_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_MAP_ARCHIVE;
+ if (share_bool_option(scfg, SHARE_MAP_SYSTEM, SHARE_MAP_SYSTEM_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_MAP_SYSTEM;
+ if (share_bool_option(scfg, SHARE_READONLY, SHARE_READONLY_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_READONLY;
+ if (share_bool_option(scfg, SHARE_STRICT_SYNC, SHARE_STRICT_SYNC_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_STRICT_SYNC;
+ if (share_bool_option(scfg, SHARE_STRICT_LOCKING, SHARE_STRICT_LOCKING_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_STRICT_LOCKING;
+ if (share_bool_option(scfg, SHARE_CI_FILESYSTEM, SHARE_CI_FILESYSTEM_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_CI_FILESYSTEM;
+ if (share_bool_option(scfg, PVFS_FAKE_OPLOCKS, PVFS_FAKE_OPLOCKS_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_FAKE_OPLOCKS;
+
+#if defined(O_DIRECTORY) && defined(O_NOFOLLOW)
+ /* set PVFS_PERM_OVERRIDE by default only if the system
+ * supports the necessary capabilities to make it secure
+ */
+ def_perm_override = true;
+#endif
+ if (share_bool_option(scfg, PVFS_PERM_OVERRIDE, def_perm_override))
+ pvfs->flags |= PVFS_FLAG_PERM_OVERRIDE;
+
+ /* file perm options */
+ pvfs->options.create_mask = share_int_option(scfg,
+ SHARE_CREATE_MASK,
+ SHARE_CREATE_MASK_DEFAULT);
+ pvfs->options.dir_mask = share_int_option(scfg,
+ SHARE_DIR_MASK,
+ SHARE_DIR_MASK_DEFAULT);
+ pvfs->options.force_dir_mode = share_int_option(scfg,
+ SHARE_FORCE_DIR_MODE,
+ SHARE_FORCE_DIR_MODE_DEFAULT);
+ pvfs->options.force_create_mode = share_int_option(scfg,
+ SHARE_FORCE_CREATE_MODE,
+ SHARE_FORCE_CREATE_MODE_DEFAULT);
+ /* this must be a power of 2 */
+ pvfs->alloc_size_rounding = share_int_option(scfg,
+ PVFS_ALLOCATION_ROUNDING,
+ PVFS_ALLOCATION_ROUNDING_DEFAULT);
+
+ pvfs->search.inactivity_time = share_int_option(scfg,
+ PVFS_SEARCH_INACTIVITY,
+ PVFS_SEARCH_INACTIVITY_DEFAULT);
+
+#ifdef HAVE_XATTR_SUPPORT
+ if (share_bool_option(scfg, PVFS_XATTR, PVFS_XATTR_DEFAULT))
+ pvfs->flags |= PVFS_FLAG_XATTR_ENABLE;
+#endif
+
+ pvfs->sharing_violation_delay = share_int_option(scfg,
+ PVFS_SHARE_DELAY,
+ PVFS_SHARE_DELAY_DEFAULT);
+
+ pvfs->oplock_break_timeout = share_int_option(scfg,
+ PVFS_OPLOCK_TIMEOUT,
+ PVFS_OPLOCK_TIMEOUT_DEFAULT);
+
+ pvfs->writetime_delay = share_int_option(scfg,
+ PVFS_WRITETIME_DELAY,
+ PVFS_WRITETIME_DELAY_DEFAULT);
+
+ pvfs->share_name = talloc_strdup(pvfs, scfg->name);
+
+ pvfs->fs_attribs =
+ FS_ATTR_CASE_SENSITIVE_SEARCH |
+ FS_ATTR_CASE_PRESERVED_NAMES |
+ FS_ATTR_UNICODE_ON_DISK;
+
+ /* allow xattrs to be stored in a external tdb */
+ eadb = share_string_option(pvfs, scfg, PVFS_EADB, NULL);
+ if (eadb != NULL) {
+ pvfs->ea_db = tdb_wrap_open(
+ pvfs, eadb, 50000,
+ lpcfg_tdb_flags(pvfs->ntvfs->ctx->lp_ctx, TDB_DEFAULT),
+ O_RDWR|O_CREAT, 0600);
+ if (pvfs->ea_db != NULL) {
+ pvfs->flags |= PVFS_FLAG_XATTR_ENABLE;
+ } else {
+ DEBUG(0,("Failed to open eadb '%s' - %s\n",
+ eadb, strerror(errno)));
+ pvfs->flags &= ~PVFS_FLAG_XATTR_ENABLE;
+ }
+ TALLOC_FREE(eadb);
+ }
+
+ if (pvfs->flags & PVFS_FLAG_XATTR_ENABLE) {
+ pvfs->fs_attribs |= FS_ATTR_NAMED_STREAMS;
+ }
+ if (pvfs->flags & PVFS_FLAG_XATTR_ENABLE) {
+ pvfs->fs_attribs |= FS_ATTR_PERSISTANT_ACLS;
+ }
+
+ pvfs->sid_cache.creator_owner = dom_sid_parse_talloc(pvfs, SID_CREATOR_OWNER);
+ pvfs->sid_cache.creator_group = dom_sid_parse_talloc(pvfs, SID_CREATOR_GROUP);
+
+ /* check if the system really supports xattrs */
+ if (pvfs->flags & PVFS_FLAG_XATTR_ENABLE) {
+ pvfs_xattr_probe(pvfs);
+ }
+
+ /* enable an ACL backend */
+ xattr_backend = share_string_option(pvfs, scfg, PVFS_ACL, "xattr");
+ pvfs->acl_ops = pvfs_acl_backend_byname(xattr_backend);
+ TALLOC_FREE(xattr_backend);
+}
+
+static int pvfs_state_destructor(struct pvfs_state *pvfs)
+{
+ struct pvfs_file *f, *fn;
+ struct pvfs_search_state *s, *sn;
+
+ /*
+ * make sure we cleanup files and searches before anything else
+ * because there destructors need to access the pvfs_state struct
+ */
+ for (f=pvfs->files.list; f; f=fn) {
+ fn = f->next;
+ talloc_free(f);
+ }
+
+ for (s=pvfs->search.list; s; s=sn) {
+ sn = s->next;
+ talloc_free(s);
+ }
+
+ return 0;
+}
+
+/*
+ connect to a share - used when a tree_connect operation comes
+ in. For a disk based backend we needs to ensure that the base
+ directory exists (tho it doesn't need to be accessible by the user,
+ that comes later)
+*/
+static NTSTATUS pvfs_connect(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_tcon* tcon)
+{
+ struct pvfs_state *pvfs;
+ struct stat st;
+ char *base_directory;
+ NTSTATUS status;
+ const char *sharename;
+
+ switch (tcon->generic.level) {
+ case RAW_TCON_TCON:
+ sharename = tcon->tcon.in.service;
+ break;
+ case RAW_TCON_TCONX:
+ sharename = tcon->tconx.in.path;
+ break;
+ case RAW_TCON_SMB2:
+ sharename = tcon->smb2.in.path;
+ break;
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (strncmp(sharename, "\\\\", 2) == 0) {
+ char *p = strchr(sharename+2, '\\');
+ if (p) {
+ sharename = p + 1;
+ }
+ }
+
+ /*
+ * TODO: call this from ntvfs_posix_init()
+ * but currently we don't have a lp_ctx there
+ */
+ status = pvfs_acl_init();
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ pvfs = talloc_zero(ntvfs, struct pvfs_state);
+ NT_STATUS_HAVE_NO_MEMORY(pvfs);
+
+ /* for simplicity of path construction, remove any trailing slash now */
+ base_directory = share_string_option(pvfs, ntvfs->ctx->config, SHARE_PATH, "");
+ NT_STATUS_HAVE_NO_MEMORY(base_directory);
+ if (strcmp(base_directory, "/") != 0) {
+ trim_string(base_directory, NULL, "/");
+ }
+
+ pvfs->ntvfs = ntvfs;
+ pvfs->base_directory = base_directory;
+
+ /* the directory must exist. Note that we deliberately don't
+ check that it is readable */
+ if (stat(pvfs->base_directory, &st) != 0 || !S_ISDIR(st.st_mode)) {
+ DEBUG(0,("pvfs_connect: '%s' is not a directory, when connecting to [%s]\n",
+ pvfs->base_directory, sharename));
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+
+ ntvfs->ctx->fs_type = talloc_strdup(ntvfs->ctx, "NTFS");
+ NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->fs_type);
+
+ ntvfs->ctx->dev_type = talloc_strdup(ntvfs->ctx, "A:");
+ NT_STATUS_HAVE_NO_MEMORY(ntvfs->ctx->dev_type);
+
+ if (tcon->generic.level == RAW_TCON_TCONX) {
+ tcon->tconx.out.fs_type = ntvfs->ctx->fs_type;
+ tcon->tconx.out.dev_type = ntvfs->ctx->dev_type;
+ }
+
+ ntvfs->private_data = pvfs;
+
+ pvfs->brl_context = brlock_init(pvfs,
+ pvfs->ntvfs->ctx->server_id,
+ pvfs->ntvfs->ctx->lp_ctx,
+ pvfs->ntvfs->ctx->msg_ctx);
+ if (pvfs->brl_context == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ pvfs->odb_context = odb_init(pvfs, pvfs->ntvfs->ctx);
+ if (pvfs->odb_context == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* allow this to be NULL - we just disable change notify */
+ pvfs->notify_context = notify_init(pvfs,
+ pvfs->ntvfs->ctx->server_id,
+ pvfs->ntvfs->ctx->msg_ctx,
+ pvfs->ntvfs->ctx->lp_ctx,
+ pvfs->ntvfs->ctx->event_ctx,
+ pvfs->ntvfs->ctx->config);
+
+ /* allocate the search handle -> ptr tree */
+ pvfs->search.idtree = idr_init(pvfs);
+ NT_STATUS_HAVE_NO_MEMORY(pvfs->search.idtree);
+
+ status = pvfs_mangle_init(pvfs);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ pvfs_setup_options(pvfs);
+
+ talloc_set_destructor(pvfs, pvfs_state_destructor);
+
+#ifdef SIGXFSZ
+ /* who had the stupid idea to generate a signal on a large
+ file write instead of just failing it!? */
+ BlockSignals(true, SIGXFSZ);
+#endif
+
+ return NT_STATUS_OK;
+}
+
+/*
+ disconnect from a share
+*/
+static NTSTATUS pvfs_disconnect(struct ntvfs_module_context *ntvfs)
+{
+ return NT_STATUS_OK;
+}
+
+/*
+ check if a directory exists
+*/
+static NTSTATUS pvfs_chkpath(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req,
+ union smb_chkpath *cp)
+{
+ struct pvfs_state *pvfs = talloc_get_type(ntvfs->private_data,
+ struct pvfs_state);
+ struct pvfs_filename *name;
+ NTSTATUS status;
+
+ /* resolve the cifs name to a posix name */
+ status = pvfs_resolve_name(pvfs, req, cp->chkpath.in.path, 0, &name);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ if (!name->exists) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!S_ISDIR(name->st.st_mode)) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ copy a set of files
+*/
+static NTSTATUS pvfs_copy(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, struct smb_copy *cp)
+{
+ DEBUG(0,("pvfs_copy not implemented\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+/*
+ return print queue info
+*/
+static NTSTATUS pvfs_lpq(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, union smb_lpq *lpq)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+/* SMBtrans - not used on file shares */
+static NTSTATUS pvfs_trans(struct ntvfs_module_context *ntvfs,
+ struct ntvfs_request *req, struct smb_trans2 *trans2)
+{
+ return NT_STATUS_ACCESS_DENIED;
+}
+
+/*
+ initialise the POSIX disk backend, registering ourselves with the ntvfs subsystem
+ */
+NTSTATUS ntvfs_posix_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+ struct ntvfs_ops ops;
+ NTVFS_CURRENT_CRITICAL_SIZES(vers);
+
+ ZERO_STRUCT(ops);
+
+ ops.type = NTVFS_DISK;
+
+ /* fill in all the operations */
+ ops.connect_fn = pvfs_connect;
+ ops.disconnect_fn = pvfs_disconnect;
+ ops.unlink_fn = pvfs_unlink;
+ ops.chkpath_fn = pvfs_chkpath;
+ ops.qpathinfo_fn = pvfs_qpathinfo;
+ ops.setpathinfo_fn = pvfs_setpathinfo;
+ ops.open_fn = pvfs_open;
+ ops.mkdir_fn = pvfs_mkdir;
+ ops.rmdir_fn = pvfs_rmdir;
+ ops.rename_fn = pvfs_rename;
+ ops.copy_fn = pvfs_copy;
+ ops.ioctl_fn = pvfs_ioctl;
+ ops.read_fn = pvfs_read;
+ ops.write_fn = pvfs_write;
+ ops.seek_fn = pvfs_seek;
+ ops.flush_fn = pvfs_flush;
+ ops.close_fn = pvfs_close;
+ ops.exit_fn = pvfs_exit;
+ ops.lock_fn = pvfs_lock;
+ ops.setfileinfo_fn = pvfs_setfileinfo;
+ ops.qfileinfo_fn = pvfs_qfileinfo;
+ ops.fsinfo_fn = pvfs_fsinfo;
+ ops.lpq_fn = pvfs_lpq;
+ ops.search_first_fn = pvfs_search_first;
+ ops.search_next_fn = pvfs_search_next;
+ ops.search_close_fn = pvfs_search_close;
+ ops.trans_fn = pvfs_trans;
+ ops.logoff_fn = pvfs_logoff;
+ ops.async_setup_fn = pvfs_async_setup;
+ ops.cancel_fn = pvfs_cancel;
+ ops.notify_fn = pvfs_notify;
+
+ /* register ourselves with the NTVFS subsystem. We register
+ under the name 'default' as we wish to be the default
+ backend, and also register as 'posix' */
+ ops.name = "default";
+ ret = ntvfs_register(&ops, &vers);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register POSIX backend as '%s'!\n", ops.name));
+ }
+
+ ops.name = "posix";
+ ret = ntvfs_register(&ops, &vers);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0,("Failed to register POSIX backend as '%s'!\n", ops.name));
+ }
+
+ if (NT_STATUS_IS_OK(ret)) {
+ ret = ntvfs_common_init();
+ }
+
+ return ret;
+}
diff --git a/source4/ntvfs/posix/vfs_posix.h b/source4/ntvfs/posix/vfs_posix.h
new file mode 100644
index 0000000..3dbd785
--- /dev/null
+++ b/source4/ntvfs/posix/vfs_posix.h
@@ -0,0 +1,290 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - structure definitions
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 _VFS_POSIX_H_
+#define _VFS_POSIX_H_
+
+#include "librpc/gen_ndr/xattr.h"
+#include "system/filesys.h"
+#include "ntvfs/ntvfs.h"
+#include "ntvfs/common/ntvfs_common.h"
+#include "libcli/wbclient/wbclient.h"
+#include "lib/events/events.h"
+
+struct pvfs_wait;
+struct pvfs_oplock;
+
+/* this is the private structure for the posix vfs backend. It is used
+ to hold per-connection (per tree connect) state information */
+struct pvfs_state {
+ struct ntvfs_module_context *ntvfs;
+ const char *base_directory;
+ struct GUID *base_fs_uuid;
+
+ const char *share_name;
+ unsigned int flags;
+
+ struct pvfs_mangle_context *mangle_ctx;
+
+ struct brl_context *brl_context;
+ struct odb_context *odb_context;
+ struct notify_context *notify_context;
+
+ /* a list of pending async requests. Needed to support
+ ntcancel */
+ struct pvfs_wait *wait_list;
+
+ /* the sharing violation timeout (nsecs) */
+ unsigned int sharing_violation_delay;
+
+ /* the oplock break timeout (secs) */
+ unsigned int oplock_break_timeout;
+
+ /* the write time update delay (nsecs) */
+ unsigned int writetime_delay;
+
+ /* filesystem attributes (see FS_ATTR_*) */
+ uint32_t fs_attribs;
+
+ /* if posix:eadb is set, then this gets setup */
+ struct tdb_wrap *ea_db;
+
+ /* the allocation size rounding */
+ uint32_t alloc_size_rounding;
+
+ struct {
+ /* the open files as DLINKLIST */
+ struct pvfs_file *list;
+ } files;
+
+ struct {
+ /* an id tree mapping open search ID to a pvfs_search_state structure */
+ struct idr_context *idtree;
+
+ /* the open searches as DLINKLIST */
+ struct pvfs_search_state *list;
+
+ /* how long to keep inactive searches around for */
+ unsigned int inactivity_time;
+ } search;
+
+ /* used to accelerate acl mapping */
+ struct {
+ const struct dom_sid *creator_owner;
+ const struct dom_sid *creator_group;
+ } sid_cache;
+
+ /* the acl backend */
+ const struct pvfs_acl_ops *acl_ops;
+
+ /* non-flag share options */
+ struct {
+ mode_t dir_mask;
+ mode_t force_dir_mode;
+ mode_t create_mask;
+ mode_t force_create_mode;
+ } options;
+};
+
+/* this is the basic information needed about a file from the filesystem */
+struct pvfs_dos_fileinfo {
+ NTTIME create_time;
+ NTTIME access_time;
+ NTTIME write_time;
+ NTTIME change_time;
+ uint32_t attrib;
+ uint64_t alloc_size;
+ uint32_t nlink;
+ uint32_t ea_size;
+ uint64_t file_id;
+ uint32_t flags;
+};
+
+/*
+ this is the structure returned by pvfs_resolve_name(). It holds the posix details of
+ a filename passed by the client to any function
+*/
+struct pvfs_filename {
+ char *original_name;
+ char *full_name;
+ char *stream_name; /* does not include :$DATA suffix */
+ uint32_t stream_id; /* this uses a hash, so is probabilistic */
+ bool has_wildcard;
+ bool exists; /* true if the base filename exists */
+ bool stream_exists; /* true if the stream exists */
+ bool allow_override;
+ struct stat st;
+ struct pvfs_dos_fileinfo dos;
+};
+
+
+/* open file handle state - encapsulates the posix fd
+
+ Note that this is separated from the pvfs_file structure in order
+ to cope with the openx DENY_DOS semantics where a 2nd DENY_DOS open
+ on the same connection gets the same low level filesystem handle,
+ rather than a new handle
+*/
+struct pvfs_file_handle {
+ int fd;
+
+ struct pvfs_filename *name;
+
+ /* a unique file key to be used for open file locking */
+ DATA_BLOB odb_locking_key;
+
+ uint32_t create_options;
+
+ /* this is set by the mode_information level. What does it do? */
+ uint32_t mode;
+
+ /* yes, we need 2 independent positions ... */
+ uint64_t seek_offset;
+ uint64_t position;
+
+ bool have_opendb_entry;
+
+ /*
+ * we need to wait for oplock break requests from other processes,
+ * and we need to remember the pvfs_file so we can correctly
+ * forward the oplock break to the client
+ */
+ struct pvfs_oplock *oplock;
+
+ /* we need this hook back to our parent for lock destruction */
+ struct pvfs_state *pvfs;
+
+ struct {
+ bool update_triggered;
+ struct tevent_timer *update_event;
+ bool update_on_close;
+ NTTIME close_time;
+ bool update_forced;
+ } write_time;
+
+ /* the open went through to completion */
+ bool open_completed;
+
+ uint8_t private_flags;
+};
+
+/* open file state */
+struct pvfs_file {
+ struct pvfs_file *next, *prev;
+ struct pvfs_file_handle *handle;
+ struct ntvfs_handle *ntvfs;
+
+ struct pvfs_state *pvfs;
+
+ uint32_t impersonation;
+ uint32_t share_access;
+ uint32_t access_mask;
+
+ /* a list of pending locks - used for locking cancel operations */
+ struct pvfs_pending_lock *pending_list;
+
+ /* a file handle to be used for byte range locking */
+ struct brl_handle *brl_handle;
+
+ /* a count of active locks - used to avoid calling brlock_close on
+ file close */
+ uint64_t lock_count;
+
+ /* for directories, a buffer of pending notify events */
+ struct pvfs_notify_buffer *notify_buffer;
+
+ /* for directories, the state of an incomplete SMB2 Find */
+ struct pvfs_search_state *search;
+};
+
+/* the state of a search started with pvfs_search_first() */
+struct pvfs_search_state {
+ struct pvfs_search_state *prev, *next;
+ struct pvfs_state *pvfs;
+ uint16_t handle;
+ off_t current_index;
+ uint16_t search_attrib;
+ uint16_t must_attrib;
+ struct pvfs_dir *dir;
+ time_t last_used; /* monotonic clock time */
+ unsigned int num_ea_names;
+ struct ea_name *ea_names;
+ struct tevent_timer *te;
+};
+
+/* flags to pvfs_resolve_name() */
+#define PVFS_RESOLVE_WILDCARD (1<<0)
+#define PVFS_RESOLVE_STREAMS (1<<1)
+#define PVFS_RESOLVE_NO_OPENDB (1<<2)
+
+/* flags in pvfs->flags */
+#define PVFS_FLAG_CI_FILESYSTEM (1<<0) /* the filesystem is case insensitive */
+#define PVFS_FLAG_MAP_ARCHIVE (1<<1)
+#define PVFS_FLAG_MAP_SYSTEM (1<<2)
+#define PVFS_FLAG_MAP_HIDDEN (1<<3)
+#define PVFS_FLAG_READONLY (1<<4)
+#define PVFS_FLAG_STRICT_SYNC (1<<5)
+#define PVFS_FLAG_STRICT_LOCKING (1<<6)
+#define PVFS_FLAG_XATTR_ENABLE (1<<7)
+#define PVFS_FLAG_FAKE_OPLOCKS (1<<8)
+#define PVFS_FLAG_PERM_OVERRIDE (1<<10)
+
+/* forward declare some anonymous structures */
+struct pvfs_dir;
+
+/* types of notification for pvfs wait events */
+enum pvfs_wait_notice {PVFS_WAIT_EVENT, PVFS_WAIT_TIMEOUT, PVFS_WAIT_CANCEL};
+
+/*
+ state of a pending retry
+*/
+struct pvfs_odb_retry;
+
+#define PVFS_EADB "posix:eadb"
+#define PVFS_XATTR "posix:xattr"
+#define PVFS_FAKE_OPLOCKS "posix:fakeoplocks"
+#define PVFS_SHARE_DELAY "posix:sharedelay"
+#define PVFS_OPLOCK_TIMEOUT "posix:oplocktimeout"
+#define PVFS_WRITETIME_DELAY "posix:writetimeupdatedelay"
+#define PVFS_ALLOCATION_ROUNDING "posix:allocationrounding"
+#define PVFS_SEARCH_INACTIVITY "posix:searchinactivity"
+#define PVFS_ACL "posix:acl"
+#define PVFS_PERM_OVERRIDE "posix:permission override"
+
+#define PVFS_XATTR_DEFAULT true
+#define PVFS_FAKE_OPLOCKS_DEFAULT false
+#define PVFS_SHARE_DELAY_DEFAULT 1000000 /* nsecs */
+#define PVFS_OPLOCK_TIMEOUT_DEFAULT 30 /* secs */
+#define PVFS_WRITETIME_DELAY_DEFAULT 2000000 /* nsecs */
+#define PVFS_ALLOCATION_ROUNDING_DEFAULT 512
+#define PVFS_SEARCH_INACTIVITY_DEFAULT 300
+
+struct pvfs_acl_ops {
+ const char *name;
+ NTSTATUS (*acl_load)(struct pvfs_state *, struct pvfs_filename *, int , TALLOC_CTX *,
+ struct security_descriptor **);
+ NTSTATUS (*acl_save)(struct pvfs_state *, struct pvfs_filename *, int , struct security_descriptor *);
+};
+
+#include "ntvfs/posix/vfs_posix_proto.h"
+#include "ntvfs/posix/vfs_acl_proto.h"
+
+#endif /* _VFS_POSIX_H_ */
diff --git a/source4/ntvfs/posix/wscript_build b/source4/ntvfs/posix/wscript_build
new file mode 100644
index 0000000..649dea6
--- /dev/null
+++ b/source4/ntvfs/posix/wscript_build
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+if bld.CONFIG_SET('WITH_NTVFS_FILESERVER'):
+ bld.SAMBA_SUBSYSTEM('pvfs_acl',
+ source='pvfs_acl.c',
+ autoproto='vfs_acl_proto.h',
+ deps='events samba-modules',
+ )
+
+
+ bld.SAMBA_MODULE('pvfs_acl_xattr',
+ source='pvfs_acl_xattr.c',
+ subsystem='pvfs_acl',
+ init_function='pvfs_acl_xattr_init',
+ deps='NDR_XATTR events'
+ )
+
+
+ bld.SAMBA_MODULE('pvfs_acl_nfs4',
+ source='pvfs_acl_nfs4.c',
+ subsystem='pvfs_acl',
+ init_function='pvfs_acl_nfs4_init',
+ deps='NDR_NFS4ACL samdb events'
+ )
+
+
+ bld.SAMBA_MODULE('ntvfs_posix',
+ source='vfs_posix.c pvfs_util.c pvfs_search.c pvfs_dirlist.c pvfs_fileinfo.c pvfs_unlink.c pvfs_mkdir.c pvfs_open.c pvfs_read.c pvfs_flush.c pvfs_write.c pvfs_fsinfo.c pvfs_qfileinfo.c pvfs_setfileinfo.c pvfs_rename.c pvfs_resolve.c pvfs_shortname.c pvfs_lock.c pvfs_oplock.c pvfs_wait.c pvfs_seek.c pvfs_ioctl.c pvfs_xattr.c pvfs_streams.c pvfs_notify.c pvfs_sys.c xattr_system.c',
+ autoproto='vfs_posix_proto.h',
+ subsystem='ntvfs',
+ init_function='ntvfs_posix_init',
+ deps='NDR_XATTR attr ntvfs_common MESSAGING LIBWBCLIENT_OLD pvfs_acl posix_eadb',
+ internal_module=True
+ )
+
+
+bld.SAMBA_LIBRARY('posix_eadb',
+ source='posix_eadb.c',
+ deps='tdb tdb-wrap samba-util',
+ autoproto='posix_eadb_proto.h',
+ private_library=True)
+
+pyparam_util = bld.pyembed_libname('pyparam_util')
+
+bld.SAMBA_PYTHON('python_xattr_native',
+ source='python/pyxattr_native.c',
+ deps='ndr ldb samdb samba-credentials %s attr' % pyparam_util,
+ realname='samba/xattr_native.so'
+ )
+
+bld.SAMBA_PYTHON('python_posix_eadb',
+ source='python/pyposix_eadb.c',
+ deps='%s posix_eadb tdb' % pyparam_util,
+ realname='samba/posix_eadb.so'
+ )
+
+bld.SAMBA_PYTHON('python_xattr_tdb',
+ source='python/pyxattr_tdb.c',
+ deps='%s xattr_tdb' % pyparam_util,
+ realname='samba/xattr_tdb.so'
+ )
diff --git a/source4/ntvfs/posix/xattr_system.c b/source4/ntvfs/posix/xattr_system.c
new file mode 100644
index 0000000..ebb2010
--- /dev/null
+++ b/source4/ntvfs/posix/xattr_system.c
@@ -0,0 +1,145 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ POSIX NTVFS backend - xattr support using filesystem xattrs
+
+ Copyright (C) Andrew Tridgell 2004
+
+ 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 "includes.h"
+#include "vfs_posix.h"
+
+/*
+ pull a xattr as a blob, from either a file or a file descriptor
+*/
+NTSTATUS pull_xattr_blob_system(struct pvfs_state *pvfs,
+ TALLOC_CTX *mem_ctx,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ size_t estimated_size,
+ DATA_BLOB *blob)
+{
+ int ret;
+
+ *blob = data_blob_talloc(mem_ctx, NULL, estimated_size+16);
+ if (blob->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+again:
+ if (fd != -1) {
+ ret = fgetxattr(fd, attr_name, blob->data, estimated_size);
+ } else {
+ ret = getxattr(fname, attr_name, blob->data, estimated_size);
+ }
+ if (ret == -1 && errno == ERANGE) {
+ estimated_size *= 2;
+ blob->data = talloc_realloc(mem_ctx, blob->data,
+ uint8_t, estimated_size);
+ if (blob->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ blob->length = estimated_size;
+ goto again;
+ }
+ if (ret == -1 && errno == EPERM) {
+ struct stat statbuf;
+
+ if (fd != -1) {
+ ret = fstat(fd, &statbuf);
+ } else {
+ ret = stat(fname, &statbuf);
+ }
+ if (ret == 0) {
+ /* check if this is a directory and the sticky bit is set */
+ if (S_ISDIR(statbuf.st_mode) && (statbuf.st_mode & S_ISVTX)) {
+ /* pretend we could not find the xattr */
+
+ data_blob_free(blob);
+ return NT_STATUS_NOT_FOUND;
+
+ } else {
+ /* if not this was probably a legitimate error
+ * reset ret and errno to the correct values */
+ errno = EPERM;
+ ret = -1;
+ }
+ }
+ }
+
+ if (ret == -1) {
+ data_blob_free(blob);
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ blob->length = ret;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ push a xattr as a blob, from either a file or a file descriptor
+*/
+NTSTATUS push_xattr_blob_system(struct pvfs_state *pvfs,
+ const char *attr_name,
+ const char *fname,
+ int fd,
+ const DATA_BLOB *blob)
+{
+ int ret;
+
+ if (fd != -1) {
+ ret = fsetxattr(fd, attr_name, blob->data, blob->length, 0);
+ } else {
+ ret = setxattr(fname, attr_name, blob->data, blob->length, 0);
+ }
+ if (ret == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ delete a xattr
+*/
+NTSTATUS delete_xattr_system(struct pvfs_state *pvfs, const char *attr_name,
+ const char *fname, int fd)
+{
+ int ret;
+
+ if (fd != -1) {
+ ret = fremovexattr(fd, attr_name);
+ } else {
+ ret = removexattr(fname, attr_name);
+ }
+ if (ret == -1) {
+ return pvfs_map_errno(pvfs, errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ unlink a file - cleanup any xattrs
+*/
+NTSTATUS unlink_xattr_system(struct pvfs_state *pvfs, const char *fname)
+{
+ /* nothing needs to be done for filesystem based xattrs */
+ return NT_STATUS_OK;
+}