diff options
Diffstat (limited to 'source3/modules')
-rw-r--r-- | source3/modules/gpfs.c | 20 | ||||
-rw-r--r-- | source3/modules/nfs4_acls.c | 187 | ||||
-rw-r--r-- | source3/modules/vfs_aio_fork.c | 728 | ||||
-rw-r--r-- | source3/modules/vfs_default.c | 111 | ||||
-rw-r--r-- | source3/modules/vfs_gpfs.c | 281 | ||||
-rw-r--r-- | source3/modules/vfs_gpfs.h | 32 | ||||
-rw-r--r-- | source3/modules/vfs_hpuxacl.c | 4 | ||||
-rw-r--r-- | source3/modules/vfs_prealloc.c | 11 | ||||
-rw-r--r-- | source3/modules/vfs_recycle.c | 4 | ||||
-rw-r--r-- | source3/modules/vfs_shadow_copy2.c | 637 | ||||
-rw-r--r-- | source3/modules/vfs_streams_depot.c | 641 | ||||
-rw-r--r-- | source3/modules/vfs_streams_xattr.c | 685 | ||||
-rw-r--r-- | source3/modules/vfs_tsmsm.c | 365 | ||||
-rw-r--r-- | source3/modules/vfs_xattr_tdb.c | 36 |
14 files changed, 3632 insertions, 110 deletions
diff --git a/source3/modules/gpfs.c b/source3/modules/gpfs.c index 300e90fa69..590dbac26f 100644 --- a/source3/modules/gpfs.c +++ b/source3/modules/gpfs.c @@ -22,9 +22,11 @@ #ifdef HAVE_GPFS #include "gpfs_gpl.h" +#include "vfs_gpfs.h" static void *libgpfs_handle = NULL; static bool gpfs_share_modes; +static bool gpfs_leases; static int (*gpfs_set_share_fn)(int fd, unsigned int allow, unsigned int deny); static int (*gpfs_set_lease_fn)(int fd, unsigned int leaseType); @@ -42,7 +44,7 @@ bool set_gpfs_sharemode(files_struct *fsp, uint32 access_mask, if (!gpfs_share_modes) { return True; } - + if (gpfs_set_share_fn == NULL) { return False; } @@ -88,7 +90,7 @@ int set_gpfs_lease(int fd, int leasetype) { int gpfs_type = GPFS_LEASE_NONE; - if (!gpfs_share_modes) { + if (!gpfs_leases) { return True; } @@ -103,6 +105,13 @@ int set_gpfs_lease(int fd, int leasetype) if (leasetype == F_WRLCK) { gpfs_type = GPFS_LEASE_WRITE; } + + /* we unconditionally set CAP_LEASE, rather than looking for + -1/EACCES as there is a bug in some versions of + libgpfs_gpl.so which results in a leaked fd on /dev/ss0 + each time we try this with the wrong capabilities set + */ + linux_set_lease_capability(); return gpfs_set_lease_fn(fd, gpfs_type); } @@ -172,11 +181,8 @@ void init_gpfs(void) goto failed; } - if (lp_parm_bool(-1, "gpfs", "sharemodes", True)) { - gpfs_share_modes = True; - } else { - gpfs_share_modes = False; - } + gpfs_share_modes = lp_parm_bool(-1, "gpfs", "sharemodes", True); + gpfs_leases = lp_parm_bool(-1, "gpfs", "leases", True); return; diff --git a/source3/modules/nfs4_acls.c b/source3/modules/nfs4_acls.c index 52d3983fff..0c3d010dcd 100644 --- a/source3/modules/nfs4_acls.c +++ b/source3/modules/nfs4_acls.c @@ -20,6 +20,9 @@ #include "includes.h" #include "nfs4_acls.h" +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_ACLS + #define SMBACL4_PARAM_TYPE_NAME "nfs4" #define SMB_ACE4_INT_MAGIC 0x76F8A967 @@ -352,6 +355,7 @@ typedef struct _smbacl4_vfs_params { enum smbacl4_mode_enum mode; bool do_chown; enum smbacl4_acedup_enum acedup; + struct db_context *sid_mapping_table; } smbacl4_vfs_params; /* @@ -451,8 +455,65 @@ static SMB_ACE4PROP_T *smbacl4_find_equal_special( return NULL; } -static int smbacl4_fill_ace4( +static bool nfs4_map_sid(smbacl4_vfs_params *params, const DOM_SID *src, + DOM_SID *dst) +{ + static struct db_context *mapping_db = NULL; + TDB_DATA data; + + if (mapping_db == NULL) { + const char *dbname = lp_parm_const_string( + -1, SMBACL4_PARAM_TYPE_NAME, "sidmap", NULL); + + if (dbname == NULL) { + DEBUG(10, ("%s:sidmap not defined\n", + SMBACL4_PARAM_TYPE_NAME)); + return False; + } + + become_root(); + mapping_db = db_open(NULL, dbname, 0, TDB_DEFAULT, + O_RDONLY, 0600); + unbecome_root(); + + if (mapping_db == NULL) { + DEBUG(1, ("could not open sidmap: %s\n", + strerror(errno))); + return False; + } + } + + if (mapping_db->fetch(mapping_db, NULL, + string_term_tdb_data(sid_string_tos(src)), + &data) == -1) { + DEBUG(10, ("could not find mapping for SID %s\n", + sid_string_dbg(src))); + return False; + } + + if ((data.dptr == NULL) || (data.dsize <= 0) + || (data.dptr[data.dsize-1] != '\0')) { + DEBUG(5, ("invalid mapping for SID %s\n", + sid_string_dbg(src))); + TALLOC_FREE(data.dptr); + return False; + } + + if (!string_to_sid(dst, (char *)data.dptr)) { + DEBUG(1, ("invalid mapping %s for SID %s\n", + (char *)data.dptr, sid_string_dbg(src))); + TALLOC_FREE(data.dptr); + return False; + } + + TALLOC_FREE(data.dptr); + + return True; +} + +static bool smbacl4_fill_ace4( TALLOC_CTX *mem_ctx, + const char *filename, smbacl4_vfs_params *params, uid_t ownerUID, gid_t ownerGID, @@ -460,11 +521,6 @@ static int smbacl4_fill_ace4( SMB_ACE4PROP_T *ace_v4 /* output */ ) { - const char *dom, *name; - enum lsa_SidType type; - uid_t uid; - gid_t gid; - DEBUG(10, ("got ace for %s\n", sid_string_dbg(&ace_nt->trustee))); memset(ace_v4, 0, sizeof(SMB_ACE4PROP_T)); @@ -485,18 +541,46 @@ static int smbacl4_fill_ace4( ace_v4->who.special_id = SMB_ACE4_WHO_EVERYONE; ace_v4->flags |= SMB_ACE4_ID_SPECIAL; } else { - if (!lookup_sid(mem_ctx, &ace_nt->trustee, &dom, &name, &type)) { - DEBUG(8, ("Could not find %s' type\n", - sid_string_dbg(&ace_nt->trustee))); - errno = EINVAL; - return -1; + const char *dom, *name; + enum lsa_SidType type; + uid_t uid; + gid_t gid; + DOM_SID sid; + + sid_copy(&sid, &ace_nt->trustee); + + if (!lookup_sid(mem_ctx, &sid, &dom, &name, &type)) { + + DOM_SID mapped; + + if (!nfs4_map_sid(params, &sid, &mapped)) { + DEBUG(1, ("nfs4_acls.c: file [%s]: SID %s " + "unknown\n", filename, sid_string_dbg(&sid))); + errno = EINVAL; + return False; + } + + DEBUG(2, ("nfs4_acls.c: file [%s]: mapped SID %s " + "to %s\n", filename, sid_string_dbg(&sid), sid_string_dbg(&mapped))); + + if (!lookup_sid(mem_ctx, &mapped, &dom, + &name, &type)) { + DEBUG(1, ("nfs4_acls.c: file [%s]: SID %s " + "mapped from %s is unknown\n", + filename, sid_string_dbg(&mapped), sid_string_dbg(&sid))); + errno = EINVAL; + return False; + } + + sid_copy(&sid, &mapped); } - + if (type == SID_NAME_USER) { - if (!sid_to_uid(&ace_nt->trustee, &uid)) { - DEBUG(2, ("Could not convert %s to uid\n", - sid_string_dbg(&ace_nt->trustee))); - return -1; + if (!sid_to_uid(&sid, &uid)) { + DEBUG(1, ("nfs4_acls.c: file [%s]: could not " + "convert %s to uid\n", filename, + sid_string_dbg(&sid))); + return False; } if (params->mode==e_special && uid==ownerUID) { @@ -506,11 +590,13 @@ static int smbacl4_fill_ace4( ace_v4->who.uid = uid; } } else { /* else group? - TODO check it... */ - if (!sid_to_gid(&ace_nt->trustee, &gid)) { - DEBUG(2, ("Could not convert %s to gid\n", - sid_string_dbg(&ace_nt->trustee))); - return -1; + if (!sid_to_gid(&sid, &gid)) { + DEBUG(1, ("nfs4_acls.c: file [%s]: could not " + "convert %s to gid\n", filename, + sid_string_dbg(&sid))); + return False; } + ace_v4->aceFlags |= SMB_ACE4_IDENTIFIER_GROUP; if (params->mode==e_special && gid==ownerGID) { @@ -522,7 +608,7 @@ static int smbacl4_fill_ace4( } } - return 0; /* OK */ + return True; /* OK */ } static int smbacl4_MergeIgnoreReject( @@ -560,6 +646,7 @@ static int smbacl4_MergeIgnoreReject( } static SMB4ACL_T *smbacl4_win2nfs4( + const char *filename, SEC_ACL *dacl, smbacl4_vfs_params *pparams, uid_t ownerUID, @@ -580,9 +667,14 @@ static SMB4ACL_T *smbacl4_win2nfs4( SMB_ACE4PROP_T ace_v4; bool addNewACE = True; - if (smbacl4_fill_ace4(mem_ctx, pparams, ownerUID, ownerGID, - dacl->aces + i, &ace_v4)) - return NULL; + if (!smbacl4_fill_ace4(mem_ctx, filename, pparams, + ownerUID, ownerGID, + dacl->aces + i, &ace_v4)) { + DEBUG(3, ("Could not fill ace for file %s, SID %s\n", + filename, + sid_string_dbg(&((dacl->aces+i)->trustee)))); + continue; + } if (pparams->acedup!=e_dontcare) { if (smbacl4_MergeIgnoreReject(pparams->acedup, acl, @@ -607,6 +699,7 @@ NTSTATUS smb_set_nt_acl_nfs4(files_struct *fsp, bool result; SMB_STRUCT_STAT sbuf; + bool need_chown = False; uid_t newUID = (uid_t)-1; gid_t newGID = (gid_t)-1; @@ -635,25 +728,33 @@ NTSTATUS smb_set_nt_acl_nfs4(files_struct *fsp, return status; } if (((newUID != (uid_t)-1) && (sbuf.st_uid != newUID)) || - ((newGID != (gid_t)-1) && (sbuf.st_gid != newGID))) { - if(try_chown(fsp->conn, fsp->fsp_name, newUID, newGID)) { - DEBUG(3,("chown %s, %u, %u failed. Error = %s.\n", - fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID, strerror(errno) )); - if (errno == EPERM) { - return NT_STATUS_INVALID_OWNER; + ((newGID != (gid_t)-1) && (sbuf.st_gid != newGID))) { + need_chown = True; + } + if (need_chown) { + if ((newUID == (uid_t)-1 || newUID == current_user.ut.uid)) { + if(try_chown(fsp->conn, fsp->fsp_name, newUID, newGID)) { + DEBUG(3,("chown %s, %u, %u failed. Error = %s.\n", + fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID, + strerror(errno))); + return map_nt_error_from_unix(errno); } - return map_nt_error_from_unix(errno); + + DEBUG(10,("chown %s, %u, %u succeeded.\n", + fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID)); + if (smbacl4_GetFileOwner(fsp->conn, fsp->fsp_name, &sbuf)) + return map_nt_error_from_unix(errno); + need_chown = False; + } else { /* chown is needed, but _after_ changing acl */ + sbuf.st_uid = newUID; /* OWNER@ in case of e_special */ + sbuf.st_gid = newGID; /* GROUP@ in case of e_special */ } - DEBUG(10,("chown %s, %u, %u succeeded.\n", - fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID)); - if (smbacl4_fGetFileOwner(fsp, &sbuf)) - return map_nt_error_from_unix(errno); } } if ((security_info_sent & DACL_SECURITY_INFORMATION)!=0 && psd->dacl!=NULL) { - acl = smbacl4_win2nfs4(psd->dacl, ¶ms, sbuf.st_uid, sbuf.st_gid); + acl = smbacl4_win2nfs4(fsp->fsp_name, psd->dacl, ¶ms, sbuf.st_uid, sbuf.st_gid); if (!acl) return map_nt_error_from_unix(errno); @@ -668,6 +769,20 @@ NTSTATUS smb_set_nt_acl_nfs4(files_struct *fsp, } else DEBUG(10, ("no dacl found; security_info_sent = 0x%x\n", security_info_sent)); + /* Any chown pending? */ + if (need_chown) { + DEBUG(3,("chown#2 %s. uid = %u, gid = %u.\n", + fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID)); + if (try_chown(fsp->conn, fsp->fsp_name, newUID, newGID)) { + DEBUG(2,("chown#2 %s, %u, %u failed. Error = %s.\n", + fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID, + strerror(errno))); + return map_nt_error_from_unix(errno); + } + DEBUG(10,("chown#2 %s, %u, %u succeeded.\n", + fsp->fsp_name, (unsigned int)newUID, (unsigned int)newGID)); + } + DEBUG(10, ("smb_set_nt_acl_nfs4 succeeded\n")); return NT_STATUS_OK; } diff --git a/source3/modules/vfs_aio_fork.c b/source3/modules/vfs_aio_fork.c new file mode 100644 index 0000000000..21f63d0b87 --- /dev/null +++ b/source3/modules/vfs_aio_fork.c @@ -0,0 +1,728 @@ +/* + * Simulate the Posix AIO using mmap/fork + * + * Copyright (C) Volker Lendecke 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 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" + +struct mmap_area { + size_t size; + volatile void *ptr; +}; + +static int mmap_area_destructor(struct mmap_area *area) +{ + munmap((void *)area->ptr, area->size); + return 0; +} + +static struct mmap_area *mmap_area_init(TALLOC_CTX *mem_ctx, size_t size) +{ + struct mmap_area *result; + int fd; + + result = talloc(mem_ctx, struct mmap_area); + if (result == NULL) { + DEBUG(0, ("talloc failed\n")); + goto fail; + } + + fd = open("/dev/zero", O_RDWR); + if (fd == -1) { + DEBUG(3, ("open(\"/dev/zero\") failed: %s\n", + strerror(errno))); + goto fail; + } + + result->ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, fd, 0); + if (result->ptr == MAP_FAILED) { + DEBUG(1, ("mmap failed: %s\n", strerror(errno))); + goto fail; + } + + result->size = size; + talloc_set_destructor(result, mmap_area_destructor); + + return result; + +fail: + TALLOC_FREE(result); + return NULL; +} + +struct rw_cmd { + size_t n; + SMB_OFF_T offset; + bool read_cmd; +}; + +struct rw_ret { + ssize_t size; + int ret_errno; +}; + +struct aio_child_list; + +struct aio_child { + struct aio_child *prev, *next; + struct aio_child_list *list; + SMB_STRUCT_AIOCB *aiocb; + pid_t pid; + int sockfd; + struct fd_event *sock_event; + struct rw_ret retval; + struct mmap_area *map; /* ==NULL means write request */ + bool dont_delete; /* Marked as in use since last cleanup */ + bool cancelled; + bool read_cmd; +}; + +struct aio_child_list { + struct aio_child *children; + struct timed_event *cleanup_event; +}; + +static void free_aio_children(void **p) +{ + TALLOC_FREE(*p); +} + +static ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) +{ + struct msghdr msg; + struct iovec iov[1]; + ssize_t n; +#ifndef HAVE_MSGHDR_MSG_CONTROL + int newfd; +#endif + +#ifdef HAVE_MSGHDR_MSG_CONTROL + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmptr; + + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); +#else +#if HAVE_MSGHDR_MSG_ACCTRIGHTS + msg.msg_accrights = (caddr_t) &newfd; + msg.msg_accrightslen = sizeof(int); +#else +#error Can not pass file descriptors +#endif +#endif + + msg.msg_name = NULL; + msg.msg_namelen = 0; + + iov[0].iov_base = ptr; + iov[0].iov_len = nbytes; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if ( (n = recvmsg(fd, &msg, 0)) <= 0) { + return(n); + } + +#ifdef HAVE_MSGHDR_MSG_CONTROL + if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL + && cmptr->cmsg_len == CMSG_LEN(sizeof(int))) { + if (cmptr->cmsg_level != SOL_SOCKET) { + DEBUG(10, ("control level != SOL_SOCKET")); + errno = EINVAL; + return -1; + } + if (cmptr->cmsg_type != SCM_RIGHTS) { + DEBUG(10, ("control type != SCM_RIGHTS")); + errno = EINVAL; + return -1; + } + *recvfd = *((int *) CMSG_DATA(cmptr)); + } else { + *recvfd = -1; /* descriptor was not passed */ + } +#else + if (msg.msg_accrightslen == sizeof(int)) { + *recvfd = newfd; + } + else { + *recvfd = -1; /* descriptor was not passed */ + } +#endif + + return(n); +} + +static ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd) +{ + struct msghdr msg; + struct iovec iov[1]; + +#ifdef HAVE_MSGHDR_MSG_CONTROL + union { + struct cmsghdr cm; + char control[CMSG_SPACE(sizeof(int))]; + } control_un; + struct cmsghdr *cmptr; + + ZERO_STRUCT(msg); + ZERO_STRUCT(control_un); + + msg.msg_control = control_un.control; + msg.msg_controllen = sizeof(control_un.control); + + cmptr = CMSG_FIRSTHDR(&msg); + cmptr->cmsg_len = CMSG_LEN(sizeof(int)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + *((int *) CMSG_DATA(cmptr)) = sendfd; +#else + ZERO_STRUCT(msg); + msg.msg_accrights = (caddr_t) &sendfd; + msg.msg_accrightslen = sizeof(int); +#endif + + msg.msg_name = NULL; + msg.msg_namelen = 0; + + ZERO_STRUCT(iov); + iov[0].iov_base = ptr; + iov[0].iov_len = nbytes; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + return (sendmsg(fd, &msg, 0)); +} + +static void aio_child_cleanup(struct event_context *event_ctx, + struct timed_event *te, + const struct timeval *now, + void *private_data) +{ + struct aio_child_list *list = talloc_get_type_abort( + private_data, struct aio_child_list); + struct aio_child *child, *next; + + TALLOC_FREE(list->cleanup_event); + + for (child = list->children; child != NULL; child = next) { + next = child->next; + + if (child->aiocb != NULL) { + DEBUG(10, ("child %d currently active\n", + (int)child->pid)); + continue; + } + + if (child->dont_delete) { + DEBUG(10, ("Child %d was active since last cleanup\n", + (int)child->pid)); + child->dont_delete = false; + continue; + } + + DEBUG(10, ("Child %d idle for more than 30 seconds, " + "deleting\n", (int)child->pid)); + + TALLOC_FREE(child); + } + + if (list->children != NULL) { + /* + * Re-schedule the next cleanup round + */ + list->cleanup_event = event_add_timed(smbd_event_context(), list, + timeval_add(now, 30, 0), + "aio_child_cleanup", + aio_child_cleanup, list); + + } +} + +static struct aio_child_list *init_aio_children(struct vfs_handle_struct *handle) +{ + struct aio_child_list *data = NULL; + + if (SMB_VFS_HANDLE_TEST_DATA(handle)) { + SMB_VFS_HANDLE_GET_DATA(handle, data, struct aio_child_list, + return NULL); + } + + if (data == NULL) { + data = TALLOC_ZERO_P(NULL, struct aio_child_list); + if (data == NULL) { + return NULL; + } + } + + /* + * Regardless of whether the child_list had been around or not, make + * sure that we have a cleanup timed event. This timed event will + * delete itself when it finds that no children are around anymore. + */ + + if (data->cleanup_event == NULL) { + data->cleanup_event = event_add_timed(smbd_event_context(), data, + timeval_current_ofs(30, 0), + "aio_child_cleanup", + aio_child_cleanup, data); + if (data->cleanup_event == NULL) { + TALLOC_FREE(data); + return NULL; + } + } + + if (!SMB_VFS_HANDLE_TEST_DATA(handle)) { + SMB_VFS_HANDLE_SET_DATA(handle, data, free_aio_children, + struct aio_child_list, return False); + } + + return data; +} + +static void aio_child_loop(int sockfd, struct mmap_area *map) +{ + while (true) { + int fd = -1; + ssize_t ret; + struct rw_cmd cmd_struct; + struct rw_ret ret_struct; + + ret = read_fd(sockfd, &cmd_struct, sizeof(cmd_struct), &fd); + if (ret != sizeof(cmd_struct)) { + DEBUG(10, ("read_fd returned %d: %s\n", (int)ret, + strerror(errno))); + exit(1); + } + + DEBUG(10, ("aio_child_loop: %s %d bytes at %d from fd %d\n", + cmd_struct.read_cmd ? "read" : "write", + (int)cmd_struct.n, (int)cmd_struct.offset, fd)); + +#ifdef ENABLE_BUILD_FARM_HACKS + { + /* + * In the build farm, we want erratic behaviour for + * async I/O times + */ + uint8_t randval; + unsigned msecs; + /* + * use generate_random_buffer, we just forked from a + * common parent state + */ + generate_random_buffer(&randval, sizeof(randval)); + msecs = randval + 20; + DEBUG(10, ("delaying for %u msecs\n", msecs)); + smb_msleep(msecs); + } +#endif + + + ZERO_STRUCT(ret_struct); + + if (cmd_struct.read_cmd) { + ret_struct.size = sys_pread( + fd, (void *)map->ptr, cmd_struct.n, + cmd_struct.offset); + } + else { + ret_struct.size = sys_pwrite( + fd, (void *)map->ptr, cmd_struct.n, + cmd_struct.offset); + } + + DEBUG(10, ("aio_child_loop: syscall returned %d\n", + (int)ret_struct.size)); + + if (ret_struct.size == -1) { + ret_struct.ret_errno = errno; + } + + ret = write_data(sockfd, (char *)&ret_struct, + sizeof(ret_struct)); + if (ret != sizeof(ret_struct)) { + DEBUG(10, ("could not write ret_struct: %s\n", + strerror(errno))); + exit(2); + } + + close(fd); + } +} + +static void handle_aio_completion(struct event_context *event_ctx, + struct fd_event *event, uint16 flags, + void *p) +{ + struct aio_child *child = (struct aio_child *)p; + uint16 mid; + + DEBUG(10, ("handle_aio_completion called with flags=%d\n", flags)); + + if ((flags & EVENT_FD_READ) == 0) { + return; + } + + if (!NT_STATUS_IS_OK(read_data(child->sockfd, + (char *)&child->retval, + sizeof(child->retval)))) { + DEBUG(0, ("aio child %d died\n", (int)child->pid)); + child->retval.size = -1; + child->retval.ret_errno = EIO; + } + + if (child->cancelled) { + child->aiocb = NULL; + child->cancelled = false; + return; + } + + if (child->read_cmd && (child->retval.size > 0)) { + SMB_ASSERT(child->retval.size <= child->aiocb->aio_nbytes); + memcpy((void *)child->aiocb->aio_buf, (void *)child->map->ptr, + child->retval.size); + } + + mid = child->aiocb->aio_sigevent.sigev_value.sival_int; + + DEBUG(10, ("mid %d finished\n", (int)mid)); + + aio_request_done(mid); + process_aio_queue(); +} + +static int aio_child_destructor(struct aio_child *child) +{ + SMB_ASSERT((child->aiocb == NULL) || child->cancelled); + close(child->sockfd); + DLIST_REMOVE(child->list->children, child); + return 0; +} + +static NTSTATUS create_aio_child(struct aio_child_list *children, + size_t map_size, + struct aio_child **presult) +{ + struct aio_child *result; + int fdpair[2]; + NTSTATUS status; + + fdpair[0] = fdpair[1] = -1; + + result = TALLOC_ZERO_P(children, struct aio_child); + NT_STATUS_HAVE_NO_MEMORY(result); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) { + status = map_nt_error_from_unix(errno); + DEBUG(10, ("socketpair() failed: %s\n", strerror(errno))); + TALLOC_FREE(result); + goto fail; + } + + DEBUG(10, ("fdpair = %d/%d\n", fdpair[0], fdpair[1])); + + result->map = mmap_area_init(result, map_size); + if (result->map == NULL) { + status = map_nt_error_from_unix(errno); + DEBUG(0, ("Could not create mmap area\n")); + goto fail; + } + + result->pid = sys_fork(); + if (result->pid == -1) { + status = map_nt_error_from_unix(errno); + DEBUG(0, ("fork failed: %s\n", strerror(errno))); + goto fail; + } + + if (result->pid == 0) { + close(fdpair[0]); + result->sockfd = fdpair[1]; + aio_child_loop(result->sockfd, result->map); + } + + DEBUG(10, ("Child %d created\n", result->pid)); + + result->sockfd = fdpair[0]; + close(fdpair[1]); + + result->sock_event = event_add_fd(smbd_event_context(), result, + result->sockfd, EVENT_FD_READ, + handle_aio_completion, + result); + if (result->sock_event == NULL) { + status = NT_STATUS_NO_MEMORY; + DEBUG(0, ("event_add_fd failed\n")); + goto fail; + } + + result->list = children; + DLIST_ADD(children->children, result); + + talloc_set_destructor(result, aio_child_destructor); + + *presult = result; + + return NT_STATUS_OK; + + fail: + if (fdpair[0] != -1) close(fdpair[0]); + if (fdpair[1] != -1) close(fdpair[1]); + TALLOC_FREE(result); + + return status; +} + +static NTSTATUS get_idle_child(struct vfs_handle_struct *handle, + struct aio_child **pchild) +{ + struct aio_child_list *children; + struct aio_child *child; + NTSTATUS status; + + children = init_aio_children(handle); + if (children == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (child = children->children; child != NULL; child = child->next) { + if (child->aiocb == NULL) { + /* idle */ + break; + } + } + + if (child == NULL) { + DEBUG(10, ("no idle child found, creating new one\n")); + + status = create_aio_child(children, 128*1024, &child); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("create_aio_child failed: %s\n", + nt_errstr(status))); + return status; + } + } + + child->dont_delete = true; + + *pchild = child; + return NT_STATUS_OK; +} + +static int aio_fork_read(struct vfs_handle_struct *handle, + struct files_struct *fsp, SMB_STRUCT_AIOCB *aiocb) +{ + struct aio_child *child; + struct rw_cmd cmd; + ssize_t ret; + NTSTATUS status; + + if (aiocb->aio_nbytes > 128*1024) { + /* TODO: support variable buffers */ + errno = EINVAL; + return -1; + } + + status = get_idle_child(handle, &child); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Could not get an idle child\n")); + return -1; + } + + child->read_cmd = true; + child->aiocb = aiocb; + child->retval.ret_errno = EINPROGRESS; + + ZERO_STRUCT(cmd); + cmd.n = aiocb->aio_nbytes; + cmd.offset = aiocb->aio_offset; + cmd.read_cmd = child->read_cmd; + + DEBUG(10, ("sending fd %d to child %d\n", fsp->fh->fd, + (int)child->pid)); + + ret = write_fd(child->sockfd, &cmd, sizeof(cmd), fsp->fh->fd); + if (ret == -1) { + DEBUG(10, ("write_fd failed: %s\n", strerror(errno))); + return -1; + } + + return 0; +} + +static int aio_fork_write(struct vfs_handle_struct *handle, + struct files_struct *fsp, SMB_STRUCT_AIOCB *aiocb) +{ + struct aio_child *child; + struct rw_cmd cmd; + ssize_t ret; + NTSTATUS status; + + if (aiocb->aio_nbytes > 128*1024) { + /* TODO: support variable buffers */ + errno = EINVAL; + return -1; + } + + status = get_idle_child(handle, &child); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Could not get an idle child\n")); + return -1; + } + + child->read_cmd = false; + child->aiocb = aiocb; + child->retval.ret_errno = EINPROGRESS; + + memcpy((void *)child->map->ptr, (void *)aiocb->aio_buf, + aiocb->aio_nbytes); + + ZERO_STRUCT(cmd); + cmd.n = aiocb->aio_nbytes; + cmd.offset = aiocb->aio_offset; + cmd.read_cmd = child->read_cmd; + + DEBUG(10, ("sending fd %d to child %d\n", fsp->fh->fd, + (int)child->pid)); + + ret = write_fd(child->sockfd, &cmd, sizeof(cmd), fsp->fh->fd); + if (ret == -1) { + DEBUG(10, ("write_fd failed: %s\n", strerror(errno))); + return -1; + } + + return 0; +} + +static struct aio_child *aio_fork_find_child(struct vfs_handle_struct *handle, + SMB_STRUCT_AIOCB *aiocb) +{ + struct aio_child_list *children; + struct aio_child *child; + + children = init_aio_children(handle); + if (children == NULL) { + return NULL; + } + + for (child = children->children; child != NULL; child = child->next) { + if (child->aiocb == aiocb) { + return child; + } + } + + return NULL; +} + +static ssize_t aio_fork_return_fn(struct vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_STRUCT_AIOCB *aiocb) +{ + struct aio_child *child = aio_fork_find_child(handle, aiocb); + + if (child == NULL) { + errno = EINVAL; + DEBUG(0, ("returning EINVAL\n")); + return -1; + } + + child->aiocb = NULL; + + if (child->retval.size == -1) { + errno = child->retval.ret_errno; + } + + return child->retval.size; +} + +static int aio_fork_cancel(struct vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_STRUCT_AIOCB *aiocb) +{ + struct aio_child_list *children; + struct aio_child *child; + + children = init_aio_children(handle); + if (children == NULL) { + errno = EINVAL; + return -1; + } + + for (child = children->children; child != NULL; child = child->next) { + if (child->aiocb == NULL) { + continue; + } + if (child->aiocb->aio_fildes != fsp->fh->fd) { + continue; + } + if ((aiocb != NULL) && (child->aiocb != aiocb)) { + continue; + } + + /* + * We let the child do its job, but we discard the result when + * it's finished. + */ + + child->cancelled = true; + } + + return AIO_CANCELED; +} + +static int aio_fork_error_fn(struct vfs_handle_struct *handle, + struct files_struct *fsp, + SMB_STRUCT_AIOCB *aiocb) +{ + struct aio_child *child = aio_fork_find_child(handle, aiocb); + + if (child == NULL) { + errno = EINVAL; + return -1; + } + + return child->retval.ret_errno; +} + +/* VFS operations structure */ + +static vfs_op_tuple aio_fork_ops[] = { + {SMB_VFS_OP(aio_fork_read), SMB_VFS_OP_AIO_READ, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(aio_fork_write), SMB_VFS_OP_AIO_WRITE, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(aio_fork_return_fn), SMB_VFS_OP_AIO_RETURN, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(aio_fork_cancel), SMB_VFS_OP_AIO_CANCEL, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(aio_fork_error_fn), SMB_VFS_OP_AIO_ERROR, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, + SMB_VFS_LAYER_NOOP} +}; + +NTSTATUS vfs_aio_fork_init(void); +NTSTATUS vfs_aio_fork_init(void) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "aio_fork", aio_fork_ops); +} diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c index e21136ccee..31ebb6352a 100644 --- a/source3/modules/vfs_default.c +++ b/source3/modules/vfs_default.c @@ -90,6 +90,17 @@ static int vfswrap_statvfs(struct vfs_handle_struct *handle, const char *path, return sys_statvfs(path, statbuf); } +static uint32_t vfswrap_fs_capabilities(struct vfs_handle_struct *handle) +{ +#if defined(DARWINOS) + struct vfs_statvfs_struct statbuf; + ZERO_STRUCT(statbuf); + sys_statvfs(handle->conn->connectpath, &statbuf); + return statbuf.FsCapabilities; +#endif + return FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES; +} + /* Directory operations */ static SMB_STRUCT_DIR *vfswrap_opendir(vfs_handle_struct *handle, const char *fname, const char *mask, uint32 attr) @@ -457,7 +468,7 @@ static int vfswrap_rename(vfs_handle_struct *handle, const char *oldname, const START_PROFILE(syscall_rename); result = rename(oldname, newname); - if (errno == EXDEV) { + if ((result == -1) && (errno == EXDEV)) { /* Rename across filesystems needed. */ result = copy_reg(oldname, newname); } @@ -942,6 +953,62 @@ static struct file_id vfswrap_file_id_create(struct vfs_handle_struct *handle, S return file_id_create_dev(dev, inode); } +static NTSTATUS vfswrap_streaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + const char *fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + SMB_STRUCT_STAT sbuf; + unsigned int num_streams = 0; + struct stream_struct *streams = NULL; + int ret; + + if ((fsp != NULL) && (fsp->is_directory)) { + /* + * No default streams on directories + */ + goto done; + } + + if ((fsp != NULL) && (fsp->fh->fd != -1)) { + ret = SMB_VFS_FSTAT(fsp, &sbuf); + } + else { + ret = SMB_VFS_STAT(handle->conn, fname, &sbuf); + } + + if (ret == -1) { + return map_nt_error_from_unix(errno); + } + + if (S_ISDIR(sbuf.st_mode)) { + goto done; + } + + streams = talloc(mem_ctx, struct stream_struct); + + if (streams == NULL) { + return NT_STATUS_NO_MEMORY; + } + + streams->size = sbuf.st_size; + streams->alloc_size = get_allocation_size(handle->conn, fsp, &sbuf); + + streams->name = talloc_strdup(streams, "::$DATA"); + if (streams->name == NULL) { + TALLOC_FREE(streams); + return NT_STATUS_NO_MEMORY; + } + + num_streams = 1; + done: + *pnum_streams = num_streams; + *pstreams = streams; + return NT_STATUS_OK; +} + static NTSTATUS vfswrap_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32 security_info, SEC_DESC **ppdesc) @@ -1225,6 +1292,36 @@ static int vfswrap_aio_suspend(struct vfs_handle_struct *handle, struct files_st return sys_aio_suspend(aiocb, n, timeout); } +static bool vfswrap_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp) +{ + return false; +} + +static bool vfswrap_is_offline(struct vfs_handle_struct *handle, const char *path, SMB_STRUCT_STAT *sbuf) +{ + if (ISDOT(path) || ISDOTDOT(path)) { + return false; + } + + if (!lp_dmapi_support(SNUM(handle->conn)) || !dmapi_have_session()) { +#if defined(ENOTSUP) + errno = ENOTSUP; +#endif + return false; + } + + return (dmapi_file_flags(path) & FILE_ATTRIBUTE_OFFLINE) != 0; +} + +static int vfswrap_set_offline(struct vfs_handle_struct *handle, const char *path) +{ + /* We don't know how to set offline bit by default, needs to be overriden in the vfs modules */ +#if defined(ENOTSUP) + errno = ENOTSUP; +#endif + return -1; +} + static vfs_op_tuple vfs_default_ops[] = { /* Disk operations */ @@ -1243,6 +1340,8 @@ static vfs_op_tuple vfs_default_ops[] = { SMB_VFS_LAYER_OPAQUE}, {SMB_VFS_OP(vfswrap_statvfs), SMB_VFS_OP_STATVFS, SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(vfswrap_fs_capabilities), SMB_VFS_OP_FS_CAPABILITIES, + SMB_VFS_LAYER_OPAQUE}, /* Directory operations */ @@ -1337,6 +1436,8 @@ static vfs_op_tuple vfs_default_ops[] = { SMB_VFS_LAYER_OPAQUE}, {SMB_VFS_OP(vfswrap_file_id_create), SMB_VFS_OP_FILE_ID_CREATE, SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(vfswrap_streaminfo), SMB_VFS_OP_STREAMINFO, + SMB_VFS_LAYER_OPAQUE}, /* NT ACL operations. */ @@ -1442,6 +1543,14 @@ static vfs_op_tuple vfs_default_ops[] = { {SMB_VFS_OP(vfswrap_aio_suspend),SMB_VFS_OP_AIO_SUSPEND, SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(vfswrap_aio_force), SMB_VFS_OP_AIO_FORCE, + SMB_VFS_LAYER_OPAQUE}, + + {SMB_VFS_OP(vfswrap_is_offline),SMB_VFS_OP_IS_OFFLINE, + SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(vfswrap_set_offline),SMB_VFS_OP_SET_OFFLINE, + SMB_VFS_LAYER_OPAQUE}, + /* Finish VFS operations definition */ {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, diff --git a/source3/modules/vfs_gpfs.c b/source3/modules/vfs_gpfs.c index bcf61f3bc7..d10906dfb1 100644 --- a/source3/modules/vfs_gpfs.c +++ b/source3/modules/vfs_gpfs.c @@ -30,7 +30,7 @@ #include <gpfs_gpl.h> #include "nfs4_acls.h" - +#include "vfs_gpfs.h" static int vfs_gpfs_kernel_flock(vfs_handle_struct *handle, files_struct *fsp, uint32 share_mode) @@ -153,7 +153,7 @@ static int gpfs_get_nfs4_acl(const char *fname, SMB4ACL_T **ppacl) DEBUG(10, ("gpfs_get_nfs4_acl invoked for %s\n", fname)); /* First get the real acl length */ - gacl = gpfs_getacl_alloc(fname, GPFS_ACL_TYPE_NFS4); + gacl = gpfs_getacl_alloc(fname, 0); if (gacl == NULL) { DEBUG(9, ("gpfs_getacl failed for %s with %s\n", fname, strerror(errno))); @@ -208,10 +208,10 @@ static int gpfs_get_nfs4_acl(const char *fname, SMB4ACL_T **ppacl) if (i > 0 && gace->aceType == SMB_ACE4_ACCESS_DENIED_ACE_TYPE) { struct gpfs_ace_v4 *prev = &gacl->ace_v4[i-1]; if (prev->aceType == SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE && - prev->aceFlags == gace->aceFlags && - prev->aceIFlags == gace->aceIFlags && - (gace->aceMask & prev->aceMask) == 0 && - gace->aceWho == prev->aceWho) { + prev->aceFlags == gace->aceFlags && + prev->aceIFlags == gace->aceIFlags && + (gace->aceMask & prev->aceMask) == 0 && + gace->aceWho == prev->aceWho) { /* its redundent - skip it */ continue; } @@ -256,7 +256,7 @@ static NTSTATUS gpfsacl_get_nt_acl(vfs_handle_struct *handle, int result; *ppdesc = NULL; - result = gpfs_get_nfs4_acl(fsp->fsp_name, &pacl); + result = gpfs_get_nfs4_acl(name, &pacl); if (result == 0) return smb_get_nt_acl_nfs4(handle->conn, name, security_info, ppdesc, pacl); @@ -301,8 +301,31 @@ static bool gpfsacl_process_smbacl(files_struct *fsp, SMB4ACL_T *smbacl) gace->aceType = aceprop->aceType; gace->aceFlags = aceprop->aceFlags; gace->aceMask = aceprop->aceMask; + + /* + * GPFS can't distinguish between WRITE and APPEND on + * files, so one being set without the other is an + * error. Sorry for the many ()'s :-) + */ + + if (!fsp->is_directory + && + ((((gace->aceMask & ACE4_MASK_WRITE) == 0) + && ((gace->aceMask & ACE4_MASK_APPEND) != 0)) + || + (((gace->aceMask & ACE4_MASK_WRITE) != 0) + && ((gace->aceMask & ACE4_MASK_APPEND) == 0))) + && + lp_parm_bool(fsp->conn->params->service, "gpfs", + "merge_writeappend", True)) { + DEBUG(2, ("vfs_gpfs.c: file [%s]: ACE contains " + "WRITE^APPEND, setting WRITE|APPEND\n", + fsp->fsp_name)); + gace->aceMask |= ACE4_MASK_WRITE|ACE4_MASK_APPEND; + } + gace->aceIFlags = (aceprop->flags&SMB_ACE4_ID_SPECIAL) ? ACE4_IFLAG_SPECIAL_ID : 0; - + if (aceprop->flags&SMB_ACE4_ID_SPECIAL) { switch(aceprop->who.special_id) @@ -347,7 +370,7 @@ static NTSTATUS gpfsacl_set_nt_acl_internal(files_struct *fsp, uint32 security_i struct gpfs_acl *acl; NTSTATUS result = NT_STATUS_ACCESS_DENIED; - acl = gpfs_getacl_alloc(fsp->fsp_name, GPFS_ACL_TYPE_ACCESS); + acl = gpfs_getacl_alloc(fsp->fsp_name, 0); if (acl == NULL) return result; @@ -628,75 +651,225 @@ int gpfsacl_sys_acl_delete_def_file(vfs_handle_struct *handle, return -1; } +/* + * Assumed: mode bits are shiftable and standard + * Output: the new aceMask field for an smb nfs4 ace + */ +static uint32 gpfsacl_mask_filter(uint32 aceType, uint32 aceMask, uint32 rwx) +{ + const uint32 posix_nfs4map[3] = { + SMB_ACE4_EXECUTE, /* execute */ + SMB_ACE4_WRITE_DATA | SMB_ACE4_APPEND_DATA, /* write; GPFS specific */ + SMB_ACE4_READ_DATA /* read */ + }; + int i; + uint32_t posix_mask = 0x01; + uint32_t posix_bit; + uint32_t nfs4_bits; + + for(i=0; i<3; i++) { + nfs4_bits = posix_nfs4map[i]; + posix_bit = rwx & posix_mask; + + if (aceType==SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) { + if (posix_bit) + aceMask |= nfs4_bits; + else + aceMask &= ~nfs4_bits; + } else { + /* add deny bits when suitable */ + if (!posix_bit) + aceMask |= nfs4_bits; + else + aceMask &= ~nfs4_bits; + } /* other ace types are unexpected */ + + posix_mask <<= 1; + } + + return aceMask; +} + +static int gpfsacl_emu_chmod(const char *path, mode_t mode) +{ + SMB4ACL_T *pacl = NULL; + int result; + bool haveAllowEntry[SMB_ACE4_WHO_EVERYONE + 1] = {False, False, False, False}; + int i; + files_struct fake_fsp; /* TODO: rationalize parametrization */ + SMB4ACE_T *smbace; + + DEBUG(10, ("gpfsacl_emu_chmod invoked for %s mode %o\n", path, mode)); + + result = gpfs_get_nfs4_acl(path, &pacl); + if (result) + return result; + + if (mode & ~(S_IRWXU | S_IRWXG | S_IRWXO)) { + DEBUG(2, ("WARNING: cutting extra mode bits %o on %s\n", mode, path)); + } + + for (smbace=smb_first_ace4(pacl); smbace!=NULL; smbace = smb_next_ace4(smbace)) { + SMB_ACE4PROP_T *ace = smb_get_ace4(smbace); + uint32_t specid = ace->who.special_id; + + if (ace->flags&SMB_ACE4_ID_SPECIAL && + ace->aceType<=SMB_ACE4_ACCESS_DENIED_ACE_TYPE && + specid <= SMB_ACE4_WHO_EVERYONE) { + + uint32_t newMask; + + if (ace->aceType==SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) + haveAllowEntry[specid] = True; + + /* mode >> 6 for @owner, mode >> 3 for @group, + * mode >> 0 for @everyone */ + newMask = gpfsacl_mask_filter(ace->aceType, ace->aceMask, + mode >> ((SMB_ACE4_WHO_EVERYONE - specid) * 3)); + if (ace->aceMask!=newMask) { + DEBUG(10, ("ace changed for %s (%o -> %o) id=%d\n", + path, ace->aceMask, newMask, specid)); + } + ace->aceMask = newMask; + } + } + + /* make sure we have at least ALLOW entries + * for all the 3 special ids (@EVERYONE, @OWNER, @GROUP) + * - if necessary + */ + for(i = SMB_ACE4_WHO_OWNER; i<=SMB_ACE4_WHO_EVERYONE; i++) { + SMB_ACE4PROP_T ace; + + if (haveAllowEntry[i]==True) + continue; + + memset(&ace, 0, sizeof(SMB_ACE4PROP_T)); + ace.aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE; + ace.flags |= SMB_ACE4_ID_SPECIAL; + ace.who.special_id = i; + + if (i==SMB_ACE4_WHO_GROUP) /* not sure it's necessary... */ + ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP; + + ace.aceMask = gpfsacl_mask_filter(ace.aceType, ace.aceMask, + mode >> ((SMB_ACE4_WHO_EVERYONE - i) * 3)); + + /* don't add unnecessary aces */ + if (!ace.aceMask) + continue; + + /* we add it to the END - as windows expects allow aces */ + smb_add_ace4(pacl, &ace); + DEBUG(10, ("Added ALLOW ace for %s, mode=%o, id=%d, aceMask=%x\n", + path, mode, i, ace.aceMask)); + } + + /* don't add complementary DENY ACEs here */ + memset(&fake_fsp, 0, sizeof(struct files_struct)); + fake_fsp.fsp_name = (char *)path; /* no file_new is needed here */ + + /* put the acl */ + if (gpfsacl_process_smbacl(&fake_fsp, pacl) == False) + return -1; + return 0; /* ok for [f]chmod */ +} + static int vfs_gpfs_chmod(vfs_handle_struct *handle, const char *path, mode_t mode) { SMB_STRUCT_STAT st; + int rc; + if (SMB_VFS_NEXT_STAT(handle, path, &st) != 0) { - return -1; + return -1; } + /* avoid chmod() if possible, to preserve acls */ if ((st.st_mode & ~S_IFMT) == mode) { - return 0; + return 0; } - return SMB_VFS_NEXT_CHMOD(handle, path, mode); + + rc = gpfsacl_emu_chmod(path, mode); + if (rc == 1) + return SMB_VFS_NEXT_CHMOD(handle, path, mode); + return rc; } static int vfs_gpfs_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode) { SMB_STRUCT_STAT st; + int rc; + if (SMB_VFS_NEXT_FSTAT(handle, fsp, &st) != 0) { - return -1; + return -1; } + /* avoid chmod() if possible, to preserve acls */ if ((st.st_mode & ~S_IFMT) == mode) { - return 0; + return 0; } - return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + + rc = gpfsacl_emu_chmod(fsp->fsp_name, mode); + if (rc == 1) + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); + return rc; } /* VFS operations structure */ static vfs_op_tuple gpfs_op_tuples[] = { - - { SMB_VFS_OP(vfs_gpfs_kernel_flock), SMB_VFS_OP_KERNEL_FLOCK, - SMB_VFS_LAYER_OPAQUE }, - - { SMB_VFS_OP(vfs_gpfs_setlease), SMB_VFS_OP_LINUX_SETLEASE, - SMB_VFS_LAYER_OPAQUE }, - - { SMB_VFS_OP(gpfsacl_fget_nt_acl), SMB_VFS_OP_FGET_NT_ACL, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_get_nt_acl), SMB_VFS_OP_GET_NT_ACL, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_fset_nt_acl), SMB_VFS_OP_FSET_NT_ACL, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_set_nt_acl), SMB_VFS_OP_SET_NT_ACL, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_sys_acl_get_file), SMB_VFS_OP_SYS_ACL_GET_FILE, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_sys_acl_get_fd), SMB_VFS_OP_SYS_ACL_GET_FD, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_sys_acl_set_file), SMB_VFS_OP_SYS_ACL_SET_FILE, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(gpfsacl_sys_acl_set_fd), SMB_VFS_OP_SYS_ACL_SET_FD, - SMB_VFS_LAYER_TRANSPARENT }, - + + { SMB_VFS_OP(vfs_gpfs_kernel_flock), + SMB_VFS_OP_KERNEL_FLOCK, + SMB_VFS_LAYER_OPAQUE }, + + { SMB_VFS_OP(vfs_gpfs_setlease), + SMB_VFS_OP_LINUX_SETLEASE, + SMB_VFS_LAYER_OPAQUE }, + + { SMB_VFS_OP(gpfsacl_fget_nt_acl), + SMB_VFS_OP_FGET_NT_ACL, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_get_nt_acl), + SMB_VFS_OP_GET_NT_ACL, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_fset_nt_acl), + SMB_VFS_OP_FSET_NT_ACL, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_set_nt_acl), + SMB_VFS_OP_SET_NT_ACL, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_sys_acl_get_file), + SMB_VFS_OP_SYS_ACL_GET_FILE, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_sys_acl_get_fd), + SMB_VFS_OP_SYS_ACL_GET_FD, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_sys_acl_set_file), + SMB_VFS_OP_SYS_ACL_SET_FILE, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(gpfsacl_sys_acl_set_fd), + SMB_VFS_OP_SYS_ACL_SET_FD, + SMB_VFS_LAYER_TRANSPARENT }, + { SMB_VFS_OP(gpfsacl_sys_acl_delete_def_file), - SMB_VFS_OP_SYS_ACL_DELETE_DEF_FILE, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(vfs_gpfs_chmod), SMB_VFS_OP_CHMOD, - SMB_VFS_LAYER_TRANSPARENT }, - - { SMB_VFS_OP(vfs_gpfs_fchmod), SMB_VFS_OP_FCHMOD, - SMB_VFS_LAYER_TRANSPARENT }, + SMB_VFS_OP_SYS_ACL_DELETE_DEF_FILE, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(vfs_gpfs_chmod), + SMB_VFS_OP_CHMOD, + SMB_VFS_LAYER_TRANSPARENT }, + + { SMB_VFS_OP(vfs_gpfs_fchmod), + SMB_VFS_OP_FCHMOD, + SMB_VFS_LAYER_TRANSPARENT }, { SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP } diff --git a/source3/modules/vfs_gpfs.h b/source3/modules/vfs_gpfs.h new file mode 100644 index 0000000000..3c499b0850 --- /dev/null +++ b/source3/modules/vfs_gpfs.h @@ -0,0 +1,32 @@ +/* + Unix SMB/CIFS implementation. + Wrap gpfs calls in vfs functions. + + Copyright (C) Christian Ambach <cambach1@de.ibm.com> 2006 + + Major code contributions by Chetan Shringarpure <chetan.sh@in.ibm.com> + and Gomati Mohanan <gomati.mohanan@in.ibm.com> + + 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. + + +*/ + +bool set_gpfs_sharemode(files_struct *fsp, uint32 access_mask, + uint32 share_access); +int set_gpfs_lease(int fd, int leasetype); +int smbd_gpfs_getacl(char *pathname, int flags, void *acl); +int smbd_gpfs_putacl(char *pathname, int flags, void *acl); +void init_gpfs(void); diff --git a/source3/modules/vfs_hpuxacl.c b/source3/modules/vfs_hpuxacl.c index e101886450..f9293405fb 100644 --- a/source3/modules/vfs_hpuxacl.c +++ b/source3/modules/vfs_hpuxacl.c @@ -140,7 +140,7 @@ SMB_ACL_T hpuxacl_sys_acl_get_file(vfs_handle_struct *handle, { SMB_ACL_T result = NULL; int count; - HPUX_ACL_T hpux_acl; + HPUX_ACL_T hpux_acl = NULL; DEBUG(10, ("hpuxacl_sys_acl_get_file called for file '%s'.\n", path_p)); @@ -213,7 +213,7 @@ int hpuxacl_sys_acl_set_file(vfs_handle_struct *handle, { int ret = -1; SMB_STRUCT_STAT s; - HPUX_ACL_T hpux_acl; + HPUX_ACL_T hpux_acl = NULL; int count; DEBUG(10, ("hpuxacl_sys_acl_set_file called for file '%s'\n", diff --git a/source3/modules/vfs_prealloc.c b/source3/modules/vfs_prealloc.c index 2d64bc0184..2a06e3d81b 100644 --- a/source3/modules/vfs_prealloc.c +++ b/source3/modules/vfs_prealloc.c @@ -47,11 +47,16 @@ #define lock_type struct flock64 #endif +#ifdef HAVE_GPFS +#include "gpfs_gpl.h" +#endif + #define MODULE "prealloc" static int module_debug; static int preallocate_space(int fd, SMB_OFF_T size) { +#ifndef HAVE_GPFS lock_type fl = {0}; int err; @@ -78,6 +83,9 @@ static int preallocate_space(int fd, SMB_OFF_T size) err = -1; errno = ENOSYS; #endif +#else /* GPFS uses completely different interface */ + err = gpfs_prealloc(fd, (gpfs_off64_t)0, (gpfs_off64_t)size); +#endif if (err) { DEBUG(module_debug, @@ -191,7 +199,7 @@ static int prealloc_ftruncate(vfs_handle_struct * handle, /* Maintain the allocated space even in the face of truncates. */ if ((psize = VFS_FETCH_FSP_EXTENSION(handle, fsp))) { - preallocate_space(fd, *psize); + preallocate_space(fsp->fh->fd, *psize); } return ret; @@ -210,4 +218,3 @@ NTSTATUS vfs_prealloc_init(void) return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE, prealloc_op_tuples); } - diff --git a/source3/modules/vfs_recycle.c b/source3/modules/vfs_recycle.c index fef65efa77..da1716719a 100644 --- a/source3/modules/vfs_recycle.c +++ b/source3/modules/vfs_recycle.c @@ -269,6 +269,7 @@ static bool recycle_create_dir(vfs_handle_struct *handle, const char *dname) char *token; char *tok_str; bool ret = False; + char *saveptr; mode = recycle_directory_mode(handle); @@ -286,7 +287,8 @@ static bool recycle_create_dir(vfs_handle_struct *handle, const char *dname) } /* Create directory tree if neccessary */ - for(token = strtok(tok_str, "/"); token; token = strtok(NULL, "/")) { + for(token = strtok_r(tok_str, "/", &saveptr); token; + token = strtok_r(NULL, "/", &saveptr)) { safe_strcat(new_dir, token, len); if (recycle_directory_exist(handle, new_dir)) DEBUG(10, ("recycle: dir %s already exists\n", new_dir)); diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c new file mode 100644 index 0000000000..ddbc5aab18 --- /dev/null +++ b/source3/modules/vfs_shadow_copy2.c @@ -0,0 +1,637 @@ +/* + * implementation of an Shadow Copy module - version 2 + * + * Copyright (C) Andrew Tridgell 2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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" + +/* + + This is a 2nd implemetation of a shadow copy module for exposing + snapshots to windows clients as shadow copies. This version has the + following features: + + 1) you don't need to populate your shares with symlinks to the + snapshots. This can be very important when you have thousands of + shares, or use [homes] + + 2) the inode number of the files is altered so it is different + from the original. This allows the 'restore' button to work + without a sharing violation + + Module options: + + shadow:snapdir = <directory where snapshots are kept> + + This is the directory containing the @GMT-* snapshot directories. If it is an absolute + path it is used as-is. If it is a relative path, then it is taken relative to the mount + point of the filesystem that the root of this share is on + + shadow:basedir = <base directory that snapshots are from> + + This is an optional parameter that specifies the directory that + the snapshots are relative to. It defaults to the filesystem + mount point + + shadow:fixinodes = yes/no + + If you enable shadow:fixinodes then this module will modify the + apparent inode number of files in the snapshot directories using + a hash of the files path. This is needed for snapshot systems + where the snapshots have the same device:inode number as the + original files (such as happens with GPFS snapshots). If you + don't set this option then the 'restore' button in the shadow + copy UI will fail with a sharing violation. + + Note that the directory names in the snapshot directory must take the form + @GMT-YYYY.MM.DD-HH.MM.SS + + The following command would generate a correctly formatted directory name: + date -u +@GMT-%Y.%m.%d-%H.%M.%S + + */ + +static int vfs_shadow_copy2_debug_level = DBGC_VFS; + +#undef DBGC_CLASS +#define DBGC_CLASS vfs_shadow_copy2_debug_level + +#define GMT_NAME_LEN 24 /* length of a @GMT- name */ + +/* + make very sure it is one of our special names + */ +static inline bool shadow_copy2_match_name(const char *name) +{ + unsigned year, month, day, hr, min, sec; + if (name[0] != '@') return False; + if (strncmp(name, "@GMT-", 5) != 0) return False; + if (sscanf(name, "@GMT-%04u.%02u.%02u-%02u.%02u.%02u", &year, &month, + &day, &hr, &min, &sec) != 6) { + return False; + } + if (name[24] != 0 && name[24] != '/') { + return False; + } + return True; +} + +/* + convert a name to the shadow directory + */ + +#define _SHADOW2_NEXT(op, args, rtype, eret, extra) do { \ + const char *name = fname; \ + if (shadow_copy2_match_name(fname)) { \ + char *name2; \ + rtype ret; \ + name2 = convert_shadow2_name(handle, fname); \ + if (name2 == NULL) { \ + errno = EINVAL; \ + return eret; \ + } \ + name = name2; \ + ret = SMB_VFS_NEXT_ ## op args; \ + talloc_free(name2); \ + if (ret != eret) extra; \ + return ret; \ + } else { \ + return SMB_VFS_NEXT_ ## op args; \ + } \ +} while (0) + +/* + convert a name to the shadow directory: NTSTATUS-specific handling + */ + +#define _SHADOW2_NTSTATUS_NEXT(op, args, eret, extra) do { \ + const char *name = fname; \ + if (shadow_copy2_match_name(fname)) { \ + char *name2; \ + NTSTATUS ret; \ + name2 = convert_shadow2_name(handle, fname); \ + if (name2 == NULL) { \ + errno = EINVAL; \ + return eret; \ + } \ + name = name2; \ + ret = SMB_VFS_NEXT_ ## op args; \ + talloc_free(name2); \ + if (!NT_STATUS_EQUAL(ret, eret)) extra; \ + return ret; \ + } else { \ + return SMB_VFS_NEXT_ ## op args; \ + } \ +} while (0) + +#define SHADOW2_NTSTATUS_NEXT(op, args, eret) _SHADOW2_NTSTATUS_NEXT(op, args, eret, ) + +#define SHADOW2_NEXT(op, args, rtype, eret) _SHADOW2_NEXT(op, args, rtype, eret, ) + +#define SHADOW2_NEXT2(op, args) do { \ + if (shadow_copy2_match_name(oldname) || shadow_copy2_match_name(newname)) { \ + errno = EROFS; \ + return -1; \ + } else { \ + return SMB_VFS_NEXT_ ## op args; \ + } \ +} while (0) + + +/* + find the mount point of a filesystem + */ +static char *find_mount_point(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) +{ + char *path = talloc_strdup(mem_ctx, handle->conn->connectpath); + dev_t dev; + struct stat st; + char *p; + + if (stat(path, &st) != 0) { + talloc_free(path); + return NULL; + } + + dev = st.st_dev; + + while ((p = strrchr(path, '/')) && p > path) { + *p = 0; + if (stat(path, &st) != 0) { + talloc_free(path); + return NULL; + } + if (st.st_dev != dev) { + *p = '/'; + break; + } + } + + return path; +} + +/* + work out the location of the snapshot for this share + */ +static const char *shadow_copy2_find_snapdir(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) +{ + const char *snapdir; + char *mount_point; + const char *ret; + + snapdir = lp_parm_const_string(SNUM(handle->conn), "shadow", "snapdir", NULL); + if (snapdir == NULL) { + return NULL; + } + /* if its an absolute path, we're done */ + if (*snapdir == '/') { + return snapdir; + } + + /* other its relative to the filesystem mount point */ + mount_point = find_mount_point(mem_ctx, handle); + if (mount_point == NULL) { + return NULL; + } + + ret = talloc_asprintf(mem_ctx, "%s/%s", mount_point, snapdir); + talloc_free(mount_point); + return ret; +} + +/* + work out the location of the base directory for snapshots of this share + */ +static const char *shadow_copy2_find_basedir(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle) +{ + const char *basedir = lp_parm_const_string(SNUM(handle->conn), "shadow", "basedir", NULL); + + /* other its the filesystem mount point */ + if (basedir == NULL) { + basedir = find_mount_point(mem_ctx, handle); + } + + return basedir; +} + +/* + convert a filename from a share relative path, to a path in the + snapshot directory + */ +static char *convert_shadow2_name(vfs_handle_struct *handle, const char *fname) +{ + TALLOC_CTX *tmp_ctx = talloc_new(handle->data); + const char *snapdir, *relpath, *baseoffset, *basedir; + size_t baselen; + char *ret; + + snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle); + if (snapdir == NULL) { + DEBUG(2,("no snapdir found for share at %s\n", handle->conn->connectpath)); + talloc_free(tmp_ctx); + return NULL; + } + + basedir = shadow_copy2_find_basedir(tmp_ctx, handle); + if (basedir == NULL) { + DEBUG(2,("no basedir found for share at %s\n", handle->conn->connectpath)); + talloc_free(tmp_ctx); + return NULL; + } + + relpath = fname + GMT_NAME_LEN; + baselen = strlen(basedir); + baseoffset = handle->conn->connectpath + baselen; + + /* some sanity checks */ + if (strncmp(basedir, handle->conn->connectpath, baselen) != 0 || + (handle->conn->connectpath[baselen] != 0 && handle->conn->connectpath[baselen] != '/')) { + DEBUG(0,("convert_shadow2_name: basedir %s is not a parent of %s\n", + basedir, handle->conn->connectpath)); + talloc_free(tmp_ctx); + return NULL; + } + + if (*relpath == '/') relpath++; + if (*baseoffset == '/') baseoffset++; + + ret = talloc_asprintf(handle->data, "%s/%.*s/%s/%s", + snapdir, + GMT_NAME_LEN, fname, + baseoffset, + relpath); + DEBUG(6,("convert_shadow2_name: '%s' -> '%s'\n", fname, ret)); + talloc_free(tmp_ctx); + return ret; +} + + +/* + simple string hash + */ +static uint32 string_hash(const char *s) +{ + uint32 n = 0; + while (*s) { + n = ((n << 5) + n) ^ (uint32)(*s++); + } + return n; +} + +/* + modify a sbuf return to ensure that inodes in the shadow directory + are different from those in the main directory + */ +static void convert_sbuf(vfs_handle_struct *handle, const char *fname, SMB_STRUCT_STAT *sbuf) +{ + if (lp_parm_bool(SNUM(handle->conn), "shadow", "fixinodes", False)) { + /* some snapshot systems, like GPFS, return the name + device:inode for the snapshot files as the current + files. That breaks the 'restore' button in the shadow copy + GUI, as the client gets a sharing violation. + + This is a crude way of allowing both files to be + open at once. It has a slight chance of inode + number collision, but I can't see a better approach + without significant VFS changes + */ + uint32_t shash = string_hash(fname) & 0xFF000000; + if (shash == 0) { + shash = 1; + } + sbuf->st_ino ^= shash; + } +} + +static int shadow_copy2_rename(vfs_handle_struct *handle, + const char *oldname, const char *newname) +{ + SHADOW2_NEXT2(RENAME, (handle, oldname, newname)); +} + +static int shadow_copy2_symlink(vfs_handle_struct *handle, + const char *oldname, const char *newname) +{ + SHADOW2_NEXT2(SYMLINK, (handle, oldname, newname)); +} + +static int shadow_copy2_link(vfs_handle_struct *handle, + const char *oldname, const char *newname) +{ + SHADOW2_NEXT2(LINK, (handle, oldname, newname)); +} + +static int shadow_copy2_open(vfs_handle_struct *handle, + const char *fname, files_struct *fsp, int flags, mode_t mode) +{ + SHADOW2_NEXT(OPEN, (handle, name, fsp, flags, mode), int, -1); +} + +static SMB_STRUCT_DIR *shadow_copy2_opendir(vfs_handle_struct *handle, + const char *fname, const char *mask, uint32 attr) +{ + SHADOW2_NEXT(OPENDIR, (handle, name, mask, attr), SMB_STRUCT_DIR *, NULL); +} + +static int shadow_copy2_stat(vfs_handle_struct *handle, + const char *fname, SMB_STRUCT_STAT *sbuf) +{ + _SHADOW2_NEXT(STAT, (handle, name, sbuf), int, -1, convert_sbuf(handle, fname, sbuf)); +} + +static int shadow_copy2_lstat(vfs_handle_struct *handle, + const char *fname, SMB_STRUCT_STAT *sbuf) +{ + _SHADOW2_NEXT(LSTAT, (handle, name, sbuf), int, -1, convert_sbuf(handle, fname, sbuf)); +} + +static int shadow_copy2_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf) +{ + int ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret == 0 && shadow_copy2_match_name(fsp->fsp_name)) { + convert_sbuf(handle, fsp->fsp_name, sbuf); + } + return ret; +} + +static int shadow_copy2_unlink(vfs_handle_struct *handle, const char *fname) +{ + SHADOW2_NEXT(UNLINK, (handle, name), int, -1); +} + +static int shadow_copy2_chmod(vfs_handle_struct *handle, + const char *fname, mode_t mode) +{ + SHADOW2_NEXT(CHMOD, (handle, name, mode), int, -1); +} + +static int shadow_copy2_chown(vfs_handle_struct *handle, + const char *fname, uid_t uid, gid_t gid) +{ + SHADOW2_NEXT(CHOWN, (handle, name, uid, gid), int, -1); +} + +static int shadow_copy2_chdir(vfs_handle_struct *handle, + const char *fname) +{ + SHADOW2_NEXT(CHDIR, (handle, name), int, -1); +} + +static int shadow_copy2_ntimes(vfs_handle_struct *handle, + const char *fname, const struct timespec ts[2]) +{ + SHADOW2_NEXT(NTIMES, (handle, name, ts), int, -1); +} + +static int shadow_copy2_readlink(vfs_handle_struct *handle, + const char *fname, char *buf, size_t bufsiz) +{ + SHADOW2_NEXT(READLINK, (handle, name, buf, bufsiz), int, -1); +} + +static int shadow_copy2_mknod(vfs_handle_struct *handle, + const char *fname, mode_t mode, SMB_DEV_T dev) +{ + SHADOW2_NEXT(MKNOD, (handle, name, mode, dev), int, -1); +} + +static char *shadow_copy2_realpath(vfs_handle_struct *handle, + const char *fname, char *resolved_path) +{ + SHADOW2_NEXT(REALPATH, (handle, name, resolved_path), char *, NULL); +} + +static NTSTATUS shadow_copy2_get_nt_acl(vfs_handle_struct *handle, + const char *fname, uint32 security_info, + struct security_descriptor **ppdesc) +{ + SHADOW2_NTSTATUS_NEXT(GET_NT_ACL, (handle, name, security_info, ppdesc), NT_STATUS_ACCESS_DENIED); +} + +static NTSTATUS shadow_copy2_set_nt_acl(vfs_handle_struct *handle, files_struct *fsp, + const char *fname, uint32 security_info_sent, + struct security_descriptor *psd) +{ + SHADOW2_NTSTATUS_NEXT(SET_NT_ACL, (handle, fsp, name, security_info_sent, psd), NT_STATUS_ACCESS_DENIED); +} + +static int shadow_copy2_mkdir(vfs_handle_struct *handle, const char *fname, mode_t mode) +{ + SHADOW2_NEXT(MKDIR, (handle, name, mode), int, -1); +} + +static int shadow_copy2_rmdir(vfs_handle_struct *handle, const char *fname) +{ + SHADOW2_NEXT(RMDIR, (handle, name), int, -1); +} + +static int shadow_copy2_chflags(vfs_handle_struct *handle, const char *fname, int flags) +{ + SHADOW2_NEXT(CHFLAGS, (handle, name, flags), int, -1); +} + +static ssize_t shadow_copy2_getxattr(vfs_handle_struct *handle, + const char *fname, const char *aname, void *value, size_t size) +{ + SHADOW2_NEXT(GETXATTR, (handle, name, aname, value, size), ssize_t, -1); +} + +static ssize_t shadow_copy2_lgetxattr(vfs_handle_struct *handle, + const char *fname, const char *aname, void *value, size_t size) +{ + SHADOW2_NEXT(LGETXATTR, (handle, name, aname, value, size), ssize_t, -1); +} + +static ssize_t shadow_copy2_listxattr(struct vfs_handle_struct *handle, const char *fname, + char *list, size_t size) +{ + SHADOW2_NEXT(LISTXATTR, (handle, name, list, size), ssize_t, -1); +} + +static int shadow_copy2_removexattr(struct vfs_handle_struct *handle, const char *fname, + const char *aname) +{ + SHADOW2_NEXT(REMOVEXATTR, (handle, name, aname), int, -1); +} + +static int shadow_copy2_lremovexattr(struct vfs_handle_struct *handle, const char *fname, + const char *aname) +{ + SHADOW2_NEXT(LREMOVEXATTR, (handle, name, aname), int, -1); +} + +static int shadow_copy2_setxattr(struct vfs_handle_struct *handle, const char *fname, + const char *aname, const void *value, size_t size, int flags) +{ + SHADOW2_NEXT(SETXATTR, (handle, name, aname, value, size, flags), int, -1); +} + +static int shadow_copy2_lsetxattr(struct vfs_handle_struct *handle, const char *fname, + const char *aname, const void *value, size_t size, int flags) +{ + SHADOW2_NEXT(LSETXATTR, (handle, name, aname, value, size, flags), int, -1); +} + +static int shadow_copy2_chmod_acl(vfs_handle_struct *handle, + const char *fname, mode_t mode) +{ + /* If the underlying VFS doesn't have ACL support... */ + if (!handle->vfs_next.ops.chmod_acl) { + errno = ENOSYS; + return -1; + } + SHADOW2_NEXT(CHMOD_ACL, (handle, name, mode), int, -1); +} + +static int shadow_copy2_get_shadow_copy2_data(vfs_handle_struct *handle, + files_struct *fsp, + SHADOW_COPY_DATA *shadow_copy2_data, + bool labels) +{ + SMB_STRUCT_DIR *p; + const char *snapdir; + SMB_STRUCT_DIRENT *d; + TALLOC_CTX *tmp_ctx = talloc_new(handle->data); + + snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle); + if (snapdir == NULL) { + DEBUG(0,("shadow:snapdir not found for %s in get_shadow_copy_data\n", + handle->conn->connectpath)); + errno = EINVAL; + talloc_free(tmp_ctx); + return -1; + } + + p = SMB_VFS_NEXT_OPENDIR(handle, snapdir, NULL, 0); + + if (!p) { + DEBUG(0,("shadow_copy2: SMB_VFS_NEXT_OPENDIR() failed for '%s' - %s\n", + snapdir, strerror(errno))); + talloc_free(tmp_ctx); + return -1; + } + + talloc_free(tmp_ctx); + + shadow_copy2_data->num_volumes = 0; + shadow_copy2_data->labels = NULL; + + while ((d = SMB_VFS_NEXT_READDIR(handle, p))) { + SHADOW_COPY_LABEL *tlabels; + + /* ignore names not of the right form in the snapshot directory */ + if (!shadow_copy2_match_name(d->d_name)) { + continue; + } + + if (!labels) { + /* the caller doesn't want the labels */ + shadow_copy2_data->num_volumes++; + continue; + } + + tlabels = talloc_realloc(shadow_copy2_data->mem_ctx, + shadow_copy2_data->labels, + SHADOW_COPY_LABEL, shadow_copy2_data->num_volumes+1); + if (tlabels == NULL) { + DEBUG(0,("shadow_copy2: out of memory\n")); + SMB_VFS_NEXT_CLOSEDIR(handle, p); + return -1; + } + + strlcpy(tlabels[shadow_copy2_data->num_volumes], d->d_name, sizeof(*tlabels)); + shadow_copy2_data->num_volumes++; + shadow_copy2_data->labels = tlabels; + } + + SMB_VFS_NEXT_CLOSEDIR(handle,p); + return 0; +} + +/* VFS operations structure */ + +static vfs_op_tuple shadow_copy2_ops[] = { + {SMB_VFS_OP(shadow_copy2_opendir), SMB_VFS_OP_OPENDIR, SMB_VFS_LAYER_TRANSPARENT}, + + /* directory operations */ + {SMB_VFS_OP(shadow_copy2_mkdir), SMB_VFS_OP_MKDIR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_rmdir), SMB_VFS_OP_RMDIR, SMB_VFS_LAYER_TRANSPARENT}, + + /* xattr and flags operations */ + {SMB_VFS_OP(shadow_copy2_chflags), SMB_VFS_OP_CHFLAGS, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_getxattr), SMB_VFS_OP_GETXATTR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_lgetxattr), SMB_VFS_OP_LGETXATTR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_listxattr), SMB_VFS_OP_LISTXATTR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_removexattr), SMB_VFS_OP_REMOVEXATTR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_lremovexattr),SMB_VFS_OP_LREMOVEXATTR,SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_setxattr), SMB_VFS_OP_SETXATTR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_lsetxattr), SMB_VFS_OP_LSETXATTR, SMB_VFS_LAYER_TRANSPARENT}, + + /* File operations */ + {SMB_VFS_OP(shadow_copy2_open), SMB_VFS_OP_OPEN, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_rename), SMB_VFS_OP_RENAME, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_stat), SMB_VFS_OP_STAT, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_lstat), SMB_VFS_OP_LSTAT, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_fstat), SMB_VFS_OP_FSTAT, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_unlink), SMB_VFS_OP_UNLINK, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_chmod), SMB_VFS_OP_CHMOD, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_chown), SMB_VFS_OP_CHOWN, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_chdir), SMB_VFS_OP_CHDIR, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_ntimes), SMB_VFS_OP_NTIMES, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_symlink), SMB_VFS_OP_SYMLINK, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_readlink), SMB_VFS_OP_READLINK, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_link), SMB_VFS_OP_LINK, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_mknod), SMB_VFS_OP_MKNOD, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_realpath), SMB_VFS_OP_REALPATH, SMB_VFS_LAYER_TRANSPARENT}, + + /* NT File ACL operations */ + {SMB_VFS_OP(shadow_copy2_get_nt_acl), SMB_VFS_OP_GET_NT_ACL, SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(shadow_copy2_set_nt_acl), SMB_VFS_OP_SET_NT_ACL, SMB_VFS_LAYER_TRANSPARENT}, + + /* POSIX ACL operations */ + {SMB_VFS_OP(shadow_copy2_chmod_acl), SMB_VFS_OP_CHMOD_ACL, SMB_VFS_LAYER_TRANSPARENT}, + + /* special shadown copy op */ + {SMB_VFS_OP(shadow_copy2_get_shadow_copy2_data), + SMB_VFS_OP_GET_SHADOW_COPY_DATA,SMB_VFS_LAYER_OPAQUE}, + + {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP} +}; + +NTSTATUS vfs_shadow_copy2_init(void); +NTSTATUS vfs_shadow_copy2_init(void) +{ + NTSTATUS ret; + + ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "shadow_copy2", shadow_copy2_ops); + + if (!NT_STATUS_IS_OK(ret)) + return ret; + + vfs_shadow_copy2_debug_level = debug_add_class("shadow_copy2"); + if (vfs_shadow_copy2_debug_level == -1) { + vfs_shadow_copy2_debug_level = DBGC_VFS; + DEBUG(0, ("%s: Couldn't register custom debugging class!\n", + "vfs_shadow_copy2_init")); + } else { + DEBUG(10, ("%s: Debug class number of '%s': %d\n", + "vfs_shadow_copy2_init","shadow_copy2",vfs_shadow_copy2_debug_level)); + } + + return ret; +} diff --git a/source3/modules/vfs_streams_depot.c b/source3/modules/vfs_streams_depot.c new file mode 100644 index 0000000000..fa85ea4a57 --- /dev/null +++ b/source3/modules/vfs_streams_depot.c @@ -0,0 +1,641 @@ +/* + * Store streams in a separate subdirectory + * + * Copyright (C) Volker Lendecke, 2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/* + * Excerpt from a mail from tridge: + * + * Volker, what I'm thinking of is this: + * /mount-point/.streams/XX/YY/aaaa.bbbb/namedstream1 + * /mount-point/.streams/XX/YY/aaaa.bbbb/namedstream2 + * + * where XX/YY is a 2 level hash based on the fsid/inode. "aaaa.bbbb" + * is the fsid/inode. "namedstreamX" is a file named after the stream + * name. + */ + +static uint32_t hash_fn(DATA_BLOB key) +{ + uint32_t value; /* Used to compute the hash value. */ + uint32_t i; /* Used to cycle through random values. */ + + /* Set the initial value from the key size. */ + for (value = 0x238F13AF * key.length, i=0; i < key.length; i++) + value = (value + (key.data[i] << (i*5 % 24))); + + return (1103515243 * value + 12345); +} + +/* + * With the hashing scheme based on the inode we need to protect against + * streams showing up on files with re-used inodes. This can happen if we + * create a stream directory from within Samba, and a local process or NFS + * client deletes the file without deleting the streams directory. When the + * inode is re-used and the stream directory is still around, the streams in + * there would be show up as belonging to the new file. + * + * There are several workarounds for this, probably the easiest one is on + * systems which have a true birthtime stat element: When the file has a later + * birthtime than the streams directory, then we have to recreate the + * directory. + * + * The other workaround is to somehow mark the file as generated by Samba with + * something that a NFS client would not do. The closest one is a special + * xattr value being set. On systems which do not support xattrs, it might be + * an option to put in a special ACL entry for a non-existing group. + */ + +#define SAMBA_XATTR_MARKER "user.SAMBA_STREAMS" + +static bool file_is_valid(vfs_handle_struct *handle, const char *path) +{ + char buf; + + DEBUG(10, ("file_is_valid (%s) called\n", path)); + + if (SMB_VFS_NEXT_GETXATTR(handle, path, SAMBA_XATTR_MARKER, + &buf, sizeof(buf)) != sizeof(buf)) { + DEBUG(10, ("GETXATTR failed: %s\n", strerror(errno))); + return false; + } + + if (buf != '1') { + DEBUG(10, ("got wrong buffer content: '%c'\n", buf)); + return false; + } + + return true; +} + +static bool mark_file_valid(vfs_handle_struct *handle, const char *path) +{ + char buf = '1'; + int ret; + + DEBUG(10, ("marking file %s as valid\n", path)); + + ret = SMB_VFS_NEXT_SETXATTR(handle, path, SAMBA_XATTR_MARKER, + &buf, sizeof(buf), 0); + + if (ret == -1) { + DEBUG(10, ("SETXATTR failed: %s\n", strerror(errno))); + return false; + } + + return true; +} + +static char *stream_dir(vfs_handle_struct *handle, const char *base_path, + const SMB_STRUCT_STAT *base_sbuf, bool create_it) +{ + uint32_t hash; + char *result = NULL; + SMB_STRUCT_STAT sbuf; + uint8_t first, second; + char *tmp; + char *id_hex; + struct file_id id; + uint8 id_buf[16]; + + const char *rootdir = lp_parm_const_string( + SNUM(handle->conn), "streams", "directory", + handle->conn->connectpath); + + if (base_sbuf == NULL) { + if (SMB_VFS_NEXT_STAT(handle, base_path, &sbuf) == -1) { + /* + * base file is not there + */ + goto fail; + } + base_sbuf = &sbuf; + } + + id = SMB_VFS_FILE_ID_CREATE(handle->conn, base_sbuf->st_dev, + base_sbuf->st_ino); + + push_file_id_16((char *)id_buf, &id); + + hash = hash_fn(data_blob_const(id_buf, sizeof(id_buf))); + + first = hash & 0xff; + second = (hash >> 8) & 0xff; + + id_hex = hex_encode(talloc_tos(), id_buf, sizeof(id_buf)); + + if (id_hex == NULL) { + errno = ENOMEM; + goto fail; + } + + result = talloc_asprintf(talloc_tos(), "%s/%2.2X/%2.2X/%s", rootdir, + first, second, id_hex); + + TALLOC_FREE(id_hex); + + if (result == NULL) { + errno = ENOMEM; + return NULL; + } + + if (SMB_VFS_NEXT_STAT(handle, result, &sbuf) == 0) { + char *newname; + + if (!S_ISDIR(sbuf.st_mode)) { + errno = EINVAL; + goto fail; + } + + if (file_is_valid(handle, base_path)) { + return result; + } + + /* + * Someone has recreated a file under an existing inode + * without deleting the streams directory. For now, just move + * it away. + */ + + again: + newname = talloc_asprintf(talloc_tos(), "lost-%lu", random()); + if (newname == NULL) { + errno = ENOMEM; + goto fail; + } + + if (SMB_VFS_NEXT_RENAME(handle, result, newname) == -1) { + if ((errno == EEXIST) || (errno == ENOTEMPTY)) { + TALLOC_FREE(newname); + goto again; + } + goto fail; + } + + TALLOC_FREE(newname); + } + + if (!create_it) { + errno = ENOENT; + goto fail; + } + + if ((SMB_VFS_NEXT_MKDIR(handle, rootdir, 0755) != 0) + && (errno != EEXIST)) { + goto fail; + } + + tmp = talloc_asprintf(result, "%s/%2.2X", rootdir, first); + if (tmp == NULL) { + errno = ENOMEM; + goto fail; + } + + if ((SMB_VFS_NEXT_MKDIR(handle, tmp, 0755) != 0) + && (errno != EEXIST)) { + goto fail; + } + + TALLOC_FREE(tmp); + + tmp = talloc_asprintf(result, "%s/%2.2X/%2.2X", rootdir, first, + second); + if (tmp == NULL) { + errno = ENOMEM; + goto fail; + } + + if ((SMB_VFS_NEXT_MKDIR(handle, tmp, 0755) != 0) + && (errno != EEXIST)) { + goto fail; + } + + TALLOC_FREE(tmp); + + if ((SMB_VFS_NEXT_MKDIR(handle, result, 0755) != 0) + && (errno != EEXIST)) { + goto fail; + } + + if (!mark_file_valid(handle, base_path)) { + goto fail; + } + + return result; + + fail: + TALLOC_FREE(result); + return NULL; +} + +static char *stream_name(vfs_handle_struct *handle, const char *fname, + bool create_dir) +{ + char *base = NULL; + char *sname = NULL; + char *id_hex = NULL; + char *dirname, *stream_fname; + + if (!NT_STATUS_IS_OK(split_ntfs_stream_name(talloc_tos(), fname, + &base, &sname))) { + DEBUG(10, ("split_ntfs_stream_name failed\n")); + errno = ENOMEM; + goto fail; + } + + dirname = stream_dir(handle, base, NULL, create_dir); + + if (dirname == NULL) { + goto fail; + } + + stream_fname = talloc_asprintf(talloc_tos(), "%s/:%s", dirname, sname); + + if (stream_fname == NULL) { + errno = ENOMEM; + goto fail; + } + + DEBUG(10, ("stream filename = %s\n", stream_fname)); + + TALLOC_FREE(base); + TALLOC_FREE(sname); + TALLOC_FREE(id_hex); + + return stream_fname; + + fail: + DEBUG(5, ("stream_name failed: %s\n", strerror(errno))); + TALLOC_FREE(base); + TALLOC_FREE(sname); + TALLOC_FREE(id_hex); + return NULL; +} + +static NTSTATUS walk_streams(vfs_handle_struct *handle, + const char *fname, + const SMB_STRUCT_STAT *sbuf, + char **pdirname, + bool (*fn)(const char *dirname, + const char *dirent, + void *private_data), + void *private_data) +{ + char *dirname; + SMB_STRUCT_DIR *dirhandle = NULL; + char *dirent; + + dirname = stream_dir(handle, fname, sbuf, false); + + if (dirname == NULL) { + if (errno == ENOENT) { + /* + * no stream around + */ + return NT_STATUS_OK; + } + return map_nt_error_from_unix(errno); + } + + DEBUG(10, ("walk_streams: dirname=%s\n", dirname)); + + dirhandle = SMB_VFS_NEXT_OPENDIR(handle, dirname, NULL, 0); + + if (dirhandle == NULL) { + TALLOC_FREE(dirname); + return map_nt_error_from_unix(errno); + } + + while ((dirent = vfs_readdirname(handle->conn, dirhandle)) != NULL) { + + if (ISDOT(dirent) || ISDOTDOT(dirent)) { + continue; + } + + DEBUG(10, ("walk_streams: dirent=%s\n", dirent)); + + if (!fn(dirname, dirent, private_data)) { + break; + } + } + + SMB_VFS_NEXT_CLOSEDIR(handle, dirhandle); + + if (pdirname != NULL) { + *pdirname = dirname; + } + else { + TALLOC_FREE(dirname); + } + + return NT_STATUS_OK; +} + +static int streams_depot_stat(vfs_handle_struct *handle, const char *fname, + SMB_STRUCT_STAT *sbuf) +{ + char *stream_fname; + int ret = -1; + + DEBUG(10, ("streams_depot_stat called for [%s]\n", fname)); + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_STAT(handle, fname, sbuf); + } + + stream_fname = stream_name(handle, fname, false); + if (stream_fname == NULL) { + goto done; + } + + ret = SMB_VFS_NEXT_STAT(handle, stream_fname, sbuf); + + done: + TALLOC_FREE(stream_fname); + return ret; +} + +static int streams_depot_lstat(vfs_handle_struct *handle, const char *fname, + SMB_STRUCT_STAT *sbuf) +{ + char *stream_fname; + int ret = -1; + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_LSTAT(handle, fname, sbuf); + } + + stream_fname = stream_name(handle, fname, false); + if (stream_fname == NULL) { + goto done; + } + + ret = SMB_VFS_NEXT_LSTAT(handle, stream_fname, sbuf); + + done: + TALLOC_FREE(stream_fname); + return ret; +} + +static int streams_depot_open(vfs_handle_struct *handle, const char *fname, + files_struct *fsp, int flags, mode_t mode) +{ + TALLOC_CTX *frame; + char *base = NULL; + SMB_STRUCT_STAT base_sbuf; + char *stream_fname; + int ret = -1; + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode); + } + + frame = talloc_stackframe(); + + if (!NT_STATUS_IS_OK(split_ntfs_stream_name(talloc_tos(), fname, + &base, NULL))) { + errno = ENOMEM; + goto done; + } + + ret = SMB_VFS_NEXT_STAT(handle, base, &base_sbuf); + + if (ret == -1) { + goto done; + } + + TALLOC_FREE(base); + + stream_fname = stream_name(handle, fname, true); + if (stream_fname == NULL) { + goto done; + } + + ret = SMB_VFS_NEXT_OPEN(handle, stream_fname, fsp, flags, mode); + + done: + TALLOC_FREE(frame); + return ret; +} + +static int streams_depot_unlink(vfs_handle_struct *handle, const char *fname) +{ + int ret = -1; + SMB_STRUCT_STAT sbuf; + + DEBUG(10, ("streams_depot_unlink called for %s\n", fname)); + + if (is_ntfs_stream_name(fname)) { + char *stream_fname; + + stream_fname = stream_name(handle, fname, false); + if (stream_fname == NULL) { + return -1; + } + + ret = SMB_VFS_NEXT_UNLINK(handle, stream_fname); + + TALLOC_FREE(stream_fname); + return ret; + } + + /* + * We potentially need to delete the per-inode streams directory + */ + + if (SMB_VFS_NEXT_STAT(handle, fname, &sbuf) == -1) { + return -1; + } + + if (sbuf.st_nlink == 1) { + char *dirname = stream_dir(handle, fname, &sbuf, false); + + if (dirname != NULL) { + SMB_VFS_NEXT_RMDIR(handle, dirname); + } + TALLOC_FREE(dirname); + } + + return SMB_VFS_NEXT_UNLINK(handle, fname); +} + +static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, SMB_OFF_T size, + SMB_OFF_T alloc_size) +{ + struct stream_struct *tmp; + + tmp = TALLOC_REALLOC_ARRAY(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_strdup(tmp, name); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +struct streaminfo_state { + TALLOC_CTX *mem_ctx; + vfs_handle_struct *handle; + unsigned int num_streams; + struct stream_struct *streams; + NTSTATUS status; +}; + +static bool collect_one_stream(const char *dirname, + const char *dirent, + void *private_data) +{ + struct streaminfo_state *state = + (struct streaminfo_state *)private_data; + char *full_sname; + SMB_STRUCT_STAT sbuf; + + if (asprintf(&full_sname, "%s/%s", dirname, dirent) == -1) { + state->status = NT_STATUS_NO_MEMORY; + return false; + } + if (SMB_VFS_NEXT_STAT(state->handle, full_sname, &sbuf) == -1) { + DEBUG(10, ("Could not stat %s: %s\n", full_sname, + strerror(errno))); + SAFE_FREE(full_sname); + return true; + } + + SAFE_FREE(full_sname); + + if (!add_one_stream(state->mem_ctx, + &state->num_streams, &state->streams, + dirent, sbuf.st_size, + get_allocation_size( + state->handle->conn, NULL, &sbuf))) { + state->status = NT_STATUS_NO_MEMORY; + return false; + } + + return true; +} + +static NTSTATUS streams_depot_streaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + const char *fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + SMB_STRUCT_STAT sbuf; + int ret; + NTSTATUS status; + struct streaminfo_state state; + + if ((fsp != NULL) && (fsp->fh->fd != -1)) { + if (is_ntfs_stream_name(fsp->fsp_name)) { + return NT_STATUS_INVALID_PARAMETER; + } + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf); + } + else { + if (is_ntfs_stream_name(fname)) { + return NT_STATUS_INVALID_PARAMETER; + } + ret = SMB_VFS_NEXT_STAT(handle, fname, &sbuf); + } + + if (ret == -1) { + return map_nt_error_from_unix(errno); + } + + state.streams = NULL; + state.num_streams = 0; + + if (!S_ISDIR(sbuf.st_mode)) { + if (!add_one_stream(mem_ctx, + &state.num_streams, &state.streams, + "::$DATA", sbuf.st_size, + get_allocation_size(handle->conn, fsp, + &sbuf))) { + return NT_STATUS_NO_MEMORY; + } + } + + state.mem_ctx = mem_ctx; + state.handle = handle; + state.status = NT_STATUS_OK; + + status = walk_streams(handle, fname, &sbuf, NULL, collect_one_stream, + &state); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(state.streams); + return status; + } + + if (!NT_STATUS_IS_OK(state.status)) { + TALLOC_FREE(state.streams); + return state.status; + } + + *pnum_streams = state.num_streams; + *pstreams = state.streams; + return NT_STATUS_OK; +} + +static uint32_t streams_depot_fs_capabilities(struct vfs_handle_struct *handle) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle) | FILE_NAMED_STREAMS; +} + +/* VFS operations structure */ + +static vfs_op_tuple streams_depot_ops[] = { + {SMB_VFS_OP(streams_depot_fs_capabilities), SMB_VFS_OP_FS_CAPABILITIES, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_depot_open), SMB_VFS_OP_OPEN, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_depot_stat), SMB_VFS_OP_STAT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_depot_lstat), SMB_VFS_OP_LSTAT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_depot_unlink), SMB_VFS_OP_UNLINK, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_depot_streaminfo), SMB_VFS_OP_STREAMINFO, + SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP} +}; + +NTSTATUS vfs_streams_depot_init(void); +NTSTATUS vfs_streams_depot_init(void) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_depot", + streams_depot_ops); +} diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c new file mode 100644 index 0000000000..766e7d10ab --- /dev/null +++ b/source3/modules/vfs_streams_xattr.c @@ -0,0 +1,685 @@ +/* + * Store streams in xattrs + * + * Copyright (C) Volker Lendecke, 2008 + * + * Partly based on James Peach's Darwin module, which is + * + * Copyright (C) James Peach 2006-2007 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +struct stream_io { + char *base; + char *xattr_name; +}; + +static SMB_INO_T stream_inode(const SMB_STRUCT_STAT *sbuf, const char *sname) +{ + struct MD5Context ctx; + unsigned char hash[16]; + SMB_INO_T result; + char *upper_sname; + + DEBUG(10, ("stream_inode called for %lu/%lu [%s]\n", + (unsigned long)sbuf->st_dev, + (unsigned long)sbuf->st_ino, sname)); + + upper_sname = talloc_strdup_upper(talloc_tos(), sname); + SMB_ASSERT(upper_sname != NULL); + + MD5Init(&ctx); + MD5Update(&ctx, (unsigned char *)&(sbuf->st_dev), + sizeof(sbuf->st_dev)); + MD5Update(&ctx, (unsigned char *)&(sbuf->st_ino), + sizeof(sbuf->st_ino)); + MD5Update(&ctx, (unsigned char *)upper_sname, + talloc_get_size(upper_sname)-1); + MD5Final(hash, &ctx); + + TALLOC_FREE(upper_sname); + + /* Hopefully all the variation is in the lower 4 (or 8) bytes! */ + memcpy(&result, hash, sizeof(result)); + + DEBUG(10, ("stream_inode returns %lu\n", (unsigned long)result)); + + return result; +} + +static ssize_t get_xattr_size(connection_struct *conn, const char *fname, + const char *xattr_name) +{ + NTSTATUS status; + struct ea_struct ea; + ssize_t result; + + status = get_ea_value(talloc_tos(), conn, NULL, fname, + xattr_name, &ea); + + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + result = ea.value.length-1; + TALLOC_FREE(ea.value.data); + return result; +} + + +static int streams_xattr_fstat(vfs_handle_struct *handle, files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + struct stream_io *io = (struct stream_io *) + VFS_FETCH_FSP_EXTENSION(handle, fsp); + + DEBUG(10, ("streams_xattr_fstat called for %d\n", fsp->fh->fd)); + + if (io == NULL) { + return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + } + + if (SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf) == -1) { + return -1; + } + + sbuf->st_size = get_xattr_size(handle->conn, io->base, io->xattr_name); + if (sbuf->st_size == -1) { + return -1; + } + + DEBUG(10, ("sbuf->st_size = %d\n", (int)sbuf->st_size)); + + sbuf->st_ino = stream_inode(sbuf, io->xattr_name); + sbuf->st_mode &= ~S_IFMT; + sbuf->st_mode |= S_IFREG; + sbuf->st_blocks = sbuf->st_size % STAT_ST_BLOCKSIZE + 1; + + return 0; +} + +static int streams_xattr_stat(vfs_handle_struct *handle, const char *fname, + SMB_STRUCT_STAT *sbuf) +{ + NTSTATUS status; + char *base = NULL, *sname = NULL; + int result = -1; + char *xattr_name; + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_STAT(handle, fname, sbuf); + } + + status = split_ntfs_stream_name(talloc_tos(), fname, &base, &sname); + if (!NT_STATUS_IS_OK(status)) { + errno = EINVAL; + return -1; + } + + if (SMB_VFS_STAT(handle->conn, base, sbuf) == -1) { + goto fail; + } + + xattr_name = talloc_asprintf(talloc_tos(), "%s%s", + SAMBA_XATTR_DOSSTREAM_PREFIX, sname); + if (xattr_name == NULL) { + errno = ENOMEM; + goto fail; + } + + sbuf->st_size = get_xattr_size(handle->conn, base, xattr_name); + if (sbuf->st_size == -1) { + errno = ENOENT; + goto fail; + } + + sbuf->st_ino = stream_inode(sbuf, xattr_name); + sbuf->st_mode &= ~S_IFMT; + sbuf->st_mode |= S_IFREG; + sbuf->st_blocks = sbuf->st_size % STAT_ST_BLOCKSIZE + 1; + + result = 0; + fail: + TALLOC_FREE(base); + TALLOC_FREE(sname); + return result; +} + +static int streams_xattr_lstat(vfs_handle_struct *handle, const char *fname, + SMB_STRUCT_STAT *sbuf) +{ + NTSTATUS status; + char *base, *sname; + int result = -1; + char *xattr_name; + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_LSTAT(handle, fname, sbuf); + } + + status = split_ntfs_stream_name(talloc_tos(), fname, &base, &sname); + if (!NT_STATUS_IS_OK(status)) { + errno = EINVAL; + goto fail; + } + + if (SMB_VFS_LSTAT(handle->conn, base, sbuf) == -1) { + goto fail; + } + + xattr_name = talloc_asprintf(talloc_tos(), "%s%s", + SAMBA_XATTR_DOSSTREAM_PREFIX, sname); + if (xattr_name == NULL) { + errno = ENOMEM; + goto fail; + } + + sbuf->st_size = get_xattr_size(handle->conn, base, xattr_name); + if (sbuf->st_size == -1) { + errno = ENOENT; + goto fail; + } + + sbuf->st_ino = stream_inode(sbuf, xattr_name); + sbuf->st_mode &= ~S_IFMT; + sbuf->st_mode |= S_IFREG; + sbuf->st_blocks = sbuf->st_size % STAT_ST_BLOCKSIZE + 1; + + result = 0; + fail: + TALLOC_FREE(base); + TALLOC_FREE(sname); + return result; +} + +static int streams_xattr_open(vfs_handle_struct *handle, const char *fname, + files_struct *fsp, int flags, mode_t mode) +{ + TALLOC_CTX *frame; + NTSTATUS status; + struct stream_io *sio; + char *base, *sname; + struct ea_struct ea; + char *xattr_name; + int baseflags; + int hostfd = -1; + + DEBUG(10, ("streams_xattr_open called for %s\n", fname)); + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode); + } + + frame = talloc_stackframe(); + + status = split_ntfs_stream_name(talloc_tos(), fname, + &base, &sname); + if (!NT_STATUS_IS_OK(status)) { + errno = EINVAL; + goto fail; + } + + xattr_name = talloc_asprintf(talloc_tos(), "%s%s", + SAMBA_XATTR_DOSSTREAM_PREFIX, sname); + if (xattr_name == NULL) { + errno = ENOMEM; + goto fail; + } + + /* + * We use baseflags to turn off nasty side-effects when opening the + * underlying file. + */ + baseflags = flags; + baseflags &= ~O_TRUNC; + baseflags &= ~O_EXCL; + baseflags &= ~O_CREAT; + + hostfd = SMB_VFS_OPEN(handle->conn, base, fsp, baseflags, mode); + + /* It is legit to open a stream on a directory, but the base + * fd has to be read-only. + */ + if ((hostfd == -1) && (errno == EISDIR)) { + baseflags &= ~O_ACCMODE; + baseflags |= O_RDONLY; + hostfd = SMB_VFS_OPEN(handle->conn, fname, fsp, baseflags, + mode); + } + + if (hostfd == -1) { + goto fail; + } + + status = get_ea_value(talloc_tos(), handle->conn, NULL, base, + xattr_name, &ea); + + DEBUG(10, ("get_ea_value returned %s\n", nt_errstr(status))); + + if (!NT_STATUS_IS_OK(status) + && !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + /* + * The base file is not there. This is an error even if we got + * O_CREAT, the higher levels should have created the base + * file for us. + */ + DEBUG(10, ("streams_xattr_open: base file %s not around, " + "returning ENOENT\n", base)); + errno = ENOENT; + goto fail; + } + + if (!NT_STATUS_IS_OK(status)) { + /* + * The attribute does not exist + */ + + if (flags & O_CREAT) { + /* + * Darn, xattrs need at least 1 byte + */ + char null = '\0'; + + DEBUG(10, ("creating attribute %s on file %s\n", + xattr_name, base)); + + if (SMB_VFS_SETXATTR( + handle->conn, base, xattr_name, + &null, sizeof(null), + flags & O_EXCL ? XATTR_CREATE : 0) == -1) { + goto fail; + } + } + } + + if (flags & O_TRUNC) { + char null = '\0'; + if (SMB_VFS_SETXATTR( + handle->conn, base, xattr_name, + &null, sizeof(null), + flags & O_EXCL ? XATTR_CREATE : 0) == -1) { + goto fail; + } + } + + sio = (struct stream_io *)VFS_ADD_FSP_EXTENSION(handle, fsp, + struct stream_io); + if (sio == NULL) { + errno = ENOMEM; + goto fail; + } + + sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle, fsp), + xattr_name); + sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle, fsp), + base); + + if ((sio->xattr_name == NULL) || (sio->base == NULL)) { + errno = ENOMEM; + goto fail; + } + + TALLOC_FREE(frame); + return hostfd; + + fail: + if (hostfd >= 0) { + /* + * BUGBUGBUG -- we would need to call fd_close_posix here, but + * we don't have a full fsp yet + */ + SMB_VFS_CLOSE(fsp, hostfd); + } + + TALLOC_FREE(frame); + return -1; +} + +static int streams_xattr_unlink(vfs_handle_struct *handle, const char *fname) +{ + NTSTATUS status; + char *base = NULL; + char *sname = NULL; + int ret = -1; + char *xattr_name; + + if (!is_ntfs_stream_name(fname)) { + return SMB_VFS_NEXT_UNLINK(handle, fname); + } + + status = split_ntfs_stream_name(talloc_tos(), fname, &base, &sname); + if (!NT_STATUS_IS_OK(status)) { + errno = EINVAL; + goto fail; + } + + xattr_name = talloc_asprintf(talloc_tos(), "%s%s", + SAMBA_XATTR_DOSSTREAM_PREFIX, sname); + if (xattr_name == NULL) { + errno = ENOMEM; + goto fail; + } + + ret = SMB_VFS_REMOVEXATTR(handle->conn, base, xattr_name); + + if ((ret == -1) && (errno == ENOATTR)) { + errno = ENOENT; + goto fail; + } + + ret = 0; + + fail: + TALLOC_FREE(base); + TALLOC_FREE(sname); + return ret; +} + +static NTSTATUS walk_xattr_streams(connection_struct *conn, files_struct *fsp, + const char *fname, + bool (*fn)(struct ea_struct *ea, + void *private_data), + void *private_data) +{ + NTSTATUS status; + char **names; + size_t i, num_names; + size_t prefix_len = strlen(SAMBA_XATTR_DOSSTREAM_PREFIX); + + status = get_ea_names_from_file(talloc_tos(), conn, fsp, fname, + &names, &num_names); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + for (i=0; i<num_names; i++) { + struct ea_struct ea; + + if (strncmp(names[i], SAMBA_XATTR_DOSSTREAM_PREFIX, + prefix_len) != 0) { + continue; + } + + status = get_ea_value(names, conn, fsp, fname, names[i], &ea); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Could not get ea %s for file %s: %s\n", + names[i], fname, nt_errstr(status))); + continue; + } + + ea.name = talloc_asprintf(ea.value.data, ":%s", + names[i] + prefix_len); + if (ea.name == NULL) { + DEBUG(0, ("talloc failed\n")); + continue; + } + + if (!fn(&ea, private_data)) { + TALLOC_FREE(ea.value.data); + return NT_STATUS_OK; + } + + TALLOC_FREE(ea.value.data); + } + + TALLOC_FREE(names); + return NT_STATUS_OK; +} + +static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, SMB_OFF_T size, + SMB_OFF_T alloc_size) +{ + struct stream_struct *tmp; + + tmp = TALLOC_REALLOC_ARRAY(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_strdup(tmp, name); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +struct streaminfo_state { + TALLOC_CTX *mem_ctx; + vfs_handle_struct *handle; + unsigned int num_streams; + struct stream_struct *streams; + NTSTATUS status; +}; + +static bool collect_one_stream(struct ea_struct *ea, void *private_data) +{ + struct streaminfo_state *state = + (struct streaminfo_state *)private_data; + + if (!add_one_stream(state->mem_ctx, + &state->num_streams, &state->streams, + ea->name, ea->value.length-1, + smb_roundup(state->handle->conn, + ea->value.length-1))) { + state->status = NT_STATUS_NO_MEMORY; + return false; + } + + return true; +} + +static NTSTATUS streams_xattr_streaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + const char *fname, + TALLOC_CTX *mem_ctx, + unsigned int *pnum_streams, + struct stream_struct **pstreams) +{ + SMB_STRUCT_STAT sbuf; + int ret; + NTSTATUS status; + struct streaminfo_state state; + + if ((fsp != NULL) && (fsp->fh->fd != -1)) { + if (is_ntfs_stream_name(fsp->fsp_name)) { + return NT_STATUS_INVALID_PARAMETER; + } + ret = SMB_VFS_FSTAT(fsp, &sbuf); + } + else { + if (is_ntfs_stream_name(fname)) { + return NT_STATUS_INVALID_PARAMETER; + } + ret = SMB_VFS_STAT(handle->conn, fname, &sbuf); + } + + if (ret == -1) { + return map_nt_error_from_unix(errno); + } + + state.streams = NULL; + state.num_streams = 0; + + if (!S_ISDIR(sbuf.st_mode)) { + if (!add_one_stream(mem_ctx, + &state.num_streams, &state.streams, + "::$DATA", sbuf.st_size, + get_allocation_size(handle->conn, fsp, + &sbuf))) { + return NT_STATUS_NO_MEMORY; + } + } + + state.mem_ctx = mem_ctx; + state.handle = handle; + state.status = NT_STATUS_OK; + + status = walk_xattr_streams(handle->conn, fsp, fname, + collect_one_stream, &state); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(state.streams); + return status; + } + + if (!NT_STATUS_IS_OK(state.status)) { + TALLOC_FREE(state.streams); + return state.status; + } + + *pnum_streams = state.num_streams; + *pstreams = state.streams; + return NT_STATUS_OK; +} + +static uint32_t streams_xattr_fs_capabilities(struct vfs_handle_struct *handle) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle) | FILE_NAMED_STREAMS; +} + +static ssize_t streams_xattr_pwrite(vfs_handle_struct *handle, + files_struct *fsp, const void *data, + size_t n, SMB_OFF_T offset) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + struct ea_struct ea; + NTSTATUS status; + int ret; + + DEBUG(10, ("streams_xattr_pwrite called for %d bytes\n", (int)n)); + + if (sio == NULL) { + return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + } + + status = get_ea_value(talloc_tos(), handle->conn, fsp->base_fsp, + sio->base, sio->xattr_name, &ea); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + if ((offset + n) > ea.value.length-1) { + uint8 *tmp; + + tmp = TALLOC_REALLOC_ARRAY(talloc_tos(), ea.value.data, uint8, + offset + n + 1); + + if (tmp == NULL) { + TALLOC_FREE(ea.value.data); + errno = ENOMEM; + return -1; + } + ea.value.data = tmp; + ea.value.length = offset + n + 1; + ea.value.data[offset+n] = 0; + } + + memcpy(ea.value.data + offset, data, n); + + ret = SMB_VFS_SETXATTR(fsp->conn, fsp->base_fsp->fsp_name, + sio->xattr_name, + ea.value.data, ea.value.length, 0); + + TALLOC_FREE(ea.value.data); + + if (ret == -1) { + return -1; + } + + return n; +} + +static ssize_t streams_xattr_pread(vfs_handle_struct *handle, + files_struct *fsp, void *data, + size_t n, SMB_OFF_T offset) +{ + struct stream_io *sio = + (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp); + struct ea_struct ea; + NTSTATUS status; + size_t length, overlap; + + if (sio == NULL) { + return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + } + + status = get_ea_value(talloc_tos(), handle->conn, fsp->base_fsp, + sio->base, sio->xattr_name, &ea); + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + length = ea.value.length-1; + + /* Attempt to read past EOF. */ + if (length <= offset) { + errno = EINVAL; + return -1; + } + + overlap = (offset + n) > length ? (length - offset) : n; + memcpy(data, ea.value.data + offset, overlap); + + TALLOC_FREE(ea.value.data); + return overlap; +} + +/* VFS operations structure */ + +static vfs_op_tuple streams_xattr_ops[] = { + {SMB_VFS_OP(streams_xattr_fs_capabilities), SMB_VFS_OP_FS_CAPABILITIES, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_open), SMB_VFS_OP_OPEN, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_stat), SMB_VFS_OP_STAT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_fstat), SMB_VFS_OP_FSTAT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_lstat), SMB_VFS_OP_LSTAT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_pread), SMB_VFS_OP_PREAD, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_pwrite), SMB_VFS_OP_PWRITE, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_lstat), SMB_VFS_OP_LSTAT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_unlink), SMB_VFS_OP_UNLINK, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(streams_xattr_streaminfo), SMB_VFS_OP_STREAMINFO, + SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP} +}; + +NTSTATUS vfs_streams_xattr_init(void); +NTSTATUS vfs_streams_xattr_init(void) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_xattr", + streams_xattr_ops); +} diff --git a/source3/modules/vfs_tsmsm.c b/source3/modules/vfs_tsmsm.c new file mode 100644 index 0000000000..791e8cfcd0 --- /dev/null +++ b/source3/modules/vfs_tsmsm.c @@ -0,0 +1,365 @@ +/* + Unix SMB/CIFS implementation. + Samba VFS module for handling offline files + with Tivoli Storage Manager Space Management + + (c) Alexander Bokovoy, 2007, 2008 + (c) Andrew Tridgell, 2007, 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +/* + This VFS module accepts following options: + tsmsm: hsm script = <path to hsm script> (default does nothing) + hsm script should point to a shell script which accepts two arguments: + <operation> <filepath> + where <operation> is currently 'offline' to set offline status of the <filepath> + + tsmsm: online ratio = ratio to check reported size against actual file size (0.5 by default) + tsmsm: attribute name = name of DMAPI attribute that is present when a file is offline. + Default is "IBMobj" (which is what GPFS uses) + + The TSMSM VFS module tries to avoid calling expensive DMAPI calls with some heuristics + based on the fact that number of blocks reported of a file multiplied by 512 will be + bigger than 'online ratio' of actual size for online (non-migrated) files. + + If checks fail, we call DMAPI and ask for specific attribute which present for + offline (migrated) files. If this attribute presents, we consider file offline. + */ + +#include "includes.h" + +#ifndef USE_DMAPI +#error "This module requires DMAPI support!" +#endif + +#ifdef HAVE_XFS_DMAPI_H +#include <xfs/dmapi.h> +#elif defined(HAVE_SYS_DMI_H) +#include <sys/dmi.h> +#elif defined(HAVE_SYS_JFSDMAPI_H) +#include <sys/jfsdmapi.h> +#elif defined(HAVE_SYS_DMAPI_H) +#include <sys/dmapi.h> +#elif defined(HAVE_DMAPI_H) +#include <dmapi.h> +#endif + +#ifndef _ISOC99_SOURCE +#define _ISOC99_SOURCE +#endif + +#include <math.h> + +/* optimisation tunables - used to avoid the DMAPI slow path */ +#define FILE_IS_ONLINE_RATIO 0.5 + +/* default attribute name to look for */ +#define DM_ATTRIB_OBJECT "IBMObj" + +struct tsmsm_struct { + float online_ratio; + char *hsmscript; + const char *attrib_name; +}; + +static void tsmsm_free_data(void **pptr) { + struct tsmsm_struct **tsmd = (struct tsmsm_struct **)pptr; + if(!tsmd) return; + TALLOC_FREE(*tsmd); +} + +/* + called when a client connects to a share +*/ +static int tsmsm_connect(struct vfs_handle_struct *handle, + const char *service, + const char *user) { + struct tsmsm_struct *tsmd = TALLOC_ZERO_P(handle, struct tsmsm_struct); + const char *fres; + const char *tsmname; + + if (!tsmd) { + DEBUG(0,("tsmsm_connect: out of memory!\n")); + return -1; + } + + if (!dmapi_have_session()) { + DEBUG(0,("tsmsm_connect: no DMAPI session for Samba is available!\n")); + TALLOC_FREE(tsmd); + return -1; + } + + tsmname = (handle->param ? handle->param : "tsmsm"); + + /* Get 'hsm script' and 'dmapi attribute' parameters to tsmd context */ + tsmd->hsmscript = lp_parm_talloc_string(SNUM(handle->conn), tsmname, + "hsm script", NULL); + talloc_steal(tsmd, tsmd->hsmscript); + + tsmd->attrib_name = lp_parm_talloc_string(SNUM(handle->conn), tsmname, + "dmapi attribute", DM_ATTRIB_OBJECT); + talloc_steal(tsmd, tsmd->attrib_name); + + /* retrieve 'online ratio'. In case of error default to FILE_IS_ONLINE_RATIO */ + fres = lp_parm_const_string(SNUM(handle->conn), tsmname, + "online ratio", NULL); + if (fres == NULL) { + tsmd->online_ratio = FILE_IS_ONLINE_RATIO; + } else { + tsmd->online_ratio = strtof(fres, NULL); + if (tsmd->online_ratio > 1.0 || + tsmd->online_ratio <= 0.0) { + DEBUG(1, ("tsmsm_connect: invalid online ration %f - using %f.\n", + tsmd->online_ratio, (float)FILE_IS_ONLINE_RATIO)); + } + } + + /* Store the private data. */ + SMB_VFS_HANDLE_SET_DATA(handle, tsmd, tsmsm_free_data, + struct tsmsm_struct, return -1); + return SMB_VFS_NEXT_CONNECT(handle, service, user); +} + +static bool tsmsm_is_offline(struct vfs_handle_struct *handle, + const char *path, + SMB_STRUCT_STAT *stbuf) { + struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data; + const dm_sessid_t *dmsession_id; + void *dmhandle = NULL; + size_t dmhandle_len = 0; + size_t rlen; + dm_attrname_t dmname; + int ret, lerrno; + bool offline; + char buf[1]; + + /* if the file has more than FILE_IS_ONLINE_RATIO of blocks available, + then assume it is not offline (it may not be 100%, as it could be sparse) */ + if (512 * (off_t)stbuf->st_blocks >= stbuf->st_size * tsmd->online_ratio) { + DEBUG(10,("%s not offline: st_blocks=%ld st_size=%ld online_ratio=%.2f\n", + path, stbuf->st_blocks, stbuf->st_size, tsmd->online_ratio)); + return false; + } + + dmsession_id = dmapi_get_current_session(); + if (dmsession_id == NULL) { + DEBUG(2, ("tsmsm_is_offline: no DMAPI session available? " + "Assume file is online.\n")); + return false; + } + + /* using POSIX capabilities does not work here. It's a slow path, so + * become_root() is just as good anyway (tridge) + */ + + /* Also, AIX has DMAPI but no POSIX capablities support. In this case, + * we need to be root to do DMAPI manipulations. + */ + become_root(); + + /* go the slow DMAPI route */ + if (dm_path_to_handle((char*)path, &dmhandle, &dmhandle_len) != 0) { + DEBUG(2,("dm_path_to_handle failed - assuming offline (%s) - %s\n", + path, strerror(errno))); + offline = true; + goto done; + } + + memset(&dmname, 0, sizeof(dmname)); + strlcpy((char *)&dmname.an_chars[0], tsmd->attrib_name, sizeof(dmname.an_chars)); + + lerrno = 0; + + do { + ret = dm_get_dmattr(*dmsession_id, dmhandle, dmhandle_len, + DM_NO_TOKEN, &dmname, sizeof(buf), buf, &rlen); + if (ret == -1 && errno == EINVAL) { + DEBUG(0, ("Stale DMAPI session, re-creating it.\n")); + lerrno = EINVAL; + if (dmapi_new_session()) { + dmsession_id = dmapi_get_current_session(); + } else { + DEBUG(0, + ("Unable to re-create DMAPI session, assuming offline (%s) - %s\n", + path, strerror(errno))); + offline = true; + dm_handle_free(dmhandle, dmhandle_len); + goto done; + } + } + } while (ret == -1 && lerrno == EINVAL); + + /* its offline if the specified DMAPI attribute exists */ + offline = (ret == 0 || (ret == -1 && errno == E2BIG)); + + DEBUG(10,("dm_get_dmattr %s ret=%d (%s)\n", path, ret, strerror(errno))); + + ret = 0; + + dm_handle_free(dmhandle, dmhandle_len); + +done: + unbecome_root(); + return offline; +} + + +static bool tsmsm_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp) +{ + SMB_STRUCT_STAT sbuf; + struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data; + /* see if the file might be offline. This is called before each IO + to ensure we use AIO if the file is offline. We don't do the full dmapi + call as that would be too slow, instead we err on the side of using AIO + if the file might be offline + */ + if(SMB_VFS_FSTAT(fsp, &sbuf) == 0) { + DEBUG(10,("tsmsm_aio_force st_blocks=%ld st_size=%ld online_ratio=%.2f\n", + sbuf.st_blocks, sbuf.st_size, tsmd->online_ratio)); + return !(512 * (off_t)sbuf.st_blocks >= sbuf.st_size * tsmd->online_ratio); + } + return false; +} + +static ssize_t tsmsm_aio_return(struct vfs_handle_struct *handle, struct files_struct *fsp, + SMB_STRUCT_AIOCB *aiocb) +{ + ssize_t result; + + result = SMB_VFS_NEXT_AIO_RETURN(handle, fsp, aiocb); + if(result >= 0) { + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name); + } + + return result; +} + +static ssize_t tsmsm_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fsp, const DATA_BLOB *hdr, + SMB_OFF_T offset, size_t n) +{ + bool file_online = tsmsm_aio_force(handle, fsp); + + if(!file_online) + return ENOSYS; + + return SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, hdr, offset, n); +} + +/* We do overload pread to allow notification when file becomes online after offline status */ +/* We don't intercept SMB_VFS_READ here because all file I/O now goes through SMB_VFS_PREAD instead */ +static ssize_t tsmsm_pread(struct vfs_handle_struct *handle, struct files_struct *fsp, + void *data, size_t n, SMB_OFF_T offset) { + ssize_t result; + bool notify_online = tsmsm_aio_force(handle, fsp); + + result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset); + if((result != -1) && notify_online) { + /* We can't actually force AIO at this point (came here not from reply_read_and_X) + what we can do is to send notification that file became online + */ + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name); + } + + return result; +} + +static ssize_t tsmsm_pwrite(struct vfs_handle_struct *handle, struct files_struct *fsp, + void *data, size_t n, SMB_OFF_T offset) { + ssize_t result; + bool notify_online = tsmsm_aio_force(handle, fsp); + + result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset); + if((result != -1) && notify_online) { + /* We can't actually force AIO at this point (came here not from reply_read_and_X) + what we can do is to send notification that file became online + */ + notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED, + FILE_NOTIFY_CHANGE_ATTRIBUTES, + fsp->fsp_name); + } + + return result; +} + +static int tsmsm_set_offline(struct vfs_handle_struct *handle, + const char *path) { + struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data; + int result = 0; + char *command; + + if (tsmd->hsmscript == NULL) { + /* no script enabled */ + DEBUG(1, ("tsmsm_set_offline: No tsmsm:hsmscript configured\n")); + return 0; + } + + /* Now, call the script */ + command = talloc_asprintf(tsmd, "%s offline \"%s\"", tsmd->hsmscript, path); + if(!command) { + DEBUG(1, ("tsmsm_set_offline: can't allocate memory to run hsm script")); + return -1; + } + DEBUG(10, ("tsmsm_set_offline: Running [%s]\n", command)); + if((result = smbrun(command, NULL)) != 0) { + DEBUG(1,("tsmsm_set_offline: Running [%s] returned %d\n", command, result)); + } + TALLOC_FREE(command); + return result; +} + +static uint32_t tsmsm_fs_capabilities(struct vfs_handle_struct *handle) +{ + return SMB_VFS_NEXT_FS_CAPABILITIES(handle) | FILE_SUPPORTS_REMOTE_STORAGE | FILE_SUPPORTS_REPARSE_POINTS; +} + +static vfs_op_tuple vfs_tsmsm_ops[] = { + + /* Disk operations */ + + {SMB_VFS_OP(tsmsm_connect), SMB_VFS_OP_CONNECT, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_fs_capabilities), SMB_VFS_OP_FS_CAPABILITIES, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_aio_force), SMB_VFS_OP_AIO_FORCE, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_aio_return), SMB_VFS_OP_AIO_RETURN, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_pread), SMB_VFS_OP_PREAD, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_pwrite), SMB_VFS_OP_PWRITE, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_sendfile), SMB_VFS_OP_SENDFILE, + SMB_VFS_LAYER_TRANSPARENT}, + {SMB_VFS_OP(tsmsm_is_offline), SMB_VFS_OP_IS_OFFLINE, + SMB_VFS_LAYER_OPAQUE}, + {SMB_VFS_OP(tsmsm_set_offline), SMB_VFS_OP_SET_OFFLINE, + SMB_VFS_LAYER_OPAQUE}, + + /* Finish VFS operations definition */ + + {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, + SMB_VFS_LAYER_NOOP} +}; + +NTSTATUS vfs_tsmsm_init(void); +NTSTATUS vfs_tsmsm_init(void) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "tsmsm", vfs_tsmsm_ops); +} diff --git a/source3/modules/vfs_xattr_tdb.c b/source3/modules/vfs_xattr_tdb.c index 3a72831b5b..7b5e510747 100644 --- a/source3/modules/vfs_xattr_tdb.c +++ b/source3/modules/vfs_xattr_tdb.c @@ -110,7 +110,7 @@ static NTSTATUS xattr_tdb_load_attrs(TALLOC_CTX *mem_ctx, status = xattr_tdb_pull_attrs(mem_ctx, &data, presult); TALLOC_FREE(data.dptr); - return NT_STATUS_OK; + return status; } /* @@ -134,7 +134,7 @@ static struct db_record *xattr_tdb_lock_attrs(TALLOC_CTX *mem_ctx, static NTSTATUS xattr_tdb_save_attrs(struct db_record *rec, const struct tdb_xattrs *attribs) { - TDB_DATA data; + TDB_DATA data = tdb_null; NTSTATUS status; status = xattr_tdb_push_attrs(talloc_tos(), attribs, &data); @@ -165,6 +165,9 @@ static ssize_t xattr_tdb_getattr(struct db_context *db_ctx, ssize_t result = -1; NTSTATUS status; + DEBUG(10, ("xattr_tdb_getattr called for file %s, name %s\n", + file_id_string_tos(id), name)); + status = xattr_tdb_load_attrs(talloc_tos(), db_ctx, id, &attribs); if (!NT_STATUS_IS_OK(status)) { @@ -250,6 +253,9 @@ static int xattr_tdb_setattr(struct db_context *db_ctx, struct tdb_xattrs *attribs; uint32_t i; + DEBUG(10, ("xattr_tdb_setattr called for file %s, name %s\n", + file_id_string_tos(id), name)); + rec = xattr_tdb_lock_attrs(talloc_tos(), db_ctx, id); if (rec == NULL) { @@ -269,6 +275,11 @@ static int xattr_tdb_setattr(struct db_context *db_ctx, for (i=0; i<attribs->num_xattrs; i++) { if (strcmp(attribs->xattrs[i].name, name) == 0) { + if (flags & XATTR_CREATE) { + TALLOC_FREE(rec); + errno = EEXIST; + return -1; + } break; } } @@ -276,6 +287,12 @@ static int xattr_tdb_setattr(struct db_context *db_ctx, if (i == attribs->num_xattrs) { struct tdb_xattr *tmp; + if (flags & XATTR_REPLACE) { + TALLOC_FREE(rec); + errno = ENOATTR; + return -1; + } + tmp = TALLOC_REALLOC_ARRAY( attribs, attribs->xattrs, struct tdb_xattr, attribs->num_xattrs + 1); @@ -558,10 +575,11 @@ static bool xattr_tdb_init(int snum, struct db_context **p_db) struct db_context *db; const char *dbname; - dbname = lp_parm_const_string(snum, "ea", "tdb", lock_path("eas.tdb")); + dbname = lp_parm_const_string(snum, "xattr_tdb", "file", + lock_path("xattr.tdb")); if (dbname == NULL) { - errno = ENOTSUP; + errno = ENOSYS; return false; } @@ -570,7 +588,11 @@ static bool xattr_tdb_init(int snum, struct db_context **p_db) unbecome_root(); if (db == NULL) { +#if defined(ENOTSUP) errno = ENOTSUP; +#else + errno = ENOSYS; +#endif return false; } @@ -660,7 +682,7 @@ static int xattr_tdb_rmdir(vfs_handle_struct *handle, const char *path) * Destructor for the VFS private data */ -static void close_ea_db(void **data) +static void close_xattr_db(void **data) { struct db_context **p_db = (struct db_context **)data; TALLOC_FREE(*p_db); @@ -688,14 +710,14 @@ static int xattr_tdb_connect(vfs_handle_struct *handle, const char *service, } if (!xattr_tdb_init(snum, &db)) { - DEBUG(5, ("Could not init ea tdb\n")); + DEBUG(5, ("Could not init xattr tdb\n")); lp_do_parameter(snum, "ea support", "False"); return 0; } lp_do_parameter(snum, "ea support", "True"); - SMB_VFS_HANDLE_SET_DATA(handle, db, close_ea_db, + SMB_VFS_HANDLE_SET_DATA(handle, db, close_xattr_db, struct db_context, return -1); return 0; |