1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-24 21:34:56 +03:00
samba-mirror/source3/modules/vfs_streams_depot.c
Jeremy Allison 649f544ab2 s3: VFS: streams_depot: Allow "streams directory" outside of share path to work again.
As we're dealing with absolute paths here, we just need
to temporarily replace the connectpath whilst enumerating
streams.

Remove knownfail file.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14760

Signed-off-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Noel Power <noel.power@suse.com>

Autobuild-User(master): Jeremy Allison <jra@samba.org>
Autobuild-Date(master): Thu Aug 19 17:04:44 UTC 2021 on sn-devel-184
2021-08-19 17:04:44 +00:00

1233 lines
28 KiB
C

/*
* 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"
#include "smbd/smbd.h"
#include "system/filesys.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.
*/
static bool file_is_valid(vfs_handle_struct *handle,
const struct smb_filename *smb_fname)
{
char buf;
NTSTATUS status;
struct smb_filename *pathref = NULL;
int ret;
DEBUG(10, ("file_is_valid (%s) called\n", smb_fname->base_name));
status = synthetic_pathref(talloc_tos(),
handle->conn->cwd_fsp,
smb_fname->base_name,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags,
&pathref);
if (!NT_STATUS_IS_OK(status)) {
return false;
}
ret = SMB_VFS_FGETXATTR(pathref->fsp,
SAMBA_XATTR_MARKER,
&buf,
sizeof(buf));
if (ret != sizeof(buf)) {
int saved_errno = errno;
DBG_DEBUG("FGETXATTR failed: %s\n", strerror(saved_errno));
TALLOC_FREE(pathref);
errno = saved_errno;
return false;
}
TALLOC_FREE(pathref);
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 struct smb_filename *smb_fname)
{
char buf = '1';
int ret;
DEBUG(10, ("marking file %s as valid\n", smb_fname->base_name));
ret = SMB_VFS_FSETXATTR(smb_fname->fsp, SAMBA_XATTR_MARKER,
&buf, sizeof(buf), 0);
if (ret == -1) {
DEBUG(10, ("SETXATTR failed: %s\n", strerror(errno)));
return false;
}
return true;
}
/*
* Return the root of the stream directory. Can be
* external to the share definition but by default
* is "handle->conn->connectpath/.streams".
*
* Note that this is an *absolute* path, starting
* with '/', so the dirfsp being used in the
* calls below isn't looked at.
*/
static char *stream_rootdir(vfs_handle_struct *handle,
TALLOC_CTX *ctx)
{
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
char *tmp;
tmp = talloc_asprintf(ctx,
"%s/.streams",
handle->conn->connectpath);
if (tmp == NULL) {
errno = ENOMEM;
return NULL;
}
return lp_parm_substituted_string(ctx,
lp_sub,
SNUM(handle->conn),
"streams_depot",
"directory",
tmp);
}
/**
* Given an smb_filename, determine the stream directory using the file's
* base_name.
*/
static char *stream_dir(vfs_handle_struct *handle,
const struct smb_filename *smb_fname,
const SMB_STRUCT_STAT *base_sbuf, bool create_it)
{
uint32_t hash;
struct smb_filename *smb_fname_hash = NULL;
char *result = NULL;
SMB_STRUCT_STAT base_sbuf_tmp;
char *tmp = NULL;
uint8_t first, second;
char *id_hex;
struct file_id id;
uint8_t id_buf[16];
bool check_valid;
char *rootdir = NULL;
struct smb_filename *rootdir_fname = NULL;
struct smb_filename *tmp_fname = NULL;
struct smb_filename *tmpref = NULL;
const struct smb_filename *pathref = NULL;
int ret;
check_valid = lp_parm_bool(SNUM(handle->conn),
"streams_depot", "check_valid", true);
rootdir = stream_rootdir(handle,
talloc_tos());
if (rootdir == NULL) {
errno = ENOMEM;
goto fail;
}
rootdir_fname = synthetic_smb_fname(talloc_tos(),
rootdir,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (rootdir_fname == NULL) {
errno = ENOMEM;
goto fail;
}
/* Stat the base file if it hasn't already been done. */
if (base_sbuf == NULL) {
struct smb_filename *smb_fname_base;
smb_fname_base = synthetic_smb_fname(
talloc_tos(),
smb_fname->base_name,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (smb_fname_base == NULL) {
errno = ENOMEM;
goto fail;
}
if (SMB_VFS_NEXT_STAT(handle, smb_fname_base) == -1) {
TALLOC_FREE(smb_fname_base);
goto fail;
}
base_sbuf_tmp = smb_fname_base->st;
TALLOC_FREE(smb_fname_base);
} else {
base_sbuf_tmp = *base_sbuf;
}
id = SMB_VFS_FILE_ID_CREATE(handle->conn, &base_sbuf_tmp);
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(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;
}
smb_fname_hash = synthetic_smb_fname(talloc_tos(),
result,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (smb_fname_hash == NULL) {
errno = ENOMEM;
goto fail;
}
if (SMB_VFS_NEXT_STAT(handle, smb_fname_hash) == 0) {
struct smb_filename *smb_fname_new = NULL;
char *newname;
bool delete_lost;
if (!S_ISDIR(smb_fname_hash->st.st_ex_mode)) {
errno = EINVAL;
goto fail;
}
if (!check_valid ||
file_is_valid(handle, smb_fname)) {
return result;
}
/*
* Someone has recreated a file under an existing inode
* without deleting the streams directory.
* Move it away or remove if streams_depot:delete_lost is set.
*/
again:
delete_lost = lp_parm_bool(SNUM(handle->conn), "streams_depot",
"delete_lost", false);
if (delete_lost) {
DEBUG(3, ("Someone has recreated a file under an "
"existing inode. Removing: %s\n",
smb_fname_hash->base_name));
recursive_rmdir(talloc_tos(), handle->conn,
smb_fname_hash);
SMB_VFS_NEXT_UNLINKAT(handle,
handle->conn->cwd_fsp,
smb_fname_hash,
AT_REMOVEDIR);
} else {
newname = talloc_asprintf(talloc_tos(), "lost-%lu",
random());
DEBUG(3, ("Someone has recreated a file under an "
"existing inode. Renaming: %s to: %s\n",
smb_fname_hash->base_name,
newname));
if (newname == NULL) {
errno = ENOMEM;
goto fail;
}
smb_fname_new = synthetic_smb_fname(
talloc_tos(),
newname,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
TALLOC_FREE(newname);
if (smb_fname_new == NULL) {
errno = ENOMEM;
goto fail;
}
if (SMB_VFS_NEXT_RENAMEAT(handle,
handle->conn->cwd_fsp,
smb_fname_hash,
handle->conn->cwd_fsp,
smb_fname_new) == -1) {
TALLOC_FREE(smb_fname_new);
if ((errno == EEXIST) || (errno == ENOTEMPTY)) {
goto again;
}
goto fail;
}
TALLOC_FREE(smb_fname_new);
}
}
if (!create_it) {
errno = ENOENT;
goto fail;
}
ret = SMB_VFS_NEXT_MKDIRAT(handle,
handle->conn->cwd_fsp,
rootdir_fname,
0755);
if ((ret != 0) && (errno != EEXIST)) {
goto fail;
}
tmp = talloc_asprintf(result, "%s/%2.2X", rootdir, first);
if (tmp == NULL) {
errno = ENOMEM;
goto fail;
}
tmp_fname = synthetic_smb_fname(talloc_tos(),
tmp,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (tmp_fname == NULL) {
errno = ENOMEM;
goto fail;
}
ret = SMB_VFS_NEXT_MKDIRAT(handle,
handle->conn->cwd_fsp,
tmp_fname,
0755);
if ((ret != 0) && (errno != EEXIST)) {
goto fail;
}
TALLOC_FREE(tmp);
TALLOC_FREE(tmp_fname);
tmp = talloc_asprintf(result, "%s/%2.2X/%2.2X", rootdir, first,
second);
if (tmp == NULL) {
errno = ENOMEM;
goto fail;
}
tmp_fname = synthetic_smb_fname(talloc_tos(),
tmp,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (tmp_fname == NULL) {
errno = ENOMEM;
goto fail;
}
ret = SMB_VFS_NEXT_MKDIRAT(handle,
handle->conn->cwd_fsp,
tmp_fname,
0755);
if ((ret != 0) && (errno != EEXIST)) {
goto fail;
}
TALLOC_FREE(tmp);
TALLOC_FREE(tmp_fname);
/* smb_fname_hash is the struct smb_filename version of 'result' */
ret = SMB_VFS_NEXT_MKDIRAT(handle,
handle->conn->cwd_fsp,
smb_fname_hash,
0755);
if ((ret != 0) && (errno != EEXIST)) {
goto fail;
}
pathref = smb_fname;
if (smb_fname->fsp == NULL) {
NTSTATUS status;
status = synthetic_pathref(talloc_tos(),
handle->conn->cwd_fsp,
smb_fname->base_name,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags,
&tmpref);
if (!NT_STATUS_IS_OK(status)) {
goto fail;
}
pathref = tmpref;
}
if (check_valid && !mark_file_valid(handle, pathref)) {
goto fail;
}
TALLOC_FREE(tmpref);
TALLOC_FREE(rootdir_fname);
TALLOC_FREE(rootdir);
TALLOC_FREE(tmp_fname);
TALLOC_FREE(smb_fname_hash);
return result;
fail:
TALLOC_FREE(tmpref);
TALLOC_FREE(rootdir_fname);
TALLOC_FREE(rootdir);
TALLOC_FREE(tmp_fname);
TALLOC_FREE(smb_fname_hash);
TALLOC_FREE(result);
return NULL;
}
/**
* Given a stream name, populate smb_fname_out with the actual location of the
* stream.
*/
static NTSTATUS stream_smb_fname(vfs_handle_struct *handle,
const struct smb_filename *smb_fname,
struct smb_filename **smb_fname_out,
bool create_dir)
{
char *dirname, *stream_fname;
const char *stype;
NTSTATUS status;
*smb_fname_out = NULL;
stype = strchr_m(smb_fname->stream_name + 1, ':');
if (stype) {
if (strcasecmp_m(stype, ":$DATA") != 0) {
return NT_STATUS_INVALID_PARAMETER;
}
}
dirname = stream_dir(handle, smb_fname, NULL, create_dir);
if (dirname == NULL) {
status = map_nt_error_from_unix(errno);
goto fail;
}
stream_fname = talloc_asprintf(talloc_tos(), "%s/%s", dirname,
smb_fname->stream_name);
if (stream_fname == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
if (stype == NULL) {
/* Append an explicit stream type if one wasn't specified. */
stream_fname = talloc_asprintf(talloc_tos(), "%s:$DATA",
stream_fname);
if (stream_fname == NULL) {
status = NT_STATUS_NO_MEMORY;
goto fail;
}
} else {
/* Normalize the stream type to upercase. */
if (!strupper_m(strrchr_m(stream_fname, ':') + 1)) {
status = NT_STATUS_INVALID_PARAMETER;
goto fail;
}
}
DEBUG(10, ("stream filename = %s\n", stream_fname));
/* Create an smb_filename with stream_name == NULL. */
*smb_fname_out = synthetic_smb_fname(talloc_tos(),
stream_fname,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (*smb_fname_out == NULL) {
return NT_STATUS_NO_MEMORY;
}
return NT_STATUS_OK;
fail:
DEBUG(5, ("stream_name failed: %s\n", strerror(errno)));
TALLOC_FREE(*smb_fname_out);
return status;
}
static NTSTATUS walk_streams(vfs_handle_struct *handle,
struct smb_filename *smb_fname_base,
char **pdirname,
bool (*fn)(const struct smb_filename *dirname,
const char *dirent,
void *private_data),
void *private_data)
{
char *dirname;
char *rootdir = NULL;
char *orig_connectpath = NULL;
struct smb_filename *dir_smb_fname = NULL;
struct smb_Dir *dir_hnd = NULL;
const char *dname = NULL;
long offset = 0;
char *talloced = NULL;
dirname = stream_dir(handle, smb_fname_base, &smb_fname_base->st,
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));
dir_smb_fname = synthetic_smb_fname(talloc_tos(),
dirname,
NULL,
NULL,
smb_fname_base->twrp,
smb_fname_base->flags);
if (dir_smb_fname == NULL) {
TALLOC_FREE(dirname);
return NT_STATUS_NO_MEMORY;
}
/*
* For OpenDir to succeed if the stream rootdir is outside
* the share path, we must temporarily swap out the connect
* path for this share. We're dealing with absolute paths
* here so we don't care about chdir calls.
*/
rootdir = stream_rootdir(handle, talloc_tos());
if (rootdir == NULL) {
TALLOC_FREE(dir_smb_fname);
TALLOC_FREE(dirname);
return NT_STATUS_NO_MEMORY;
}
orig_connectpath = handle->conn->connectpath;
handle->conn->connectpath = rootdir;
dir_hnd = OpenDir(talloc_tos(), handle->conn, dir_smb_fname, NULL, 0);
if (dir_hnd == NULL) {
handle->conn->connectpath = orig_connectpath;
TALLOC_FREE(rootdir);
TALLOC_FREE(dir_smb_fname);
TALLOC_FREE(dirname);
return map_nt_error_from_unix(errno);
}
while ((dname = ReadDirName(dir_hnd, &offset, NULL, &talloced))
!= NULL)
{
if (ISDOT(dname) || ISDOTDOT(dname)) {
TALLOC_FREE(talloced);
continue;
}
DBG_DEBUG("dirent=%s\n", dname);
if (!fn(dir_smb_fname, dname, private_data)) {
TALLOC_FREE(talloced);
break;
}
TALLOC_FREE(talloced);
}
/* Restore the original connectpath. */
handle->conn->connectpath = orig_connectpath;
TALLOC_FREE(rootdir);
TALLOC_FREE(dir_smb_fname);
TALLOC_FREE(dir_hnd);
if (pdirname != NULL) {
*pdirname = dirname;
}
else {
TALLOC_FREE(dirname);
}
return NT_STATUS_OK;
}
static int streams_depot_stat(vfs_handle_struct *handle,
struct smb_filename *smb_fname)
{
struct smb_filename *smb_fname_stream = NULL;
NTSTATUS status;
int ret = -1;
DEBUG(10, ("streams_depot_stat called for [%s]\n",
smb_fname_str_dbg(smb_fname)));
if (!is_named_stream(smb_fname)) {
return SMB_VFS_NEXT_STAT(handle, smb_fname);
}
/* Stat the actual stream now. */
status = stream_smb_fname(handle, smb_fname, &smb_fname_stream,
false);
if (!NT_STATUS_IS_OK(status)) {
ret = -1;
errno = map_errno_from_nt_status(status);
goto done;
}
ret = SMB_VFS_NEXT_STAT(handle, smb_fname_stream);
/* Update the original smb_fname with the stat info. */
smb_fname->st = smb_fname_stream->st;
done:
TALLOC_FREE(smb_fname_stream);
return ret;
}
static int streams_depot_lstat(vfs_handle_struct *handle,
struct smb_filename *smb_fname)
{
struct smb_filename *smb_fname_stream = NULL;
NTSTATUS status;
int ret = -1;
DEBUG(10, ("streams_depot_lstat called for [%s]\n",
smb_fname_str_dbg(smb_fname)));
if (!is_named_stream(smb_fname)) {
return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
}
/* Stat the actual stream now. */
status = stream_smb_fname(handle, smb_fname, &smb_fname_stream,
false);
if (!NT_STATUS_IS_OK(status)) {
ret = -1;
errno = map_errno_from_nt_status(status);
goto done;
}
ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname_stream);
done:
TALLOC_FREE(smb_fname_stream);
return ret;
}
static int streams_depot_openat(struct vfs_handle_struct *handle,
const struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
struct files_struct *fsp,
int flags,
mode_t mode)
{
struct smb_filename *smb_fname_stream = NULL;
struct smb_filename *smb_fname_base = NULL;
struct files_struct *fspcwd = NULL;
NTSTATUS status;
int ret = -1;
if (!is_named_stream(smb_fname)) {
return SMB_VFS_NEXT_OPENAT(handle,
dirfsp,
smb_fname,
fsp,
flags,
mode);
}
/*
* For now assert this so the below SMB_VFS_STAT() is ok.
*/
SMB_ASSERT(fsp_get_pathref_fd(dirfsp) == AT_FDCWD);
/* Ensure the base file still exists. */
smb_fname_base = synthetic_smb_fname(talloc_tos(),
smb_fname->base_name,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (smb_fname_base == NULL) {
ret = -1;
errno = ENOMEM;
goto done;
}
ret = SMB_VFS_NEXT_STAT(handle, smb_fname_base);
if (ret == -1) {
goto done;
}
/* Determine the stream name, and then open it. */
status = stream_smb_fname(handle, smb_fname, &smb_fname_stream, true);
if (!NT_STATUS_IS_OK(status)) {
ret = -1;
errno = map_errno_from_nt_status(status);
goto done;
}
status = vfs_at_fspcwd(talloc_tos(), handle->conn, &fspcwd);
if (!NT_STATUS_IS_OK(status)) {
ret = -1;
errno = map_errno_from_nt_status(status);
goto done;
}
ret = SMB_VFS_NEXT_OPENAT(handle,
fspcwd,
smb_fname_stream,
fsp,
flags,
mode);
done:
TALLOC_FREE(smb_fname_stream);
TALLOC_FREE(smb_fname_base);
TALLOC_FREE(fspcwd);
return ret;
}
static int streams_depot_unlink_internal(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
int flags)
{
struct smb_filename *full_fname = NULL;
char *dirname = NULL;
int ret = -1;
full_fname = full_path_from_dirfsp_atname(talloc_tos(),
dirfsp,
smb_fname);
if (full_fname == NULL) {
return -1;
}
DEBUG(10, ("streams_depot_unlink called for %s\n",
smb_fname_str_dbg(full_fname)));
/* If there is a valid stream, just unlink the stream and return. */
if (is_named_stream(full_fname)) {
struct smb_filename *smb_fname_stream = NULL;
NTSTATUS status;
status = stream_smb_fname(handle, full_fname, &smb_fname_stream,
false);
TALLOC_FREE(full_fname);
if (!NT_STATUS_IS_OK(status)) {
errno = map_errno_from_nt_status(status);
return -1;
}
ret = SMB_VFS_NEXT_UNLINKAT(handle,
dirfsp->conn->cwd_fsp,
smb_fname_stream,
0);
TALLOC_FREE(smb_fname_stream);
return ret;
}
/*
* We potentially need to delete the per-inode streams directory
*/
if (full_fname->flags & SMB_FILENAME_POSIX_PATH) {
ret = SMB_VFS_NEXT_LSTAT(handle, full_fname);
} else {
ret = SMB_VFS_NEXT_STAT(handle, full_fname);
}
if (ret == -1) {
TALLOC_FREE(full_fname);
return -1;
}
/*
* We know the unlink should succeed as the ACL
* check is already done in the caller. Remove the
* file *after* the streams.
*/
dirname = stream_dir(handle,
full_fname,
&full_fname->st,
false);
TALLOC_FREE(full_fname);
if (dirname != NULL) {
struct smb_filename *smb_fname_dir = NULL;
smb_fname_dir = synthetic_smb_fname(talloc_tos(),
dirname,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (smb_fname_dir == NULL) {
TALLOC_FREE(dirname);
errno = ENOMEM;
return -1;
}
SMB_VFS_NEXT_UNLINKAT(handle,
dirfsp->conn->cwd_fsp,
smb_fname_dir,
AT_REMOVEDIR);
TALLOC_FREE(smb_fname_dir);
TALLOC_FREE(dirname);
}
ret = SMB_VFS_NEXT_UNLINKAT(handle,
dirfsp,
smb_fname,
flags);
return ret;
}
static int streams_depot_rmdir_internal(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname)
{
struct smb_filename *full_fname = NULL;
struct smb_filename *smb_fname_base = NULL;
int ret = -1;
full_fname = full_path_from_dirfsp_atname(talloc_tos(),
dirfsp,
smb_fname);
if (full_fname == NULL) {
return -1;
}
DBG_DEBUG("called for %s\n", full_fname->base_name);
/*
* We potentially need to delete the per-inode streams directory
*/
smb_fname_base = synthetic_smb_fname(talloc_tos(),
full_fname->base_name,
NULL,
NULL,
full_fname->twrp,
full_fname->flags);
TALLOC_FREE(full_fname);
if (smb_fname_base == NULL) {
errno = ENOMEM;
return -1;
}
if (smb_fname_base->flags & SMB_FILENAME_POSIX_PATH) {
ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname_base);
} else {
ret = SMB_VFS_NEXT_STAT(handle, smb_fname_base);
}
if (ret == -1) {
TALLOC_FREE(smb_fname_base);
return -1;
}
/*
* We know the rmdir should succeed as the ACL
* check is already done in the caller. Remove the
* directory *after* the streams.
*/
{
char *dirname = stream_dir(handle, smb_fname_base,
&smb_fname_base->st, false);
if (dirname != NULL) {
struct smb_filename *smb_fname_dir =
synthetic_smb_fname(talloc_tos(),
dirname,
NULL,
NULL,
smb_fname->twrp,
smb_fname->flags);
if (smb_fname_dir == NULL) {
TALLOC_FREE(smb_fname_base);
TALLOC_FREE(dirname);
errno = ENOMEM;
return -1;
}
SMB_VFS_NEXT_UNLINKAT(handle,
dirfsp->conn->cwd_fsp,
smb_fname_dir,
AT_REMOVEDIR);
TALLOC_FREE(smb_fname_dir);
}
TALLOC_FREE(dirname);
}
ret = SMB_VFS_NEXT_UNLINKAT(handle,
dirfsp,
smb_fname,
AT_REMOVEDIR);
TALLOC_FREE(smb_fname_base);
return ret;
}
static int streams_depot_unlinkat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *smb_fname,
int flags)
{
int ret;
if (flags & AT_REMOVEDIR) {
ret = streams_depot_rmdir_internal(handle,
dirfsp,
smb_fname);
} else {
ret = streams_depot_unlink_internal(handle,
dirfsp,
smb_fname,
flags);
}
return ret;
}
static int streams_depot_renameat(vfs_handle_struct *handle,
files_struct *srcfsp,
const struct smb_filename *smb_fname_src,
files_struct *dstfsp,
const struct smb_filename *smb_fname_dst)
{
struct smb_filename *smb_fname_src_stream = NULL;
struct smb_filename *smb_fname_dst_stream = NULL;
struct smb_filename *full_src = NULL;
struct smb_filename *full_dst = NULL;
bool src_is_stream, dst_is_stream;
NTSTATUS status;
int ret = -1;
DEBUG(10, ("streams_depot_renameat called for %s => %s\n",
smb_fname_str_dbg(smb_fname_src),
smb_fname_str_dbg(smb_fname_dst)));
src_is_stream = is_ntfs_stream_smb_fname(smb_fname_src);
dst_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst);
if (!src_is_stream && !dst_is_stream) {
return SMB_VFS_NEXT_RENAMEAT(handle,
srcfsp,
smb_fname_src,
dstfsp,
smb_fname_dst);
}
/* for now don't allow renames from or to the default stream */
if (is_ntfs_default_stream_smb_fname(smb_fname_src) ||
is_ntfs_default_stream_smb_fname(smb_fname_dst)) {
errno = ENOSYS;
goto done;
}
full_src = full_path_from_dirfsp_atname(talloc_tos(),
srcfsp,
smb_fname_src);
if (full_src == NULL) {
errno = ENOMEM;
goto done;
}
full_dst = full_path_from_dirfsp_atname(talloc_tos(),
dstfsp,
smb_fname_dst);
if (full_dst == NULL) {
errno = ENOMEM;
goto done;
}
status = stream_smb_fname(handle, full_src, &smb_fname_src_stream,
false);
if (!NT_STATUS_IS_OK(status)) {
errno = map_errno_from_nt_status(status);
goto done;
}
status = stream_smb_fname(handle, full_dst,
&smb_fname_dst_stream, false);
if (!NT_STATUS_IS_OK(status)) {
errno = map_errno_from_nt_status(status);
goto done;
}
/*
* We must use handle->conn->cwd_fsp as
* srcfsp and dstfsp directory handles here
* as we used the full pathname from the cwd dir
* to calculate the streams directory and filename
* within.
*/
ret = SMB_VFS_NEXT_RENAMEAT(handle,
handle->conn->cwd_fsp,
smb_fname_src_stream,
handle->conn->cwd_fsp,
smb_fname_dst_stream);
done:
TALLOC_FREE(smb_fname_src_stream);
TALLOC_FREE(smb_fname_dst_stream);
return ret;
}
static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
struct stream_struct **streams,
const char *name, off_t size,
off_t alloc_size)
{
struct stream_struct *tmp;
tmp = talloc_realloc(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 struct smb_filename *dirfname,
const char *dirent,
void *private_data)
{
const char *dirname = dirfname->base_name;
struct streaminfo_state *state =
(struct streaminfo_state *)private_data;
struct smb_filename *smb_fname = NULL;
char *sname = NULL;
bool ret;
sname = talloc_asprintf(talloc_tos(), "%s/%s", dirname, dirent);
if (sname == NULL) {
state->status = NT_STATUS_NO_MEMORY;
ret = false;
goto out;
}
smb_fname = synthetic_smb_fname(talloc_tos(),
sname,
NULL,
NULL,
dirfname->twrp,
0);
if (smb_fname == NULL) {
state->status = NT_STATUS_NO_MEMORY;
ret = false;
goto out;
}
if (SMB_VFS_NEXT_STAT(state->handle, smb_fname) == -1) {
DEBUG(10, ("Could not stat %s: %s\n", sname,
strerror(errno)));
ret = true;
goto out;
}
if (!add_one_stream(state->mem_ctx,
&state->num_streams, &state->streams,
dirent, smb_fname->st.st_ex_size,
SMB_VFS_GET_ALLOC_SIZE(state->handle->conn, NULL,
&smb_fname->st))) {
state->status = NT_STATUS_NO_MEMORY;
ret = false;
goto out;
}
ret = true;
out:
TALLOC_FREE(sname);
TALLOC_FREE(smb_fname);
return ret;
}
static NTSTATUS streams_depot_fstreaminfo(vfs_handle_struct *handle,
struct files_struct *fsp,
TALLOC_CTX *mem_ctx,
unsigned int *pnum_streams,
struct stream_struct **pstreams)
{
struct smb_filename *smb_fname_base = NULL;
int ret;
NTSTATUS status;
struct streaminfo_state state;
smb_fname_base = synthetic_smb_fname(talloc_tos(),
fsp->fsp_name->base_name,
NULL,
NULL,
fsp->fsp_name->twrp,
fsp->fsp_name->flags);
if (smb_fname_base == NULL) {
return NT_STATUS_NO_MEMORY;
}
ret = SMB_VFS_NEXT_FSTAT(handle, fsp, &smb_fname_base->st);
if (ret == -1) {
status = map_nt_error_from_unix(errno);
goto out;
}
state.streams = *pstreams;
state.num_streams = *pnum_streams;
state.mem_ctx = mem_ctx;
state.handle = handle;
state.status = NT_STATUS_OK;
status = walk_streams(handle,
smb_fname_base,
NULL,
collect_one_stream,
&state);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(state.streams);
goto out;
}
if (!NT_STATUS_IS_OK(state.status)) {
TALLOC_FREE(state.streams);
status = state.status;
goto out;
}
*pnum_streams = state.num_streams;
*pstreams = state.streams;
status = SMB_VFS_NEXT_FSTREAMINFO(handle,
fsp->base_fsp ? fsp->base_fsp : fsp,
mem_ctx,
pnum_streams,
pstreams);
out:
TALLOC_FREE(smb_fname_base);
return status;
}
static uint32_t streams_depot_fs_capabilities(struct vfs_handle_struct *handle,
enum timestamp_set_resolution *p_ts_res)
{
return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_NAMED_STREAMS;
}
static struct vfs_fn_pointers vfs_streams_depot_fns = {
.fs_capabilities_fn = streams_depot_fs_capabilities,
.openat_fn = streams_depot_openat,
.stat_fn = streams_depot_stat,
.lstat_fn = streams_depot_lstat,
.unlinkat_fn = streams_depot_unlinkat,
.renameat_fn = streams_depot_renameat,
.fstreaminfo_fn = streams_depot_fstreaminfo,
};
static_decl_vfs;
NTSTATUS vfs_streams_depot_init(TALLOC_CTX *ctx)
{
return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_depot",
&vfs_streams_depot_fns);
}