/* * 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 . */ #include "includes.h" #include "smbd/smbd.h" #include "system/filesys.h" #include "source3/smbd/dir.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_VFS struct streams_depot_config_data { const char *directory; bool check_valid; bool delete_lost; }; /* * 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. */ /* * 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); } static int streams_depot_mkdir_pathref(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle, struct files_struct *dirfsp, const char *relname, mode_t mode, struct smb_filename **_result) { struct smb_filename *dirname = dirfsp->fsp_name; struct smb_filename *result = NULL; NTSTATUS status; int ret; result = synthetic_smb_fname( mem_ctx, relname, NULL, NULL, dirname->twrp, dirname->flags); if (result == NULL) { return ENOMEM; } ret = SMB_VFS_NEXT_MKDIRAT(handle, dirfsp, result, mode); if ((ret != 0) && (errno != EEXIST)) { ret = errno; TALLOC_FREE(result); return ret; } status = openat_pathref_fsp_lcomp(dirfsp, result, UCF_POSIX_PATHNAMES); if (!NT_STATUS_IS_OK(status)) { ret = map_errno_from_nt_status(status); TALLOC_FREE(result); return ret; } if (!result->fsp->fsp_flags.is_directory) { TALLOC_FREE(result); return EINVAL; } *_result = result; return 0; } static int streams_depot_rootdir_pathref(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle, NTTIME twrp, uint32_t base_name_flags, bool create_it, struct smb_filename **_result) { struct streams_depot_config_data *config = NULL; struct connection_struct *conn = handle->conn; struct smb_filename *result = NULL; struct smb_filename *parent = NULL; struct smb_filename *rel = NULL; NTSTATUS status; int ret = EINVAL; SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_depot_config_data, return EINVAL); result = synthetic_smb_fname( mem_ctx, config->directory, NULL, NULL, twrp, base_name_flags); if (result == NULL) { return ENOMEM; } status = openat_pathref_fsp(conn->cwd_fsp, result); if (NT_STATUS_IS_OK(status)) { *_result = result; return 0; } if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { goto fail_ntstatus; } if (!create_it) { goto fail_ntstatus; } status = SMB_VFS_PARENT_PATHNAME(conn, mem_ctx, result, &parent, &rel); TALLOC_FREE(result); if (!NT_STATUS_IS_OK(status)) { goto fail_ntstatus; } status = openat_pathref_fsp(conn->cwd_fsp, parent); if (!NT_STATUS_IS_OK(status)) { goto fail_ntstatus; } ret = streams_depot_mkdir_pathref( mem_ctx, handle, parent->fsp, rel->base_name, 0755, &result); if (ret != 0) { goto fail; } *_result = result; result = NULL; fail_ntstatus: ret = map_errno_from_nt_status(status); fail: TALLOC_FREE(rel); TALLOC_FREE(parent); TALLOC_FREE(result); return ret; } struct streams_depot_dirnames { char first[3]; char second[3]; char id_hex[33]; char full[39]; /* first+second+id_hex + slashes */ }; static void streams_depot_get_dirnames(connection_struct *conn, const struct stat_ex *sbuf, struct streams_depot_dirnames *names) { struct file_id id = SMB_VFS_FILE_ID_CREATE(conn, sbuf); uint8_t first, second; uint8_t id_buf[16]; uint32_t hash; push_file_id_16(id_buf, &id); hash = hash_fn(data_blob_const(id_buf, sizeof(id_buf))); first = hash & 0xff; second = (hash >> 8) & 0xff; snprintf(names->first, sizeof(names->first), "%2.2X", first); snprintf(names->second, sizeof(names->second), "%2.2X", second); hex_encode_buf(names->id_hex, id_buf, sizeof(id_buf)); snprintf(names->full, sizeof(names->full), "%s/%s/%s", names->first, names->second, names->id_hex); } static bool stream_dir_valid(struct connection_struct *conn, struct files_struct *base_fsp) { int ret; char buf; ret = SMB_VFS_FGETXATTR(base_fsp, SAMBA_XATTR_MARKER, &buf, sizeof(buf)); if (ret != sizeof(buf)) { DBG_DEBUG("FGETXATTR failed: %s\n", strerror(errno)); return false; } if (buf != '1') { DBG_DEBUG("Got wrong buffer content: '%c'\n", buf); return false; } return true; } static int stream_dir_parent_pathref( TALLOC_CTX *mem_ctx, NTTIME twrp, uint32_t base_fname_flags, struct files_struct *rootdir_fsp, const struct streams_depot_dirnames *dirnames, struct smb_filename **_result) { char stream_dir_parent[6]; struct smb_filename *result = NULL; NTSTATUS status; snprintf(stream_dir_parent, sizeof(stream_dir_parent), "%s/%s", dirnames->first, dirnames->second); result = synthetic_smb_fname(mem_ctx, stream_dir_parent, NULL, NULL, twrp, base_fname_flags | UCF_POSIX_PATHNAMES); if (result == NULL) { return ENOMEM; } status = openat_pathref_fsp(rootdir_fsp, result); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(result); return map_errno_from_nt_status(status); } *_result = result; return 0; } static int stream_dir_remove_invalid( vfs_handle_struct *handle, NTTIME twrp, uint32_t base_fname_flags, struct files_struct *rootdir_fsp, struct files_struct *stream_dirfsp, const struct streams_depot_dirnames *dirnames) { struct streams_depot_config_data *config = NULL; NTSTATUS status; struct smb_filename *stream_dir_parent_fname = NULL; struct smb_filename *stream_dir_old = NULL; struct smb_filename *stream_dir_new = NULL; struct vfs_rename_how rhow = {}; int ret; SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_depot_config_data, return EINVAL); if (config->delete_lost) { DBG_NOTICE("Someone has recreated a file under an " "existing inode. Removing: %s\n", fsp_str_dbg(stream_dirfsp)); status = recursive_rmdir_fsp(stream_dirfsp); return map_errno_from_nt_status(status); } ret = stream_dir_parent_pathref(talloc_tos(), twrp, base_fname_flags, rootdir_fsp, dirnames, &stream_dir_parent_fname); if (ret != 0) { return ret; } stream_dir_old = synthetic_smb_fname(stream_dir_parent_fname, dirnames->id_hex, NULL, NULL, twrp, base_fname_flags | UCF_POSIX_PATHNAMES); if (stream_dir_old == NULL) { TALLOC_FREE(stream_dir_parent_fname); return ENOMEM; } do { char randstr[33]; char loststr[sizeof(randstr) + 5]; generate_random_str_list_buf(randstr, sizeof(randstr), hexchars_lower); snprintf(loststr, sizeof(loststr), "lost-%s", randstr); stream_dir_new = synthetic_smb_fname( stream_dir_parent_fname, loststr, NULL, NULL, twrp, base_fname_flags | UCF_POSIX_PATHNAMES); if (stream_dir_new == NULL) { TALLOC_FREE(stream_dir_parent_fname); return ENOMEM; } ret = SMB_VFS_NEXT_RENAMEAT(handle, stream_dir_parent_fname->fsp, stream_dir_old, stream_dir_parent_fname->fsp, stream_dir_new, &rhow); } while ((ret == -1) && (errno == EEXIST)); if (ret == -1) { ret = errno; TALLOC_FREE(stream_dir_parent_fname); return ret; } TALLOC_FREE(stream_dir_parent_fname); return 0; } static int stream_dir_pathref(TALLOC_CTX *mem_ctx, vfs_handle_struct *handle, const struct smb_filename *base, const struct stat_ex *base_sbuf, bool create_it, struct smb_filename **_result) { struct streams_depot_config_data *config = NULL; struct connection_struct *conn = handle->conn; struct smb_filename *rootdir_pathref = NULL; struct smb_filename *tmp_first = NULL; struct smb_filename *tmp_second = NULL; struct smb_filename *result = NULL; struct streams_depot_dirnames dirnames; NTSTATUS status; int ret = EINVAL; SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_depot_config_data, return EINVAL); ret = streams_depot_rootdir_pathref(talloc_tos(), handle, base->twrp, base->flags, create_it, &rootdir_pathref); if (ret != 0) { return ret; } streams_depot_get_dirnames(conn, base_sbuf, &dirnames); result = synthetic_smb_fname(mem_ctx, dirnames.full, NULL, NULL, base->twrp, base->flags | UCF_POSIX_PATHNAMES); if (result == NULL) { ret = ENOMEM; goto fail; } status = openat_pathref_fsp(rootdir_pathref->fsp, result); if (NT_STATUS_IS_OK(status)) { if (config->check_valid && !stream_dir_valid(conn, base->fsp)) { ret = stream_dir_remove_invalid(handle, base->twrp, base->flags, rootdir_pathref->fsp, result->fsp, &dirnames); if (ret != 0) { TALLOC_FREE(rootdir_pathref); TALLOC_FREE(result); return ret; } } TALLOC_FREE(rootdir_pathref); *_result = result; return 0; } if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND) && !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { ret = map_errno_from_nt_status(status); goto fail; } if (!create_it) { ret = ENOENT; goto fail; } ret = streams_depot_mkdir_pathref(talloc_tos(), handle, rootdir_pathref->fsp, dirnames.first, 0755, &tmp_first); TALLOC_FREE(rootdir_pathref); if (ret != 0) { goto fail; } ret = streams_depot_mkdir_pathref(talloc_tos(), handle, tmp_first->fsp, dirnames.second, 0755, &tmp_second); TALLOC_FREE(tmp_first); if (ret != 0) { goto fail; } ret = streams_depot_mkdir_pathref(mem_ctx, handle, tmp_second->fsp, dirnames.id_hex, 0755, &result); TALLOC_FREE(tmp_second); if (ret != 0) { goto fail; } *_result = result; return 0; fail: TALLOC_FREE(tmp_first); TALLOC_FREE(tmp_second); TALLOC_FREE(rootdir_pathref); TALLOC_FREE(result); return ret; } static int stream_name(TALLOC_CTX *mem_ctx, const char *stream_name_in, char **_result) { const char *stype = strchr_m(stream_name_in + 1, ':'); char *result = NULL; if ((stype != NULL) && !strequal(stype, ":$DATA")) { return EINVAL; } if (stype == NULL) { stype = stream_name_in + strlen(stream_name_in); } result = talloc_asprintf(mem_ctx, "%.*s:$DATA", (int)(stype - stream_name_in), stream_name_in); if (result == NULL) { return ENOMEM; } *_result = result; return 0; } static NTSTATUS walk_streams(vfs_handle_struct *handle, struct files_struct *fsp, bool (*fn)(struct smb_filename *stream_dir_fname, const char *dirent, void *private_data), void *private_data) { struct smb_filename *stream_dir = NULL; struct smb_Dir *dir_hnd = NULL; const char *dname = NULL; char *talloced = NULL; NTSTATUS status; int ret; ret = stream_dir_pathref(talloc_tos(), handle, fsp->fsp_name, &fsp->fsp_name->st, false, &stream_dir); if (ret == ENOENT) { return NT_STATUS_OK; } if (ret != 0) { return map_nt_error_from_unix(ret); } status = OpenDir_from_pathref( talloc_tos(), stream_dir->fsp, NULL, 0, &dir_hnd); if (!NT_STATUS_IS_OK(status)) { TALLOC_FREE(stream_dir); return status; } while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { if (ISDOT(dname) || ISDOTDOT(dname)) { TALLOC_FREE(talloced); continue; } DBG_DEBUG("dirent=%s\n", dname); if (!fn(stream_dir, dname, private_data)) { TALLOC_FREE(talloced); break; } TALLOC_FREE(talloced); } TALLOC_FREE(dir_hnd); TALLOC_FREE(stream_dir); return NT_STATUS_OK; } static int streams_depot_fstatat(struct vfs_handle_struct *handle, const struct files_struct *dirfsp, const struct smb_filename *smb_fname, SMB_STRUCT_STAT *sbuf, int flags) { struct connection_struct *conn = handle->conn; struct files_struct *local_dirfsp = NULL; struct smb_filename *base_fname = NULL; struct smb_filename *base_fname_rel = NULL; struct smb_filename *stream_dir = NULL; struct smb_filename sname = { .twrp = smb_fname->twrp, .flags = smb_fname->flags, }; NTSTATUS status; int ret = -1; DBG_DEBUG("called for [%s/%s]\n", dirfsp->fsp_name->base_name, smb_fname_str_dbg(smb_fname)); if (!is_named_stream(smb_fname)) { return SMB_VFS_NEXT_FSTATAT( handle, dirfsp, smb_fname, sbuf, flags); } status = filename_convert_dirfsp_rel( talloc_tos(), conn, discard_const_p(struct files_struct, dirfsp), smb_fname->base_name, (flags & AT_SYMLINK_NOFOLLOW) ? UCF_LCOMP_LNK_OK : 0, smb_fname->twrp, &local_dirfsp, &base_fname, &base_fname_rel); TALLOC_FREE(base_fname_rel); if (!NT_STATUS_IS_OK(status)) { errno = map_errno_from_nt_status(status); return -1; } close_file_free(NULL, &local_dirfsp, ERROR_CLOSE); if (!VALID_STAT(base_fname->st)) { TALLOC_FREE(base_fname); errno = ENOENT; return -1; } ret = stream_dir_pathref(talloc_tos(), handle, base_fname, &base_fname->st, false, &stream_dir); if (ret != 0) { TALLOC_FREE(base_fname); errno = ret; return -1; } ret = stream_name(talloc_tos(), smb_fname->stream_name, &sname.base_name); if (ret != 0) { TALLOC_FREE(base_fname); errno = ret; return -1; } ret = SMB_VFS_NEXT_FSTATAT(handle, stream_dir->fsp, &sname, sbuf, 0); { int err = errno; TALLOC_FREE(sname.base_name); TALLOC_FREE(base_fname); errno = err; } return ret; } static int streams_depot_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname) { return streams_depot_fstatat( handle, handle->conn->cwd_fsp, smb_fname, &smb_fname->st, 0); } static int streams_depot_lstat(vfs_handle_struct *handle, struct smb_filename *smb_fname) { return streams_depot_fstatat(handle, handle->conn->cwd_fsp, smb_fname, &smb_fname->st, AT_SYMLINK_NOFOLLOW); } 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, const struct vfs_open_how *how) { struct streams_depot_config_data *config = NULL; struct smb_filename *base_name = NULL; struct smb_filename *stream_dir = NULL; char *sname = NULL; struct smb_filename *smb_fname_stream = NULL; bool create_it; int ret = -1; SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_depot_config_data, return EINVAL); if (!is_named_stream(smb_fname)) { return SMB_VFS_NEXT_OPENAT( handle, dirfsp, smb_fname, fsp, how); } if ((how->resolve & ~(VFS_OPEN_HOW_WITH_BACKUP_INTENT | VFS_OPEN_HOW_RESOLVE_NO_XDEV)) != 0) { errno = ENOSYS; return -1; } SMB_ASSERT(fsp_is_alternate_stream(fsp)); SMB_ASSERT(dirfsp == NULL); base_name = fsp->base_fsp->fsp_name; SMB_ASSERT(VALID_STAT(base_name->st)); create_it = (how->flags & O_CREAT); ret = stream_name(talloc_tos(), fsp->fsp_name->stream_name, &sname); if (ret != 0) { errno = ret; ret = -1; goto done; } ret = stream_dir_pathref(talloc_tos(), handle, base_name, &base_name->st, create_it, &stream_dir); if (ret != 0) { errno = ret; ret = -1; goto done; } smb_fname_stream = synthetic_smb_fname(talloc_tos(), sname, NULL, NULL, fsp->fsp_name->twrp, fsp->fsp_name->flags); if (smb_fname_stream == NULL) { errno = ENOMEM; ret = -1; goto done; } if (create_it && config->check_valid) { char buf = '1'; DBG_DEBUG("marking file %s as valid\n", fsp->base_fsp->fsp_name->base_name); ret = SMB_VFS_FSETXATTR(fsp->base_fsp, SAMBA_XATTR_MARKER, &buf, sizeof(buf), 0); if (ret == -1) { DBG_DEBUG("FSETXATTR failed: %s\n", strerror(errno)); goto done; } } ret = SMB_VFS_NEXT_OPENAT( handle, stream_dir->fsp, smb_fname_stream, fsp, how); done: { int err = errno; TALLOC_FREE(stream_dir); TALLOC_FREE(smb_fname_stream); TALLOC_FREE(sname); errno = err; } return ret; } static int streams_depot_unlinkat_stream(vfs_handle_struct *handle, struct files_struct *dirfsp, const struct smb_filename *smb_fname) { struct smb_filename sname = {}; struct smb_filename *base_name = NULL; struct smb_filename *stream_dir = NULL; NTSTATUS status; int ret = -1; if (smb_fname->fsp != NULL) { base_name = smb_fname->fsp->base_fsp->fsp_name; } else { base_name = cp_smb_filename_nostream(talloc_tos(), smb_fname); if (base_name == NULL) { errno = ENOMEM; goto done; } status = openat_pathref_fsp(dirfsp, base_name); if (!NT_STATUS_IS_OK(status)) { errno = map_errno_from_nt_status(status); goto done; } } ret = stream_dir_pathref(talloc_tos(), handle, base_name, &base_name->st, false, &stream_dir); if (ret != 0) { errno = ret; ret = -1; goto done; } sname = (struct smb_filename){ .flags = smb_fname->flags, .twrp = smb_fname->twrp, }; ret = stream_name(stream_dir, smb_fname->stream_name, &sname.base_name); if (ret != 0) { errno = ret; ret = -1; goto done; } ret = SMB_VFS_NEXT_UNLINKAT(handle, stream_dir->fsp, &sname, 0); done: { int err = errno; TALLOC_FREE(stream_dir); errno = err; } return ret; } static int streams_depot_unlinkat(vfs_handle_struct *handle, struct files_struct *dirfsp, const struct smb_filename *smb_fname, int flags) { struct connection_struct *conn = handle->conn; struct streams_depot_dirnames dirnames; struct smb_filename *rootdir_pathref = NULL; struct smb_filename *stream_dir_parent = NULL; struct smb_filename sname = {}; struct stat_ex st; int ret; if (is_named_stream(smb_fname)) { ret = streams_depot_unlinkat_stream(handle, dirfsp, smb_fname); return ret; } ret = SMB_VFS_FSTATAT( conn, dirfsp, smb_fname, &st, AT_SYMLINK_NOFOLLOW); if (ret != 0) { return ret; } streams_depot_get_dirnames(conn, &smb_fname->st, &dirnames); ret = streams_depot_rootdir_pathref(talloc_tos(), handle, smb_fname->twrp, smb_fname->flags, false, &rootdir_pathref); if (ret == 0) { ret = stream_dir_parent_pathref(talloc_tos(), smb_fname->twrp, smb_fname->flags, rootdir_pathref->fsp, &dirnames, &stream_dir_parent); TALLOC_FREE(rootdir_pathref); } if (ret == 0) { sname = (struct smb_filename){ .base_name = dirnames.id_hex, .twrp = smb_fname->twrp, .flags = smb_fname->flags, }; ret = SMB_VFS_UNLINKAT(conn, stream_dir_parent->fsp, &sname, AT_REMOVEDIR); if (ret == -1) { DBG_DEBUG("Removing %s failed: %s, ignoring\n", dirnames.id_hex, strerror(errno)); } } TALLOC_FREE(stream_dir_parent); ret = SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags); return ret; } static int streams_depot_rename_stream(struct vfs_handle_struct *handle, struct files_struct *src_fsp, const char *dst_name, bool replace_if_exists) { struct smb_filename *basename = src_fsp->base_fsp->fsp_name; struct smb_filename *stream_dir = NULL; struct smb_filename smb_fname_src_stream = {}; struct smb_filename smb_fname_dst_stream = {}; struct vfs_rename_how how = {.flags = 0}; int ret; ret = stream_dir_pathref(talloc_tos(), handle, basename, &basename->st, false, &stream_dir); if (ret != 0) { errno = ret; ret = -1; goto done; } ret = stream_name(talloc_tos(), src_fsp->fsp_name->stream_name, &smb_fname_src_stream.base_name); if (ret != 0) { errno = ret; ret = -1; goto done; } ret = stream_name(talloc_tos(), dst_name, &smb_fname_dst_stream.base_name); if (ret != 0) { errno = ret; ret = -1; goto done; } if (!replace_if_exists) { struct stat_ex st; ret = SMB_VFS_FSTATAT(handle->conn, stream_dir->fsp, &smb_fname_dst_stream, &st, AT_SYMLINK_NOFOLLOW); if (ret == 0) { errno = EEXIST; ret = -1; goto done; } } ret = SMB_VFS_NEXT_RENAMEAT(handle, stream_dir->fsp, &smb_fname_src_stream, stream_dir->fsp, &smb_fname_dst_stream, &how); done: { int err = errno; TALLOC_FREE(stream_dir); TALLOC_FREE(smb_fname_src_stream.base_name); TALLOC_FREE(smb_fname_dst_stream.base_name); errno = err; } 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(struct smb_filename *stream_dir_fname, const char *dirent, void *private_data) { struct streaminfo_state *state = (struct streaminfo_state *)private_data; vfs_handle_struct *handle = state->handle; connection_struct *conn = handle->conn; struct smb_filename dirent_fname = { .base_name = discard_const_p(char, dirent), .flags = stream_dir_fname->flags, .twrp = stream_dir_fname->twrp, }; struct stat_ex st; int ret; bool ok; ret = SMB_VFS_NEXT_FSTATAT(conn, stream_dir_fname->fsp, &dirent_fname, &st, AT_SYMLINK_NOFOLLOW); if (ret != 0) { /* ignore */ return true; } ok = add_one_stream(state->mem_ctx, &state->num_streams, &state->streams, dirent, st.st_ex_size, SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &st)); if (!ok) { state->status = NT_STATUS_NO_MEMORY; return false; } return true; } 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 streaminfo_state state = { .streams = *pstreams, .num_streams = *pnum_streams, .mem_ctx = mem_ctx, .handle = handle, .status = NT_STATUS_OK, }; NTSTATUS status; status = walk_streams(handle, fsp, 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; status = SMB_VFS_NEXT_FSTREAMINFO( handle, metadata_fsp(fsp), mem_ctx, pnum_streams, pstreams); 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 int streams_depot_connect(struct vfs_handle_struct *handle, const char *service, const char *user) { struct connection_struct *conn = handle->conn; struct streams_depot_config_data *config = NULL; int ret; config = talloc_zero(conn, struct streams_depot_config_data); if (config == NULL) { goto nomem; } ret = SMB_VFS_NEXT_CONNECT(handle, service, user); if (ret < 0) { TALLOC_FREE(config); return ret; } config->check_valid = lp_parm_bool(SNUM(conn), "streams_depot", "check_valid", true); config->directory = stream_rootdir(handle, config); if (config->directory == NULL) { goto nomem; } config->delete_lost = lp_parm_bool(SNUM(conn), "streams_depot", "delete_lost", false); SMB_VFS_HANDLE_SET_DATA(handle, config, NULL, struct streams_depot_config_data, return -1); return 0; nomem: TALLOC_FREE(config); errno = ENOMEM; return -1; } static struct vfs_fn_pointers vfs_streams_depot_fns = { .connect_fn = streams_depot_connect, .fs_capabilities_fn = streams_depot_fs_capabilities, .openat_fn = streams_depot_openat, .stat_fn = streams_depot_stat, .lstat_fn = streams_depot_lstat, .fstatat_fn = streams_depot_fstatat, .unlinkat_fn = streams_depot_unlinkat, .rename_stream_fn = streams_depot_rename_stream, .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); }