wmi-1.3.16 from opsview.com
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
dnl #############################################
|
||||
dnl see if we have nanosecond resolution for stat
|
||||
AC_CACHE_CHECK([for tv_nsec nanosecond fields in struct stat],ac_cv_have_stat_tv_nsec,[
|
||||
AC_TRY_COMPILE(
|
||||
[
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
],
|
||||
[struct stat st;
|
||||
st.st_mtim.tv_nsec;
|
||||
st.st_atim.tv_nsec;
|
||||
st.st_ctim.tv_nsec;
|
||||
],
|
||||
ac_cv_decl_have_stat_tv_nsec=yes,
|
||||
ac_cv_decl_have_stat_tv_nsec=no)
|
||||
])
|
||||
if test x"$ac_cv_decl_have_stat_tv_nsec" = x"yes"; then
|
||||
AC_DEFINE(HAVE_STAT_TV_NSEC,1,[Whether stat has tv_nsec nanosecond fields])
|
||||
fi
|
||||
|
||||
AC_CHECK_HEADERS(blkid/blkid.h)
|
||||
AC_SEARCH_LIBS_EXT(blkid_get_cache, [blkid], BLKID_LIBS)
|
||||
AC_CHECK_FUNC_EXT(blkid_get_cache, $BLKID_LIBS)
|
||||
SMB_EXT_LIB(BLKID,[${BLKID_LIBS}],[${BLKID_CFLAGS}],[${BLKID_CPPFLAGS}],[${BLKID_LDFLAGS}])
|
||||
if test x"$ac_cv_func_ext_blkid_get_cache" = x"yes"; then
|
||||
AC_DEFINE(HAVE_LIBBLKID,1,[Whether we have blkid support (e2fsprogs)])
|
||||
SMB_ENABLE(BLKID,YES)
|
||||
fi
|
||||
@@ -0,0 +1,61 @@
|
||||
################################################
|
||||
# Start MODULE pvfs_acl_xattr
|
||||
[MODULE::pvfs_acl_xattr]
|
||||
INIT_FUNCTION = pvfs_acl_xattr_init
|
||||
SUBSYSTEM = ntvfs
|
||||
OBJ_FILES = \
|
||||
pvfs_acl_xattr.o
|
||||
PRIVATE_DEPENDENCIES = NDR_XATTR ntvfs_posix
|
||||
# End MODULE pvfs_acl_xattr
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE pvfs_acl_nfs4
|
||||
[MODULE::pvfs_acl_nfs4]
|
||||
INIT_FUNCTION = pvfs_acl_nfs4_init
|
||||
SUBSYSTEM = ntvfs
|
||||
OBJ_FILES = \
|
||||
pvfs_acl_nfs4.o
|
||||
PRIVATE_DEPENDENCIES = NDR_NFS4ACL SAMDB ntvfs_posix
|
||||
# End MODULE pvfs_acl_nfs4
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ntvfs_posix
|
||||
[MODULE::ntvfs_posix]
|
||||
SUBSYSTEM = ntvfs
|
||||
OUTPUT_TYPE = INTEGRATED
|
||||
INIT_FUNCTION = ntvfs_posix_init
|
||||
PRIVATE_PROTO_HEADER = vfs_posix_proto.h
|
||||
OBJ_FILES = \
|
||||
vfs_posix.o \
|
||||
pvfs_util.o \
|
||||
pvfs_search.o \
|
||||
pvfs_dirlist.o \
|
||||
pvfs_fileinfo.o \
|
||||
pvfs_unlink.o \
|
||||
pvfs_mkdir.o \
|
||||
pvfs_open.o \
|
||||
pvfs_read.o \
|
||||
pvfs_flush.o \
|
||||
pvfs_write.o \
|
||||
pvfs_fsinfo.o \
|
||||
pvfs_qfileinfo.o \
|
||||
pvfs_setfileinfo.o \
|
||||
pvfs_rename.o \
|
||||
pvfs_resolve.o \
|
||||
pvfs_shortname.o \
|
||||
pvfs_lock.o \
|
||||
pvfs_wait.o \
|
||||
pvfs_seek.o \
|
||||
pvfs_ioctl.o \
|
||||
pvfs_xattr.o \
|
||||
pvfs_streams.o \
|
||||
pvfs_acl.o \
|
||||
pvfs_notify.o \
|
||||
xattr_system.o \
|
||||
xattr_tdb.o
|
||||
#PRIVATE_DEPENDENCIES = pvfs_acl_xattr pvfs_acl_nfs4
|
||||
PUBLIC_DEPENDENCIES = NDR_XATTR WRAP_XATTR BLKID ntvfs_common MESSAGING
|
||||
# End MODULE ntvfs_posix
|
||||
################################################
|
||||
@@ -0,0 +1,739 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "auth/auth.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "librpc/gen_ndr/xattr.h"
|
||||
#include "libcli/security/security.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.
|
||||
*/
|
||||
_PUBLIC_ NTSTATUS pvfs_acl_register(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(talloc_autofree_context(), backends, struct pvfs_acl_backend, num_backends+1);
|
||||
NT_STATUS_HAVE_NO_MEMORY(backends);
|
||||
|
||||
new_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
|
||||
*/
|
||||
_PUBLIC_ 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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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;
|
||||
|
||||
*psd = security_descriptor_initialise(req);
|
||||
if (*psd == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
sd = *psd;
|
||||
|
||||
status = sidmap_uid_to_sid(pvfs->sidmap, sd, name->st.st_uid, &sd->owner_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
status = sidmap_gid_to_sid(pvfs->sidmap, sd, name->st.st_gid, &sd->group_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
status = sidmap_sid_to_unixuid(pvfs->sidmap, new_sd->owner_sid, &new_uid);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
}
|
||||
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)) {
|
||||
status = sidmap_sid_to_unixgid(pvfs->sidmap, new_sd->group_sid, &new_gid);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
NTSTATUS pvfs_access_check_unix(struct pvfs_state *pvfs,
|
||||
struct ntvfs_request *req,
|
||||
struct pvfs_filename *name,
|
||||
uint32_t *access_mask)
|
||||
{
|
||||
uid_t uid = geteuid();
|
||||
uint32_t max_bits = SEC_RIGHTS_FILE_READ | SEC_FILE_ALL;
|
||||
|
||||
if (pvfs_read_only(pvfs, *access_mask)) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
/* owner and root get extra permissions */
|
||||
if (uid == 0) {
|
||||
max_bits |= SEC_STD_ALL | SEC_FLAG_SYSTEM_SECURITY;
|
||||
} else if (uid == name->st.st_uid) {
|
||||
max_bits |= SEC_STD_ALL;
|
||||
}
|
||||
|
||||
if (*access_mask == SEC_FLAG_MAXIMUM_ALLOWED) {
|
||||
*access_mask = max_bits;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
if (uid != 0 && (*access_mask & SEC_FLAG_SYSTEM_SECURITY)) {
|
||||
return NT_STATUS_PRIVILEGE_NOT_HELD;
|
||||
}
|
||||
|
||||
if (*access_mask & ~max_bits) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
*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;
|
||||
|
||||
if (pvfs_read_only(pvfs, *access_mask)) {
|
||||
return NT_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
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);
|
||||
*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);
|
||||
return pvfs_access_check_unix(pvfs, req, name, access_mask);
|
||||
}
|
||||
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 = sec_access_check(sd, token, *access_mask, access_mask);
|
||||
|
||||
/* this bit is always granted, even if not asked for */
|
||||
*access_mask |= SEC_FILE_READ_ATTRIBUTE;
|
||||
|
||||
talloc_free(acl);
|
||||
|
||||
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)
|
||||
{
|
||||
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(pvfs, req, parent, access_mask);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (! ((*access_mask) & SEC_DIR_ADD_FILE)) {
|
||||
return pvfs_access_check_simple(pvfs, req, parent, SEC_DIR_ADD_FILE);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
return pvfs_access_check_simple(pvfs, req, parent, access_mask);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
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 pvfs_filename *parent;
|
||||
struct security_descriptor *parent_sd, *sd;
|
||||
BOOL container;
|
||||
|
||||
/* form the parents path */
|
||||
status = pvfs_resolve_parent(pvfs, req, name, &parent);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
acl = talloc(req, struct xattr_NTACL);
|
||||
if (acl == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
status = pvfs_acl_load(pvfs, parent, -1, acl);
|
||||
if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
switch (acl->version) {
|
||||
case 1:
|
||||
parent_sd = acl->info.sd;
|
||||
break;
|
||||
default:
|
||||
return NT_STATUS_INVALID_ACL;
|
||||
}
|
||||
|
||||
if (parent_sd == NULL ||
|
||||
parent_sd->dacl == NULL ||
|
||||
parent_sd->dacl->num_aces == 0) {
|
||||
/* go with the default ACL */
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/* create the new sd */
|
||||
sd = security_descriptor_initialise(req);
|
||||
if (sd == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
status = sidmap_uid_to_sid(pvfs->sidmap, sd, name->st.st_uid, &sd->owner_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
status = sidmap_gid_to_sid(pvfs->sidmap, sd, name->st.st_gid, &sd->group_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
sd->type |= SEC_DESC_DACL_PRESENT;
|
||||
|
||||
container = (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) ? True:False;
|
||||
|
||||
/* fill in the aces from the parent */
|
||||
status = pvfs_acl_inherit_aces(pvfs, parent_sd, sd, container);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* if there is nothing to inherit then we fallback to the
|
||||
default acl */
|
||||
if (sd->dacl == NULL || sd->dacl->num_aces == 0) {
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
acl->info.sd = sd;
|
||||
|
||||
status = pvfs_acl_save(pvfs, name, fd, acl);
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
#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;
|
||||
|
||||
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_XATTR_NAME,
|
||||
acl, 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;
|
||||
status = sidmap_uid_to_sid(pvfs->sidmap, sd, name->st.st_uid, &sd->owner_sid);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
status = sidmap_gid_to_sid(pvfs->sidmap, sd, name->st.st_gid, &sd->group_sid);
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
|
||||
for (i=0;i<acl->a_count;i++) {
|
||||
struct nfs4ace *a = &acl->ace[i];
|
||||
struct security_ace ace;
|
||||
struct dom_sid *sid;
|
||||
ace.type = a->e_type;
|
||||
ace.flags = a->e_flags;
|
||||
ace.access_mask = a->e_mask;
|
||||
if (a->e_flags & ACE4_IDENTIFIER_GROUP) {
|
||||
status = sidmap_gid_to_sid(pvfs->sidmap, sd, a->e_id, &sid);
|
||||
} else {
|
||||
status = sidmap_uid_to_sid(pvfs->sidmap, sd, a->e_id, &sid);
|
||||
}
|
||||
NT_STATUS_NOT_OK_RETURN(status);
|
||||
ace.trustee = *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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (sidmap_sid_is_group(pvfs->sidmap, &ace->trustee)) {
|
||||
gid_t gid;
|
||||
a->e_flags |= ACE4_IDENTIFIER_GROUP;
|
||||
status = sidmap_sid_to_unixgid(pvfs->sidmap, &ace->trustee, &gid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return status;
|
||||
}
|
||||
a->e_id = gid;
|
||||
} else {
|
||||
uid_t uid;
|
||||
status = sidmap_sid_to_unixuid(pvfs->sidmap, &ace->trustee, &uid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(tmp_ctx);
|
||||
return status;
|
||||
}
|
||||
a->e_id = uid;
|
||||
}
|
||||
a->e_who = "";
|
||||
}
|
||||
|
||||
privs = root_privileges();
|
||||
status = pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
|
||||
NFS4ACL_XATTR_NAME,
|
||||
&acl, ndr_push_nfs4acl);
|
||||
talloc_free(privs);
|
||||
|
||||
talloc_free(tmp_ctx);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
initialise pvfs acl NFS4 backend
|
||||
*/
|
||||
NTSTATUS pvfs_acl_nfs4_init(void)
|
||||
{
|
||||
struct pvfs_acl_ops ops = {
|
||||
.name = "nfs4acl",
|
||||
.acl_load = pvfs_acl_load_nfs4,
|
||||
.acl_save = pvfs_acl_save_nfs4
|
||||
};
|
||||
return pvfs_acl_register(&ops);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "lib/util/unix_privs.h"
|
||||
#include "librpc/gen_ndr/ndr_xattr.h"
|
||||
|
||||
/*
|
||||
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,
|
||||
(ndr_pull_flags_fn_t)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,
|
||||
(ndr_push_flags_fn_t)ndr_push_xattr_NTACL);
|
||||
talloc_free(privs);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
initialise pvfs acl xattr backend
|
||||
*/
|
||||
NTSTATUS pvfs_acl_xattr_init(void)
|
||||
{
|
||||
struct pvfs_acl_ops ops = {
|
||||
.name = "xattr",
|
||||
.acl_load = pvfs_acl_load_xattr,
|
||||
.acl_save = pvfs_acl_save_xattr
|
||||
};
|
||||
return pvfs_acl_register(&ops);
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
/*
|
||||
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(dir->pattern, ".", protocol) == 0) {
|
||||
dcache_add(dir, ".");
|
||||
return ".";
|
||||
}
|
||||
}
|
||||
|
||||
if (*ofs == DIR_OFFSET_DOTDOT) {
|
||||
(*ofs) = DIR_OFFSET_BASE;
|
||||
dir->offset = *ofs;
|
||||
if (ms_fnmatch(dir->pattern, "..", protocol) == 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(dir->pattern, dname, protocol) != 0) {
|
||||
char *short_name = pvfs_short_name_component(dir->pvfs, dname);
|
||||
if (short_name == NULL ||
|
||||
ms_fnmatch(dir->pattern, short_name, protocol) != 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;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.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, int fd)
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
#ifdef HAVE_STAT_TV_NSEC
|
||||
name->dos.create_time += name->st.st_ctim.tv_nsec / 100;
|
||||
name->dos.access_time += name->st.st_atim.tv_nsec / 100;
|
||||
name->dos.write_time += name->st.st_mtim.tv_nsec / 100;
|
||||
name->dos.change_time += name->st.st_ctim.tv_nsec / 100;
|
||||
#endif
|
||||
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;
|
||||
name->dos.file_id = (((uint64_t)name->st.st_dev)<<32) | name->st.st_ino;
|
||||
name->dos.flags = 0;
|
||||
|
||||
return pvfs_dosattrib_load(pvfs, name, fd);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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;
|
||||
|
||||
if (attrib & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
mode |= S_IXUSR;
|
||||
}
|
||||
|
||||
if (!(attrib & FILE_ATTRIBUTE_READONLY) ||
|
||||
(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
|
||||
mode |= S_IWUSR;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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 = ntvfs->private_data;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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 = ntvfs->private_data;
|
||||
uint64_t blocks_free, blocks_total;
|
||||
uint_t 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);
|
||||
}
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NT_STATUS_INVALID_LEVEL;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "libcli/raw/ioctl.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 = ntvfs->private_data;
|
||||
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:
|
||||
return NT_STATUS_FS_DRIVER_REQUIRED;
|
||||
}
|
||||
|
||||
return NT_STATUS_INVALID_LEVEL;
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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,
|
||||
uint16_t smbpid,
|
||||
uint64_t offset, uint64_t count,
|
||||
enum brl_type rw)
|
||||
{
|
||||
if (!(pvfs->flags & PVFS_FLAG_STRICT_LOCKING)) {
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
return brl_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;
|
||||
void *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-1;i>=0;i--) {
|
||||
brl_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 somoene 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, enum pvfs_wait_notice reason)
|
||||
{
|
||||
struct pvfs_pending_lock *pending = private;
|
||||
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) {
|
||||
status = NT_STATUS_FILE_LOCK_CONFLICT;
|
||||
} 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 = brl_lock(pvfs->brl_context,
|
||||
f->brl_handle,
|
||||
req->smbpid,
|
||||
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 = brl_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);
|
||||
} 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 = brl_lock(pvfs->brl_context,
|
||||
f->brl_handle,
|
||||
req->smbpid,
|
||||
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);
|
||||
} else {
|
||||
talloc_steal(pending, pending->wait_handle);
|
||||
DLIST_ADD(f->pending_list, pending);
|
||||
}
|
||||
return;
|
||||
}
|
||||
pvfs_lock_async_failed(pvfs, req, f, locks, i, status);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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));
|
||||
brl_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 = ntvfs->private_data;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(lck->lockx.in.timeout/1000,
|
||||
1000*(lck->lockx.in.timeout%1000));
|
||||
}
|
||||
|
||||
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) {
|
||||
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 */
|
||||
return NT_STATUS_DOS(ERRDOS, ERRnoatomiclocks);
|
||||
}
|
||||
|
||||
if (lck->lockx.in.mode & LOCKING_ANDX_OPLOCK_RELEASE) {
|
||||
DEBUG(0,("received unexpected oplock break\n"));
|
||||
return NT_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
|
||||
/* the unlocks happen first */
|
||||
locks = lck->lockx.in.locks;
|
||||
|
||||
for (i=0;i<lck->lockx.in.ulock_cnt;i++) {
|
||||
status = brl_unlock(pvfs->brl_context,
|
||||
f->brl_handle,
|
||||
locks[i].pid,
|
||||
locks[i].offset,
|
||||
locks[i].count);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
f->lock_count--;
|
||||
}
|
||||
|
||||
locks += i;
|
||||
|
||||
for (i=0;i<lck->lockx.in.lock_cnt;i++) {
|
||||
if (pending) {
|
||||
pending->pending_lock = i;
|
||||
}
|
||||
|
||||
status = brl_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) {
|
||||
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-1;i>=0;i--) {
|
||||
brl_unlock(pvfs->brl_context,
|
||||
f->brl_handle,
|
||||
locks[i].pid,
|
||||
locks[i].offset,
|
||||
locks[i].count);
|
||||
f->lock_count--;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
f->lock_count++;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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 (mkdir(name->full_name, mode) == -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)) {
|
||||
rmdir(name->full_name);
|
||||
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)) {
|
||||
rmdir(name->full_name);
|
||||
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 = ntvfs->private_data;
|
||||
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 (mkdir(name->full_name, mode) == -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)) {
|
||||
rmdir(name->full_name);
|
||||
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 = ntvfs->private_data;
|
||||
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 (rmdir(name->full_name) == -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;
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
/* 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 event_context *ev, struct timed_event *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);
|
||||
}
|
||||
talloc_free(notify_buffer);
|
||||
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->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 */
|
||||
event_add_timed(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, const struct notify_event *ev)
|
||||
{
|
||||
struct pvfs_notify_buffer *n = talloc_get_type(private, struct pvfs_notify_buffer);
|
||||
size_t len;
|
||||
struct notify_changes *n2;
|
||||
char *new_path;
|
||||
|
||||
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;
|
||||
|
||||
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, enum pvfs_wait_notice reason)
|
||||
{
|
||||
struct pvfs_notify_buffer *notify_buffer = talloc_get_type(private,
|
||||
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, struct notify_pending *);
|
||||
|
||||
/* if the buffer is empty then start waiting */
|
||||
if (f->notify_buffer->num_changes == 0) {
|
||||
void *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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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_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,
|
||||
uint_t 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) {
|
||||
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;
|
||||
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_GENERIC:
|
||||
return NT_STATUS_INVALID_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_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:
|
||||
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:
|
||||
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.fname.s = name->original_name;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
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 = ntvfs->private_data;
|
||||
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 NT_STATUS_DELETE_PENDING;
|
||||
}
|
||||
|
||||
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 = ntvfs->private_data;
|
||||
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_fd(pvfs, h->fd, h->name);
|
||||
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, NULL, NULL)) {
|
||||
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, NULL, NULL)) {
|
||||
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, NULL, NULL)) {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "librpc/gen_ndr/security.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 = ntvfs->private_data;
|
||||
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_FILE_IS_A_DIRECTORY;
|
||||
}
|
||||
|
||||
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 > UINT16_MAX && req->ctx->protocol < PROTOCOL_SMB2) {
|
||||
maxcnt = 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "librpc/gen_ndr/security.h"
|
||||
|
||||
|
||||
/*
|
||||
do a file rename, and send any notify triggers
|
||||
*/
|
||||
NTSTATUS pvfs_do_rename(struct pvfs_state *pvfs, const struct pvfs_filename *name1,
|
||||
const char *name2)
|
||||
{
|
||||
const char *r1, *r2;
|
||||
uint32_t mask;
|
||||
|
||||
if (rename(name1->full_name, name2) == -1) {
|
||||
return pvfs_map_errno(pvfs, errno);
|
||||
}
|
||||
|
||||
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 renming 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_size(mem_ctx, 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
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);
|
||||
NTSTATUS status;
|
||||
struct odb_lock *lck, *lck2;
|
||||
|
||||
/* 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, &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)) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* get a pvfs_filename dest object */
|
||||
status = pvfs_resolve_partial(pvfs, mem_ctx,
|
||||
dir_path, fname2, &name2);
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
status = pvfs_can_delete(pvfs, req, name2, &lck2);
|
||||
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, name1, fname2);
|
||||
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
status = odb_rename(lck, 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, '/')) {
|
||||
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 = ntvfs->private_data;
|
||||
NTSTATUS status;
|
||||
struct pvfs_filename *name1, *name2;
|
||||
struct odb_lock *lck;
|
||||
|
||||
/* 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);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pvfs_do_rename(pvfs, name1, name2->full_name);
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
status = odb_rename(lck, name2->full_name);
|
||||
}
|
||||
|
||||
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 = ntvfs->private_data;
|
||||
NTSTATUS status;
|
||||
struct pvfs_filename *name1, *name2;
|
||||
struct odb_lock *lck;
|
||||
|
||||
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, &name1);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
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);
|
||||
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, 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);
|
||||
|
||||
case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NT_STATUS_INVALID_LEVEL;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,634 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
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"
|
||||
|
||||
/*
|
||||
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)
|
||||
{
|
||||
/* break into a series of components */
|
||||
int num_components;
|
||||
char **components;
|
||||
char *p, *partial_name;
|
||||
int 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, -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;
|
||||
name->stream_name = talloc_strdup(name, s+1);
|
||||
if (name->stream_name == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
p = strchr_m(name->stream_name, ':');
|
||||
if (p == NULL) {
|
||||
name->stream_id = pvfs_name_hash(name->stream_name,
|
||||
strlen(name->stream_name));
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
if (strcasecmp_m(p, ":$DATA") != 0) {
|
||||
return NT_STATUS_OBJECT_NAME_INVALID;
|
||||
}
|
||||
*p = 0;
|
||||
if (strcmp(name->stream_name, "") == 0) {
|
||||
name->stream_name = NULL;
|
||||
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,
|
||||
uint_t flags, struct pvfs_filename *name)
|
||||
{
|
||||
char *ret, *p, *p_start;
|
||||
NTSTATUS status;
|
||||
|
||||
name->original_name = talloc_strdup(name, cifs_name);
|
||||
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);
|
||||
switch (c) {
|
||||
case '\\':
|
||||
if (name->has_wildcard) {
|
||||
/* wildcards are only allowed in the last part
|
||||
of a name */
|
||||
return NT_STATUS_ILLEGAL_CHARACTER;
|
||||
}
|
||||
if (p > p_start && p[1] == 0) {
|
||||
*p = 0;
|
||||
} else {
|
||||
*p = '/';
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
if (!(flags & PVFS_RESOLVE_STREAMS)) {
|
||||
return NT_STATUS_ILLEGAL_CHARACTER;
|
||||
}
|
||||
if (name->has_wildcard) {
|
||||
return NT_STATUS_ILLEGAL_CHARACTER;
|
||||
}
|
||||
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_ILLEGAL_CHARACTER;
|
||||
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, uint_t flags)
|
||||
{
|
||||
codepoint_t c;
|
||||
size_t c_size, len;
|
||||
int 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) {
|
||||
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+1)));
|
||||
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_size(mem_ctx, 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_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, TALLOC_CTX *mem_ctx,
|
||||
const char *cifs_name,
|
||||
uint_t flags, struct pvfs_filename **name)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
*name = talloc(mem_ctx, struct pvfs_filename);
|
||||
if (*name == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
(*name)->exists = False;
|
||||
(*name)->stream_exists = False;
|
||||
|
||||
if (!(pvfs->fs_attribs & FS_ATTR_NAMED_STREAMS)) {
|
||||
flags &= ~PVFS_RESOLVE_STREAMS;
|
||||
}
|
||||
|
||||
/* 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() */
|
||||
if ((*name)->has_wildcard) {
|
||||
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, -1);
|
||||
}
|
||||
|
||||
/* search for a matching filename */
|
||||
status = pvfs_case_search(pvfs, *name);
|
||||
|
||||
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,
|
||||
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;
|
||||
|
||||
status = pvfs_fill_dos_info(pvfs, *name, -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)
|
||||
{
|
||||
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, fd);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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_reference(*name, child->original_name);
|
||||
(*name)->stream_name = NULL;
|
||||
(*name)->stream_id = 0;
|
||||
|
||||
status = pvfs_fill_dos_info(pvfs, *name, -1);
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,843 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "system/time.h"
|
||||
#include "librpc/gen_ndr/security.h"
|
||||
#include "smbd/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 event_context *ev, struct timed_event *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 event_context *ev = search->pvfs->ntvfs->ctx->event_ctx;
|
||||
if (search->handle == INVALID_SEARCH_HANDLE) return;
|
||||
talloc_free(search->te);
|
||||
search->te = event_add_timed(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, &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:
|
||||
break;
|
||||
}
|
||||
|
||||
return NT_STATUS_INVALID_LEVEL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
the search fill loop
|
||||
*/
|
||||
static NTSTATUS pvfs_search_fill(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx,
|
||||
uint_t max_count,
|
||||
struct pvfs_search_state *search,
|
||||
enum smb_search_data_level level,
|
||||
uint_t *reply_count,
|
||||
void *search_private,
|
||||
BOOL (*callback)(void *, 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(NULL);
|
||||
|
||||
for (i=0;i<MAX_OLD_SEARCHES;i++) {
|
||||
struct pvfs_search_state *search = idr_find(pvfs->search.idtree, i);
|
||||
if (search == NULL) return;
|
||||
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 *, union smb_search_data *))
|
||||
{
|
||||
struct pvfs_dir *dir;
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
struct pvfs_search_state *search;
|
||||
uint_t 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(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 *, union smb_search_data *))
|
||||
{
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
struct pvfs_search_state *search;
|
||||
struct pvfs_dir *dir;
|
||||
uint_t 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;
|
||||
|
||||
search = idr_find(pvfs->search.idtree, handle);
|
||||
if (search == NULL) {
|
||||
/* we didn't find the search handle */
|
||||
return NT_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
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(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 *, union smb_search_data *))
|
||||
{
|
||||
struct pvfs_dir *dir;
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
struct pvfs_search_state *search;
|
||||
uint_t 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 *, union smb_search_data *))
|
||||
{
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
struct pvfs_search_state *search;
|
||||
struct pvfs_dir *dir;
|
||||
uint_t reply_count;
|
||||
uint16_t handle;
|
||||
NTSTATUS status;
|
||||
|
||||
handle = io->t2fnext.in.handle;
|
||||
|
||||
search = idr_find(pvfs->search.idtree, handle);
|
||||
if (search == NULL) {
|
||||
/* we didn't find the search handle */
|
||||
return NT_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
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 *, union smb_search_data *))
|
||||
{
|
||||
struct pvfs_dir *dir;
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
struct pvfs_search_state *search;
|
||||
uint_t 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 = 0;
|
||||
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 *, union smb_search_data *))
|
||||
{
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
struct pvfs_search_state *search;
|
||||
uint_t 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 *, 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 *, 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 = ntvfs->private_data;
|
||||
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;
|
||||
}
|
||||
|
||||
search = idr_find(pvfs->search.idtree, handle);
|
||||
if (search == NULL) {
|
||||
/* we didn't find the search handle */
|
||||
return NT_STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
talloc_free(search);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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 = ntvfs->private_data;
|
||||
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);
|
||||
h->seek_offset = h->name->st.st_size + io->lseek.in.offset;
|
||||
break;
|
||||
}
|
||||
|
||||
io->lseek.out.offset = h->seek_offset;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,635 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
default:
|
||||
needed = SEC_FILE_WRITE_ATTRIBUTE;
|
||||
break;
|
||||
}
|
||||
|
||||
return needed;
|
||||
}
|
||||
|
||||
/*
|
||||
rename_information level
|
||||
*/
|
||||
static NTSTATUS pvfs_setfileinfo_rename(struct pvfs_state *pvfs,
|
||||
struct ntvfs_request *req,
|
||||
struct pvfs_filename *name,
|
||||
union smb_setfileinfo *info)
|
||||
{
|
||||
NTSTATUS status;
|
||||
struct pvfs_filename *name2;
|
||||
char *new_name, *p;
|
||||
|
||||
/* renames are only allowed within a directory */
|
||||
if (strchr_m(info->rename_information.in.new_name, '\\')) {
|
||||
return NT_STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
/* don't allow this for now */
|
||||
return NT_STATUS_FILE_IS_A_DIRECTORY;
|
||||
}
|
||||
|
||||
/* don't allow stream renames for now */
|
||||
if (name->stream_name) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* w2k3 does not appear to allow relative rename */
|
||||
if (info->rename_information.in.root_fid != 0) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
/* construct the fully qualified windows name for the new file name */
|
||||
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;
|
||||
}
|
||||
*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, name, 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) {
|
||||
struct odb_lock *lck;
|
||||
|
||||
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, &lck);
|
||||
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;
|
||||
}
|
||||
|
||||
status = pvfs_do_rename(pvfs, name, name2->full_name);
|
||||
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 = ntvfs->private_data;
|
||||
struct utimbuf unix_times;
|
||||
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_fd(pvfs, h->fd, h->name);
|
||||
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;
|
||||
newstats.dos.flags |= XATTR_ATTRIB_FLAG_STICKY_WRITE_TIME;
|
||||
h->sticky_write_time = True;
|
||||
}
|
||||
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:
|
||||
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:
|
||||
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_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:
|
||||
return pvfs_setfileinfo_rename(pvfs, req, h->name,
|
||||
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 */
|
||||
ZERO_STRUCT(unix_times);
|
||||
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) {
|
||||
unix_times.actime = nt_time_to_unix(newstats.dos.access_time);
|
||||
change_mask |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
|
||||
}
|
||||
if (newstats.dos.write_time != h->name->dos.write_time) {
|
||||
unix_times.modtime = nt_time_to_unix(newstats.dos.write_time);
|
||||
change_mask |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
||||
}
|
||||
if (unix_times.actime != 0 || unix_times.modtime != 0) {
|
||||
if (utime(h->name->full_name, &unix_times) == -1) {
|
||||
return pvfs_map_errno(pvfs, errno);
|
||||
}
|
||||
}
|
||||
|
||||
/* possibly change the attribute */
|
||||
if (newstats.dos.attrib != h->name->dos.attrib) {
|
||||
mode_t mode = pvfs_fileperms(pvfs, newstats.dos.attrib);
|
||||
if (!(h->name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
if (fchmod(h->fd, mode) == -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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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 = ntvfs->private_data;
|
||||
struct pvfs_filename *name;
|
||||
struct pvfs_filename newstats;
|
||||
NTSTATUS status;
|
||||
struct utimbuf unix_times;
|
||||
uint32_t access_needed;
|
||||
uint32_t change_mask = 0;
|
||||
|
||||
/* 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:
|
||||
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:
|
||||
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:
|
||||
return pvfs_setfileinfo_rename(pvfs, req, name,
|
||||
info);
|
||||
|
||||
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 */
|
||||
ZERO_STRUCT(unix_times);
|
||||
if (newstats.dos.create_time != name->dos.create_time) {
|
||||
change_mask |= FILE_NOTIFY_CHANGE_CREATION;
|
||||
}
|
||||
if (newstats.dos.access_time != name->dos.access_time) {
|
||||
unix_times.actime = nt_time_to_unix(newstats.dos.access_time);
|
||||
change_mask |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
|
||||
}
|
||||
if (newstats.dos.write_time != name->dos.write_time) {
|
||||
unix_times.modtime = nt_time_to_unix(newstats.dos.write_time);
|
||||
change_mask |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
||||
}
|
||||
if (unix_times.actime != 0 || unix_times.modtime != 0) {
|
||||
if (utime(name->full_name, &unix_times) == -1) {
|
||||
return pvfs_map_errno(pvfs, errno);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 (chmod(name->full_name, mode) == -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "system/locale.h"
|
||||
#include "vfs_posix.h"
|
||||
|
||||
/*
|
||||
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",
|
||||
"LPT1", "LPT2", "LPT3", "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 = lp_parm_int(-1, "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 = lp_parm_int(-1, "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));
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "librpc/gen_ndr/xattr.h"
|
||||
|
||||
|
||||
/*
|
||||
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;
|
||||
|
||||
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;
|
||||
info->streams[i+1].stream_name.s = talloc_asprintf(streams->streams,
|
||||
":%s:$DATA",
|
||||
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 (strcasecmp_m(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 (strcasecmp_m(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 = name->stream_name;
|
||||
}
|
||||
|
||||
status = pvfs_streams_save(pvfs, name, fd, streams);
|
||||
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 (strcasecmp_m(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;
|
||||
}
|
||||
|
||||
/*
|
||||
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_xattr_load(pvfs, h, h->name->full_name, h->fd, XATTR_DOSSTREAM_PREFIX,
|
||||
h->name->stream_name, 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 (offset > XATTR_MAX_STREAM_SIZE) {
|
||||
errno = ENOSPC;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* we have to load the existing stream, then modify, then save */
|
||||
status = pvfs_xattr_load(pvfs, h, h->name->full_name, h->fd, XATTR_DOSSTREAM_PREFIX,
|
||||
h->name->stream_name, 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) {
|
||||
return NT_STATUS_DISK_FULL;
|
||||
}
|
||||
|
||||
/* we have to load the existing stream, then modify, then save */
|
||||
status = pvfs_xattr_load(pvfs, name, name->full_name, fd, XATTR_DOSSTREAM_PREFIX,
|
||||
name->stream_name, 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);
|
||||
data_blob_free(&blob);
|
||||
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
status = pvfs_stream_update_size(pvfs, name, fd, blob.length);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "system/dir.h"
|
||||
|
||||
|
||||
/*
|
||||
unlink a stream
|
||||
*/
|
||||
static NTSTATUS pvfs_unlink_stream(struct pvfs_state *pvfs,
|
||||
struct ntvfs_request *req,
|
||||
struct pvfs_filename *name,
|
||||
uint16_t attrib)
|
||||
{
|
||||
NTSTATUS status;
|
||||
struct odb_lock *lck;
|
||||
|
||||
if (!name->stream_exists) {
|
||||
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* make sure its matches the given attributes */
|
||||
status = pvfs_match_attrib(pvfs, name, attrib, 0);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pvfs_can_delete(pvfs, req, name, &lck);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return pvfs_stream_delete(pvfs, name, -1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
unlink one file
|
||||
*/
|
||||
static NTSTATUS pvfs_unlink_one(struct pvfs_state *pvfs,
|
||||
struct ntvfs_request *req,
|
||||
const char *unix_path,
|
||||
const char *fname, uint32_t attrib)
|
||||
{
|
||||
struct pvfs_filename *name;
|
||||
NTSTATUS status;
|
||||
struct odb_lock *lck;
|
||||
|
||||
/* get a pvfs_filename object */
|
||||
status = pvfs_resolve_partial(pvfs, req,
|
||||
unix_path, fname, &name);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
/* make sure its matches the given attributes */
|
||||
status = pvfs_match_attrib(pvfs, name, attrib, 0);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(name);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pvfs_can_delete(pvfs, req, name, &lck);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(name);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (name->dos.attrib & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
talloc_free(name);
|
||||
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 (unlink(name->full_name) == -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);
|
||||
}
|
||||
|
||||
talloc_free(name);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
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 = ntvfs->private_data;
|
||||
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, &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->stream_name) {
|
||||
return pvfs_unlink_stream(pvfs, req, name, unl->unlink.in.attrib);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
status = pvfs_unlink_one(pvfs, req, pvfs_list_unix_path(dir), fname, unl->unlink.in.attrib);
|
||||
if (NT_STATUS_IS_OK(status)) {
|
||||
total_deleted++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total_deleted > 0) {
|
||||
status = NT_STATUS_OK;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
/*
|
||||
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)
|
||||
{
|
||||
return map_nt_error_from_unix(unix_errno);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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)
|
||||
{
|
||||
int fd1, fd2;
|
||||
mode_t mode;
|
||||
NTSTATUS status;
|
||||
size_t buf_size = 0x10000;
|
||||
char *buf = talloc_size(name2, buf_size);
|
||||
|
||||
if (buf == NULL) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
fd1 = open(name1->full_name, O_RDONLY);
|
||||
if (fd1 == -1) {
|
||||
talloc_free(buf);
|
||||
return pvfs_map_errno(pvfs, errno);
|
||||
}
|
||||
|
||||
fd2 = open(name2->full_name, O_CREAT|O_EXCL|O_WRONLY, 0);
|
||||
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);
|
||||
unlink(name2->full_name);
|
||||
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 (fchmod(fd2, mode) == -1) {
|
||||
status = pvfs_map_errno(pvfs, errno);
|
||||
close(fd2);
|
||||
unlink(name2->full_name);
|
||||
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);
|
||||
unlink(name2->full_name);
|
||||
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_w(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);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "lib/events/events.h"
|
||||
#include "lib/util/dlinklist.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "smbd/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;
|
||||
int msg_type;
|
||||
struct messaging_context *msg_ctx;
|
||||
struct event_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)
|
||||
{
|
||||
struct pvfs_wait *pwait = private;
|
||||
pwait->handler(pwait->private, pwait->reason);
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
receive a completion message for a wait
|
||||
*/
|
||||
static void pvfs_wait_dispatch(struct messaging_context *msg, void *private, uint32_t msg_type,
|
||||
uint32_t src, DATA_BLOB *data)
|
||||
{
|
||||
struct pvfs_wait *pwait = private;
|
||||
struct ntvfs_request *req;
|
||||
|
||||
/* we need to check that this one is for us. See
|
||||
messaging_send_ptr() for the other side of this.
|
||||
*/
|
||||
if (data->length != sizeof(void *) ||
|
||||
*(void **)data->data != pwait->private) {
|
||||
return;
|
||||
}
|
||||
pwait->reason = PVFS_WAIT_EVENT;
|
||||
req = pwait->req;
|
||||
|
||||
/* 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 */
|
||||
talloc_increase_ref_count(req);
|
||||
ntvfs_async_setup(pwait->req, pwait);
|
||||
talloc_free(req);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
receive a timeout on a message wait
|
||||
*/
|
||||
static void pvfs_wait_timeout(struct event_context *ev,
|
||||
struct timed_event *te, struct timeval t, void *private)
|
||||
{
|
||||
struct pvfs_wait *pwait = talloc_get_type(private, struct pvfs_wait);
|
||||
struct ntvfs_request *req = pwait->req;
|
||||
|
||||
pwait->reason = PVFS_WAIT_TIMEOUT;
|
||||
|
||||
talloc_increase_ref_count(req);
|
||||
ntvfs_async_setup(pwait->req, pwait);
|
||||
talloc_free(req);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
destroy a pending wait
|
||||
*/
|
||||
static int pvfs_wait_destructor(struct pvfs_wait *pwait)
|
||||
{
|
||||
if (pwait->msg_type != -1) {
|
||||
messaging_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
|
||||
*/
|
||||
void *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)
|
||||
{
|
||||
struct pvfs_wait *pwait;
|
||||
|
||||
pwait = talloc(pvfs, struct pvfs_wait);
|
||||
if (pwait == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pwait->private = private;
|
||||
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 */
|
||||
event_add_timed(pwait->ev, pwait, end_time, pvfs_wait_timeout, pwait);
|
||||
}
|
||||
|
||||
/* register with the messaging subsystem for this message
|
||||
type */
|
||||
if (msg_type != -1) {
|
||||
messaging_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 = ntvfs->private_data;
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "librpc/gen_ndr/security.h"
|
||||
|
||||
|
||||
/*
|
||||
write to a file
|
||||
*/
|
||||
NTSTATUS pvfs_write(struct ntvfs_module_context *ntvfs,
|
||||
struct ntvfs_request *req, union smb_write *wr)
|
||||
{
|
||||
struct pvfs_state *pvfs = ntvfs->private_data;
|
||||
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_FILE_IS_A_DIRECTORY;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "lib/util/unix_privs.h"
|
||||
#include "librpc/gen_ndr/ndr_xattr.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(5,("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_xattr_tdb(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_xattr_tdb(pvfs, fname);
|
||||
}
|
||||
return unlink_xattr_system(pvfs, fname);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
load a NDR structure from a xattr
|
||||
*/
|
||||
_PUBLIC_ 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;
|
||||
|
||||
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 */
|
||||
status = ndr_pull_struct_blob(&blob, mem_ctx, p, (ndr_pull_flags_fn_t)pull_fn);
|
||||
|
||||
data_blob_free(&blob);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
save a NDR structure into a xattr
|
||||
*/
|
||||
_PUBLIC_ 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;
|
||||
|
||||
status = ndr_push_struct_blob(&blob, mem_ctx, p, (ndr_push_flags_fn_t)push_fn);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return status;
|
||||
}
|
||||
|
||||
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_DosInfo2 *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,
|
||||
(ndr_pull_flags_fn_t)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:
|
||||
info2 = &attrib.info.info2;
|
||||
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;
|
||||
if (name->dos.flags & XATTR_ATTRIB_FLAG_STICKY_WRITE_TIME) {
|
||||
name->dos.write_time = info2->write_time;
|
||||
}
|
||||
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_DosInfo2 *info2;
|
||||
|
||||
if (!(pvfs->flags & PVFS_FLAG_XATTR_ENABLE)) {
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
attrib.version = 2;
|
||||
info2 = &attrib.info.info2;
|
||||
|
||||
name->dos.attrib = pvfs_attrib_normalise(name->dos.attrib, name->st.st_mode);
|
||||
|
||||
info2->attrib = name->dos.attrib;
|
||||
info2->ea_size = name->dos.ea_size;
|
||||
info2->size = name->st.st_size;
|
||||
info2->alloc_size = name->dos.alloc_size;
|
||||
info2->create_time = name->dos.create_time;
|
||||
info2->change_time = name->dos.change_time;
|
||||
info2->write_time = name->dos.write_time;
|
||||
info2->flags = name->dos.flags;
|
||||
info2->name = "";
|
||||
|
||||
return pvfs_xattr_ndr_save(pvfs, name->full_name, fd,
|
||||
XATTR_DOSATTRIB_NAME, &attrib,
|
||||
(ndr_push_flags_fn_t)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 (!(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, (ndr_pull_flags_fn_t)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,
|
||||
(ndr_push_flags_fn_t)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,
|
||||
(ndr_pull_flags_fn_t)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,
|
||||
(ndr_push_flags_fn_t)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,
|
||||
(ndr_pull_flags_fn_t)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,
|
||||
(ndr_push_flags_fn_t)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);
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
/*
|
||||
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 "lib/tdb/include/tdb.h"
|
||||
#include "db_wrap.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "lib/events/events.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;
|
||||
const char *eadb;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
#if 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->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 |
|
||||
FS_ATTR_SPARSE_FILES;
|
||||
|
||||
/* allow xattrs to be stored in a external tdb */
|
||||
eadb = share_string_option(scfg, PVFS_EADB, NULL);
|
||||
if (eadb != NULL) {
|
||||
pvfs->ea_db = tdb_wrap_open(pvfs, eadb, 50000,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
pvfs->acl_ops = pvfs_acl_backend_byname(share_string_option(scfg, PVFS_ACL, "xattr"));
|
||||
}
|
||||
|
||||
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, const char *sharename)
|
||||
{
|
||||
struct pvfs_state *pvfs;
|
||||
struct stat st;
|
||||
char *base_directory;
|
||||
NTSTATUS 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 = talloc_strdup(pvfs, share_string_option(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);
|
||||
|
||||
ntvfs->private_data = pvfs;
|
||||
|
||||
pvfs->brl_context = brl_init(pvfs,
|
||||
pvfs->ntvfs->ctx->server_id,
|
||||
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,
|
||||
event_context_find(pvfs),
|
||||
pvfs->ntvfs->ctx->config);
|
||||
|
||||
pvfs->sidmap = sidmap_open(pvfs);
|
||||
if (pvfs->sidmap == NULL) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
/* 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 = ntvfs->private_data;
|
||||
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(void)
|
||||
{
|
||||
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 = pvfs_connect;
|
||||
ops.disconnect = pvfs_disconnect;
|
||||
ops.unlink = pvfs_unlink;
|
||||
ops.chkpath = pvfs_chkpath;
|
||||
ops.qpathinfo = pvfs_qpathinfo;
|
||||
ops.setpathinfo = pvfs_setpathinfo;
|
||||
ops.open = pvfs_open;
|
||||
ops.mkdir = pvfs_mkdir;
|
||||
ops.rmdir = pvfs_rmdir;
|
||||
ops.rename = pvfs_rename;
|
||||
ops.copy = pvfs_copy;
|
||||
ops.ioctl = pvfs_ioctl;
|
||||
ops.read = pvfs_read;
|
||||
ops.write = pvfs_write;
|
||||
ops.seek = pvfs_seek;
|
||||
ops.flush = pvfs_flush;
|
||||
ops.close = pvfs_close;
|
||||
ops.exit = pvfs_exit;
|
||||
ops.lock = pvfs_lock;
|
||||
ops.setfileinfo = pvfs_setfileinfo;
|
||||
ops.qfileinfo = pvfs_qfileinfo;
|
||||
ops.fsinfo = pvfs_fsinfo;
|
||||
ops.lpq = pvfs_lpq;
|
||||
ops.search_first = pvfs_search_first;
|
||||
ops.search_next = pvfs_search_next;
|
||||
ops.search_close = pvfs_search_close;
|
||||
ops.trans = pvfs_trans;
|
||||
ops.logoff = pvfs_logoff;
|
||||
ops.async_setup = pvfs_async_setup;
|
||||
ops.cancel = pvfs_cancel;
|
||||
ops.notify = 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;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#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/proto.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
|
||||
/* 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;
|
||||
uint_t flags;
|
||||
|
||||
struct pvfs_mangle_context *mangle_ctx;
|
||||
|
||||
struct brl_context *brl_context;
|
||||
struct odb_context *odb_context;
|
||||
struct notify_context *notify_context;
|
||||
struct sidmap_context *sidmap;
|
||||
|
||||
/* a list of pending async requests. Needed to support
|
||||
ntcancel */
|
||||
struct pvfs_wait *wait_list;
|
||||
|
||||
/* the sharing violation timeout */
|
||||
uint_t sharing_violation_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 */
|
||||
uint_t 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;
|
||||
};
|
||||
|
||||
/* 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 {
|
||||
const char *original_name;
|
||||
char *full_name;
|
||||
const 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 */
|
||||
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 this hook back to our parent for lock destruction */
|
||||
struct pvfs_state *pvfs;
|
||||
|
||||
/* have we set a sticky write time that we should remove on close */
|
||||
BOOL sticky_write_time;
|
||||
};
|
||||
|
||||
/* 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 brl_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;
|
||||
uint_t num_ea_names;
|
||||
struct ea_name *ea_names;
|
||||
struct timed_event *te;
|
||||
};
|
||||
|
||||
/* flags to pvfs_resolve_name() */
|
||||
#define PVFS_RESOLVE_WILDCARD (1<<0)
|
||||
#define PVFS_RESOLVE_STREAMS (1<<1)
|
||||
|
||||
/* 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)
|
||||
|
||||
/* 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};
|
||||
|
||||
#define PVFS_EADB "posix:eadb"
|
||||
#define PVFS_XATTR "posix:xattr"
|
||||
#define PVFS_FAKE_OPLOCKS "posix:fakeoplocks"
|
||||
#define PVFS_SHARE_DELAY "posix:sharedelay"
|
||||
#define PVFS_ALLOCATION_ROUNDING "posix:allocationrounding"
|
||||
#define PVFS_SEARCH_INACTIVITY "posix:searchinactivity"
|
||||
#define PVFS_ACL "posix:acl"
|
||||
|
||||
#define PVFS_XATTR_DEFAULT True
|
||||
#define PVFS_FAKE_OPLOCKS_DEFAULT False
|
||||
#define PVFS_SHARE_DELAY_DEFAULT 1000000
|
||||
#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"
|
||||
|
||||
#endif /* _VFS_POSIX_H_ */
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "lib/util/wrap_xattr.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 = wrap_fgetxattr(fd, attr_name, blob->data, estimated_size);
|
||||
} else {
|
||||
ret = wrap_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 legittimate 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 = wrap_fsetxattr(fd, attr_name, blob->data, blob->length, 0);
|
||||
} else {
|
||||
ret = wrap_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 = wrap_fremovexattr(fd, attr_name);
|
||||
} else {
|
||||
ret = wrap_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;
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
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 2 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, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "vfs_posix.h"
|
||||
#include "lib/tdb/include/tdb.h"
|
||||
#include "db_wrap.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 xattr_tdb_add_list(struct pvfs_state *pvfs, 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(pvfs);
|
||||
|
||||
status = pull_xattr_blob_tdb(pvfs, 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(pvfs, XATTR_LIST_ATTR, fname, fd, &blob);
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
form a key for using in the ea_db
|
||||
*/
|
||||
static NTSTATUS get_ea_db_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_db tdb
|
||||
*/
|
||||
NTSTATUS pull_xattr_blob_tdb(struct pvfs_state *pvfs,
|
||||
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_db_key(mem_ctx, attr_name, fname, fd, &tkey);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
tdata = tdb_fetch(pvfs->ea_db->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_db
|
||||
*/
|
||||
NTSTATUS push_xattr_blob_tdb(struct pvfs_state *pvfs,
|
||||
const char *attr_name,
|
||||
const char *fname,
|
||||
int fd,
|
||||
const DATA_BLOB *blob)
|
||||
{
|
||||
TDB_DATA tkey, tdata;
|
||||
NTSTATUS status;
|
||||
|
||||
status = get_ea_db_key(pvfs, attr_name, fname, fd, &tkey);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
tdata.dptr = blob->data;
|
||||
tdata.dsize = blob->length;
|
||||
|
||||
if (tdb_chainlock(pvfs->ea_db->tdb, tkey) != 0) {
|
||||
talloc_free(tkey.dptr);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
status = xattr_tdb_add_list(pvfs, attr_name, fname, fd);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (tdb_store(pvfs->ea_db->tdb, tkey, tdata, TDB_REPLACE) == -1) {
|
||||
status = NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
done:
|
||||
tdb_chainunlock(pvfs->ea_db->tdb, tkey);
|
||||
talloc_free(tkey.dptr);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
delete a xattr
|
||||
*/
|
||||
NTSTATUS delete_xattr_tdb(struct pvfs_state *pvfs, const char *attr_name,
|
||||
const char *fname, int fd)
|
||||
{
|
||||
TDB_DATA tkey;
|
||||
NTSTATUS status;
|
||||
|
||||
status = get_ea_db_key(NULL, attr_name, fname, fd, &tkey);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (tdb_delete(pvfs->ea_db->tdb, tkey) == -1) {
|
||||
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_xattr_tdb(struct pvfs_state *pvfs, const char *fname)
|
||||
{
|
||||
TALLOC_CTX *mem_ctx = talloc_new(pvfs);
|
||||
DATA_BLOB blob;
|
||||
const char *s;
|
||||
NTSTATUS status;
|
||||
|
||||
status = pull_xattr_blob_tdb(pvfs, mem_ctx, XATTR_LIST_ATTR,
|
||||
fname, -1, 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_xattr_tdb(pvfs, s, fname, -1);
|
||||
}
|
||||
|
||||
return delete_xattr_tdb(pvfs, XATTR_LIST_ATTR, fname, -1);
|
||||
}
|
||||
Reference in New Issue
Block a user