mirror of
https://github.com/samba-team/samba.git
synced 2025-02-23 09:57:40 +03:00
This patch introduces struct stat_ex { dev_t st_ex_dev; ino_t st_ex_ino; mode_t st_ex_mode; nlink_t st_ex_nlink; uid_t st_ex_uid; gid_t st_ex_gid; dev_t st_ex_rdev; off_t st_ex_size; struct timespec st_ex_atime; struct timespec st_ex_mtime; struct timespec st_ex_ctime; struct timespec st_ex_btime; /* birthtime */ blksize_t st_ex_blksize; blkcnt_t st_ex_blocks; }; typedef struct stat_ex SMB_STRUCT_STAT; It is really large because due to the friendly libc headers playing macro tricks with fields like st_ino, so I renamed them to st_ex_xxx. Why this change? To support birthtime, we already have quite a few #ifdef's at places where it does not really belong. With a stat struct that we control, we can consolidate the nanosecond timestamps and the birthtime deep in the VFS stat calls. At this moment it is triggered by a request to support the birthtime field for GPFS. GPFS does not extend the system level struct stat, but instead has a separate call that gets us the additional information beyond posix. Without being able to do that within the VFS stat calls, that support would have to be scattered around the main smbd code. It will very likely break all the onefs modules, but I think the changes will be reasonably easy to do.
982 lines
22 KiB
C
982 lines
22 KiB
C
/*
|
|
* 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;
|
|
void *fsp_name_ptr;
|
|
files_struct *fsp;
|
|
vfs_handle_struct *handle;
|
|
};
|
|
|
|
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_ex_dev,
|
|
(unsigned long)sbuf->st_ex_ino, sname));
|
|
|
|
upper_sname = talloc_strdup_upper(talloc_tos(), sname);
|
|
SMB_ASSERT(upper_sname != NULL);
|
|
|
|
MD5Init(&ctx);
|
|
MD5Update(&ctx, (unsigned char *)&(sbuf->st_ex_dev),
|
|
sizeof(sbuf->st_ex_dev));
|
|
MD5Update(&ctx, (unsigned char *)&(sbuf->st_ex_ino),
|
|
sizeof(sbuf->st_ex_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,
|
|
files_struct *fsp,
|
|
const char *fname,
|
|
const char *xattr_name)
|
|
{
|
|
NTSTATUS status;
|
|
struct ea_struct ea;
|
|
ssize_t result;
|
|
|
|
status = get_ea_value(talloc_tos(), conn, fsp, 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 bool streams_xattr_recheck(struct stream_io *sio)
|
|
{
|
|
NTSTATUS status;
|
|
char *base = NULL;
|
|
char *sname = NULL;
|
|
char *xattr_name = NULL;
|
|
|
|
if (sio->fsp->fsp_name == sio->fsp_name_ptr) {
|
|
return true;
|
|
}
|
|
|
|
status = split_ntfs_stream_name(talloc_tos(), sio->fsp->fsp_name,
|
|
&base, &sname);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
return false;
|
|
}
|
|
|
|
if (sname == NULL) {
|
|
/* how can this happen */
|
|
errno = EINVAL;
|
|
return false;
|
|
}
|
|
|
|
xattr_name = talloc_asprintf(talloc_tos(), "%s%s",
|
|
SAMBA_XATTR_DOSSTREAM_PREFIX, sname);
|
|
if (xattr_name == NULL) {
|
|
return false;
|
|
}
|
|
|
|
TALLOC_FREE(sio->xattr_name);
|
|
TALLOC_FREE(sio->base);
|
|
sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
|
|
xattr_name);
|
|
sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
|
|
base);
|
|
sio->fsp_name_ptr = sio->fsp->fsp_name;
|
|
|
|
if ((sio->xattr_name == NULL) || (sio->base == NULL)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int streams_xattr_fstat(vfs_handle_struct *handle, files_struct *fsp,
|
|
SMB_STRUCT_STAT *sbuf)
|
|
{
|
|
int ret = -1;
|
|
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 || fsp->base_fsp == NULL) {
|
|
return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
|
|
}
|
|
|
|
if (!streams_xattr_recheck(io)) {
|
|
return -1;
|
|
}
|
|
|
|
if (lp_posix_pathnames()) {
|
|
ret = SMB_VFS_LSTAT(handle->conn, io->base, sbuf);
|
|
} else {
|
|
ret = SMB_VFS_STAT(handle->conn, io->base, sbuf);
|
|
}
|
|
|
|
if (ret == -1) {
|
|
return -1;
|
|
}
|
|
|
|
sbuf->st_ex_size = get_xattr_size(handle->conn, fsp->base_fsp,
|
|
io->base, io->xattr_name);
|
|
if (sbuf->st_ex_size == -1) {
|
|
return -1;
|
|
}
|
|
|
|
DEBUG(10, ("sbuf->st_ex_size = %d\n", (int)sbuf->st_ex_size));
|
|
|
|
sbuf->st_ex_ino = stream_inode(sbuf, io->xattr_name);
|
|
sbuf->st_ex_mode &= ~S_IFMT;
|
|
sbuf->st_ex_mode |= S_IFREG;
|
|
sbuf->st_ex_blocks = sbuf->st_ex_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 (sname == NULL){
|
|
return SMB_VFS_NEXT_STAT(handle, base, sbuf);
|
|
}
|
|
|
|
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_ex_size = get_xattr_size(handle->conn, NULL, base, xattr_name);
|
|
if (sbuf->st_ex_size == -1) {
|
|
errno = ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
sbuf->st_ex_ino = stream_inode(sbuf, xattr_name);
|
|
sbuf->st_ex_mode &= ~S_IFMT;
|
|
sbuf->st_ex_mode |= S_IFREG;
|
|
sbuf->st_ex_blocks = sbuf->st_ex_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 (sname == NULL){
|
|
return SMB_VFS_NEXT_LSTAT(handle, base, sbuf);
|
|
}
|
|
|
|
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_ex_size = get_xattr_size(handle->conn, NULL, base, xattr_name);
|
|
if (sbuf->st_ex_size == -1) {
|
|
errno = ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
sbuf->st_ex_ino = stream_inode(sbuf, xattr_name);
|
|
sbuf->st_ex_mode &= ~S_IFMT;
|
|
sbuf->st_ex_mode |= S_IFREG;
|
|
sbuf->st_ex_blocks = sbuf->st_ex_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;
|
|
}
|
|
|
|
if (sname == NULL) {
|
|
hostfd = SMB_VFS_NEXT_OPEN(handle, base, fsp, flags, mode);
|
|
talloc_free(frame);
|
|
return hostfd;
|
|
}
|
|
|
|
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 (fsp->base_fsp->fh->fd != -1) {
|
|
if (SMB_VFS_FSETXATTR(
|
|
fsp->base_fsp, xattr_name,
|
|
&null, sizeof(null),
|
|
flags & O_EXCL ? XATTR_CREATE : 0) == -1) {
|
|
goto fail;
|
|
}
|
|
} else {
|
|
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 (fsp->base_fsp->fh->fd != -1) {
|
|
if (SMB_VFS_FSETXATTR(
|
|
fsp->base_fsp, xattr_name,
|
|
&null, sizeof(null),
|
|
flags & O_EXCL ? XATTR_CREATE : 0) == -1) {
|
|
goto fail;
|
|
}
|
|
} else {
|
|
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,
|
|
NULL);
|
|
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);
|
|
sio->fsp_name_ptr = fsp->fsp_name;
|
|
sio->handle = handle;
|
|
sio->fsp = fsp;
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (sname == NULL){
|
|
return SMB_VFS_NEXT_UNLINK(handle, base);
|
|
}
|
|
|
|
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 int streams_xattr_rename(vfs_handle_struct *handle,
|
|
const char *oldname,
|
|
const char *newname)
|
|
{
|
|
NTSTATUS status;
|
|
TALLOC_CTX *frame = NULL;
|
|
char *obase;
|
|
char *ostream;
|
|
char *nbase;
|
|
char *nstream;
|
|
const char *base;
|
|
int ret = -1;
|
|
char *oxattr_name;
|
|
char *nxattr_name;
|
|
bool o_is_stream;
|
|
bool n_is_stream;
|
|
ssize_t oret;
|
|
ssize_t nret;
|
|
struct ea_struct ea;
|
|
|
|
o_is_stream = is_ntfs_stream_name(oldname);
|
|
n_is_stream = is_ntfs_stream_name(newname);
|
|
|
|
if (!o_is_stream && !n_is_stream) {
|
|
return SMB_VFS_NEXT_RENAME(handle, oldname, newname);
|
|
}
|
|
|
|
frame = talloc_stackframe();
|
|
if (!frame) {
|
|
goto fail;
|
|
}
|
|
|
|
status = split_ntfs_stream_name(talloc_tos(), oldname, &obase, &ostream);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
status = split_ntfs_stream_name(talloc_tos(), newname, &nbase, &nstream);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
errno = EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/*TODO: maybe call SMB_VFS_NEXT_RENAME() both streams are NULL (::$DATA) */
|
|
if (ostream == NULL) {
|
|
errno = ENOSYS;
|
|
goto fail;
|
|
}
|
|
|
|
if (nstream == NULL) {
|
|
errno = ENOSYS;
|
|
goto fail;
|
|
}
|
|
|
|
if (StrCaseCmp(ostream, nstream) == 0) {
|
|
goto done;
|
|
}
|
|
|
|
base = obase;
|
|
|
|
oxattr_name = talloc_asprintf(talloc_tos(), "%s%s",
|
|
SAMBA_XATTR_DOSSTREAM_PREFIX, ostream);
|
|
if (oxattr_name == NULL) {
|
|
errno = ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
nxattr_name = talloc_asprintf(talloc_tos(), "%s%s",
|
|
SAMBA_XATTR_DOSSTREAM_PREFIX, nstream);
|
|
if (nxattr_name == NULL) {
|
|
errno = ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
/* read the old stream */
|
|
status = get_ea_value(talloc_tos(), handle->conn, NULL,
|
|
base, oxattr_name, &ea);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
errno = ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
/* (over)write the new stream */
|
|
nret = SMB_VFS_SETXATTR(handle->conn, base, nxattr_name,
|
|
ea.value.data, ea.value.length, 0);
|
|
if (nret < 0) {
|
|
if (errno == ENOATTR) {
|
|
errno = ENOENT;
|
|
}
|
|
goto fail;
|
|
}
|
|
|
|
/* remove the old stream */
|
|
oret = SMB_VFS_REMOVEXATTR(handle->conn, base, oxattr_name);
|
|
if (oret < 0) {
|
|
if (errno == ENOATTR) {
|
|
errno = ENOENT;
|
|
}
|
|
goto fail;
|
|
}
|
|
|
|
done:
|
|
errno = 0;
|
|
ret = 0;
|
|
fail:
|
|
TALLOC_FREE(frame);
|
|
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;
|
|
}
|
|
if (lp_posix_pathnames()) {
|
|
ret = SMB_VFS_LSTAT(handle->conn, fname, &sbuf);
|
|
} else {
|
|
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_ex_mode)) {
|
|
if (!add_one_stream(mem_ctx,
|
|
&state.num_streams, &state.streams,
|
|
"::$DATA", sbuf.st_ex_size,
|
|
SMB_VFS_GET_ALLOC_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);
|
|
}
|
|
|
|
if (!streams_xattr_recheck(sio)) {
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
|
|
if (fsp->base_fsp->fh->fd != -1) {
|
|
ret = SMB_VFS_FSETXATTR(fsp->base_fsp,
|
|
sio->xattr_name,
|
|
ea.value.data, ea.value.length, 0);
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
if (!streams_xattr_recheck(sio)) {
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int streams_xattr_ftruncate(struct vfs_handle_struct *handle,
|
|
struct files_struct *fsp,
|
|
SMB_OFF_T offset)
|
|
{
|
|
int ret;
|
|
uint8 *tmp;
|
|
struct ea_struct ea;
|
|
NTSTATUS status;
|
|
struct stream_io *sio =
|
|
(struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
|
|
|
|
DEBUG(10, ("streams_xattr_ftruncate called for file %s offset %.0f\n",
|
|
fsp->fsp_name,
|
|
(double)offset ));
|
|
|
|
if (sio == NULL) {
|
|
return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
|
|
}
|
|
|
|
if (!streams_xattr_recheck(sio)) {
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
tmp = TALLOC_REALLOC_ARRAY(talloc_tos(), ea.value.data, uint8,
|
|
offset + 1);
|
|
|
|
if (tmp == NULL) {
|
|
TALLOC_FREE(ea.value.data);
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
/* Did we expand ? */
|
|
if (ea.value.length < offset + 1) {
|
|
memset(&tmp[ea.value.length], '\0',
|
|
offset + 1 - ea.value.length);
|
|
}
|
|
|
|
ea.value.data = tmp;
|
|
ea.value.length = offset + 1;
|
|
ea.value.data[offset] = 0;
|
|
|
|
if (fsp->base_fsp->fh->fd != -1) {
|
|
ret = SMB_VFS_FSETXATTR(fsp->base_fsp,
|
|
sio->xattr_name,
|
|
ea.value.data, ea.value.length, 0);
|
|
} else {
|
|
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 0;
|
|
}
|
|
|
|
/* 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_unlink), SMB_VFS_OP_UNLINK,
|
|
SMB_VFS_LAYER_TRANSPARENT},
|
|
{SMB_VFS_OP(streams_xattr_rename), SMB_VFS_OP_RENAME,
|
|
SMB_VFS_LAYER_TRANSPARENT},
|
|
{SMB_VFS_OP(streams_xattr_ftruncate), SMB_VFS_OP_FTRUNCATE,
|
|
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);
|
|
}
|