diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /source4/ntvfs/posix | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/ntvfs/posix')
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..e08597c --- /dev/null +++ b/source4/ntvfs/posix/posix_eadb.c @@ -0,0 +1,295 @@ +/* + 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; + const char *s; + 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)) { + blob = data_blob(NULL, 0); + } + + 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; + } + } + + 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..d284585 --- /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 = *dom_sid_parse_talloc(req, 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 = *dom_sid_parse_talloc(req, SID_NT_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_access_check(sd, token, *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..fc6c230 --- /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..a503404 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_dirlist.c @@ -0,0 +1,407 @@ +/* + 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); + } + + dir->offset = telldir(dir->dir) + 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) { + dir->offset = telldir(dir->dir) + 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; + } + *ofs = telldir(dir->dir) + 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))) { + dir->offset = telldir(dir->dir) + 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..54c7a33 --- /dev/null +++ b/source4/ntvfs/posix/pvfs_lock.c @@ -0,0 +1,411 @@ +/* + 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++) { + 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) { + 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; + } + pvfs_lock_async_failed(pvfs, req, f, locks, i, status); + talloc_free(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..de6c0ef --- /dev/null +++ b/source4/ntvfs/posix/pvfs_open.c @@ -0,0 +1,2097 @@ +/* + 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; + } + flags = 0; + 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; + } + flags = 0; + break; + + case NTCREATEX_DISP_OPEN_IF: + flags = 0; + 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..cc3d72c --- /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 info 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 definately 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 definately 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 an 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..f50319b --- /dev/null +++ b/source4/ntvfs/posix/pvfs_search.c @@ -0,0 +1,864 @@ +/* + 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" + +/* 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..cacd8c1 --- /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 equvalent 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 equvalent 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 equvalent 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..f567046 --- /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?", + 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..a4b51d1 --- /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 + * untill 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..6afb928 --- /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 alghorithm 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..abf397f --- /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 <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..d242cd9 --- /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 <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_SetFromErrno(PyExc_TypeError); + } + 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_SetFromErrno(PyExc_TypeError); + } + 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_SetFromErrno(PyExc_TypeError); + } + 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..425fd86 --- /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 <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..9f13158 --- /dev/null +++ b/source4/ntvfs/posix/vfs_posix.c @@ -0,0 +1,425 @@ +/* + 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" + +/* + 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 acess 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; +} + +/* + initialialise 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; +} |