From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/modules/vfs_ceph_snapshots.c | 1478 ++++++++++++++++++++++++++++++++++ 1 file changed, 1478 insertions(+) create mode 100644 source3/modules/vfs_ceph_snapshots.c (limited to 'source3/modules/vfs_ceph_snapshots.c') diff --git a/source3/modules/vfs_ceph_snapshots.c b/source3/modules/vfs_ceph_snapshots.c new file mode 100644 index 0000000..98b8f5f --- /dev/null +++ b/source3/modules/vfs_ceph_snapshots.c @@ -0,0 +1,1478 @@ +/* + * Module for accessing CephFS snapshots as Previous Versions. This module is + * separate to vfs_ceph, so that it can also be used atop a CephFS kernel backed + * share with vfs_default. + * + * Copyright (C) David Disseldorp 2019 + * + * 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 +#include +#include "includes.h" +#include "include/ntioctl.h" +#include "include/smb.h" +#include "system/filesys.h" +#include "smbd/smbd.h" +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/smb_strtox.h" +#include "source3/smbd/dir.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_VFS + +/* + * CephFS has a magic snapshots subdirectory in all parts of the directory tree. + * This module automatically makes all snapshots in this subdir visible to SMB + * clients (if permitted by corresponding access control). + */ +#define CEPH_SNAP_SUBDIR_DEFAULT ".snap" +/* + * The ceph.snap.btime (virtual) extended attribute carries the snapshot + * creation time in $secs.$nsecs format. It was added as part of + * https://tracker.ceph.com/issues/38838. Running Samba atop old Ceph versions + * which don't provide this xattr will not be able to enumerate or access + * snapshots using this module. As an alternative, vfs_shadow_copy2 could be + * used instead, alongside special shadow:format snapshot directory names. + */ +#define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime" + +static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle, + struct files_struct *fsp, + time_t *_snap_secs) +{ + int ret; + char snap_btime[33]; + char *s = NULL; + char *endptr = NULL; + struct timespec snap_timespec; + int err; + + ret = SMB_VFS_NEXT_FGETXATTR(handle, + fsp, + CEPH_SNAP_BTIME_XATTR, + snap_btime, + sizeof(snap_btime)); + if (ret < 0) { + DBG_ERR("failed to get %s xattr: %s\n", + CEPH_SNAP_BTIME_XATTR, strerror(errno)); + return -errno; + } + + if (ret == 0 || ret >= sizeof(snap_btime) - 1) { + return -EINVAL; + } + + /* ensure zero termination */ + snap_btime[ret] = '\0'; + + /* format is sec.nsec */ + s = strchr(snap_btime, '.'); + if (s == NULL) { + DBG_ERR("invalid %s xattr value: %s\n", + CEPH_SNAP_BTIME_XATTR, snap_btime); + return -EINVAL; + } + + /* First component is seconds, extract it */ + *s = '\0'; + snap_timespec.tv_sec = smb_strtoull(snap_btime, + &endptr, + 10, + &err, + SMB_STR_FULL_STR_CONV); + if (err != 0) { + return -err; + } + + /* second component is nsecs */ + s++; + snap_timespec.tv_nsec = smb_strtoul(s, + &endptr, + 10, + &err, + SMB_STR_FULL_STR_CONV); + if (err != 0) { + return -err; + } + + /* + * >> 30 is a rough divide by ~10**9. No need to be exact, as @GMT + * tokens only offer 1-second resolution (while twrp is nsec). + */ + *_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30); + + return 0; +} + +/* + * XXX Ceph snapshots can be created with sub-second granularity, which means + * that multiple snapshots may be mapped to the same @GMT- label. + * + * @this_label is a pre-zeroed buffer to be filled with a @GMT label + * @return 0 if label successfully filled or -errno on error. + */ +static int ceph_snap_fill_label(struct vfs_handle_struct *handle, + TALLOC_CTX *tmp_ctx, + struct files_struct *dirfsp, + const char *subdir, + SHADOW_COPY_LABEL this_label) +{ + const char *parent_snapsdir = dirfsp->fsp_name->base_name; + struct smb_filename *smb_fname; + struct smb_filename *atname = NULL; + time_t snap_secs; + struct tm gmt_snap_time; + struct tm *tm_ret; + size_t str_sz; + char snap_path[PATH_MAX + 1]; + int ret; + NTSTATUS status; + + /* + * CephFS snapshot creation times are available via a special + * xattr - snapshot b/m/ctimes all match the snap source. + */ + ret = snprintf(snap_path, sizeof(snap_path), "%s/%s", + parent_snapsdir, subdir); + if (ret >= sizeof(snap_path)) { + return -EINVAL; + } + + smb_fname = synthetic_smb_fname(tmp_ctx, + snap_path, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + return -ENOMEM; + } + + ret = vfs_stat(handle->conn, smb_fname); + if (ret < 0) { + ret = -errno; + TALLOC_FREE(smb_fname); + return ret; + } + + atname = synthetic_smb_fname(tmp_ctx, + subdir, + NULL, + &smb_fname->st, + 0, + 0); + if (atname == NULL) { + TALLOC_FREE(smb_fname); + return -ENOMEM; + } + + status = openat_pathref_fsp(dirfsp, atname); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + return -map_errno_from_nt_status(status); + } + + ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs); + if (ret < 0) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + return ret; + } + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + + tm_ret = gmtime_r(&snap_secs, &gmt_snap_time); + if (tm_ret == NULL) { + return -EINVAL; + } + str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL), + "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time); + if (str_sz == 0) { + DBG_ERR("failed to convert tm to @GMT token\n"); + return -EINVAL; + } + + DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n", + snap_path, this_label); + + return 0; +} + +static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle, + struct smb_filename *snaps_dname, + bool labels, + struct shadow_copy_data *sc_data) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct smb_Dir *dir_hnd = NULL; + struct files_struct *dirfsp = NULL; + const char *dname = NULL; + char *talloced = NULL; + NTSTATUS status; + int ret; + uint32_t slots; + + DBG_DEBUG("enumerating shadow copy dir at %s\n", + snaps_dname->base_name); + + /* + * CephFS stat(dir).size *normally* returns the number of child entries + * for a given dir, but it unfortunately that's not the case for the one + * place we need it (dir=.snap), so we need to dynamically determine it + * via readdir. + */ + + status = OpenDir(frame, + handle->conn, + snaps_dname, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + /* Check we have SEC_DIR_LIST access on this fsp. */ + dirfsp = dir_hnd_fetch_fsp(dir_hnd); + status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp, + dirfsp, + false, + SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("user does not have list permission " + "on snapdir %s\n", + fsp_str_dbg(dirfsp)); + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + slots = 0; + sc_data->num_volumes = 0; + sc_data->labels = NULL; + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + if (ISDOT(dname) || ISDOTDOT(dname)) { + TALLOC_FREE(talloced); + continue; + } + sc_data->num_volumes++; + if (!labels) { + TALLOC_FREE(talloced); + continue; + } + if (sc_data->num_volumes > slots) { + uint32_t new_slot_count = slots + 10; + SMB_ASSERT(new_slot_count > slots); + sc_data->labels = talloc_realloc(sc_data, + sc_data->labels, + SHADOW_COPY_LABEL, + new_slot_count); + if (sc_data->labels == NULL) { + TALLOC_FREE(talloced); + ret = -ENOMEM; + goto err_closedir; + } + memset(sc_data->labels[slots], 0, + sizeof(SHADOW_COPY_LABEL) * 10); + + DBG_DEBUG("%d->%d slots for enum_snaps response\n", + slots, new_slot_count); + slots = new_slot_count; + } + DBG_DEBUG("filling shadow copy label for %s/%s\n", + snaps_dname->base_name, dname); + ret = ceph_snap_fill_label(handle, + snaps_dname, + dirfsp, + dname, + sc_data->labels[sc_data->num_volumes - 1]); + if (ret < 0) { + TALLOC_FREE(talloced); + goto err_closedir; + } + TALLOC_FREE(talloced); + } + + DBG_DEBUG("%s shadow copy enumeration found %d labels \n", + snaps_dname->base_name, sc_data->num_volumes); + + TALLOC_FREE(frame); + return 0; + +err_closedir: + TALLOC_FREE(frame); +err_out: + TALLOC_FREE(sc_data->labels); + return ret; +} + +/* + * Prior reading: The Meaning of Path Names + * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module + * + * translate paths so that we can use the parent dir for .snap access: + * myfile -> parent= trimmed=myfile + * /a -> parent=/ trimmed=a + * dir/sub/file -> parent=dir/sub trimmed=file + * /dir/sub -> parent=/dir/ trimmed=sub + */ +static int ceph_snap_get_parent_path(const char *connectpath, + const char *path, + char *_parent_buf, + size_t buflen, + const char **_trimmed) +{ + const char *p; + size_t len; + int ret; + + if (!strcmp(path, "/")) { + DBG_ERR("can't go past root for %s .snap dir\n", path); + return -EINVAL; + } + + p = strrchr_m(path, '/'); /* Find final '/', if any */ + if (p == NULL) { + DBG_DEBUG("parent .snap dir for %s is cwd\n", path); + ret = strlcpy(_parent_buf, "", buflen); + if (ret >= buflen) { + return -EINVAL; + } + if (_trimmed != NULL) { + *_trimmed = path; + } + return 0; + } + + SMB_ASSERT(p >= path); + len = p - path; + + ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path); + if (ret >= buflen) { + return -EINVAL; + } + + /* for absolute paths, check that we're not going outside the share */ + if ((len > 0) && (_parent_buf[0] == '/')) { + bool connectpath_match = false; + size_t clen = strlen(connectpath); + DBG_DEBUG("checking absolute path %s lies within share at %s\n", + _parent_buf, connectpath); + /* need to check for separator, to avoid /x/abcd vs /x/ab */ + connectpath_match = (strncmp(connectpath, + _parent_buf, + clen) == 0); + if (!connectpath_match + || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) { + DBG_ERR("%s parent path is outside of share at %s\n", + _parent_buf, connectpath); + return -EINVAL; + } + } + + if (_trimmed != NULL) { + /* + * point to path component which was trimmed from _parent_buf + * excluding path separator. + */ + *_trimmed = p + 1; + } + + DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n", + path, _parent_buf, p + 1); + + return 0; +} + +static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle, + struct files_struct *fsp, + struct shadow_copy_data *sc_data, + bool labels) +{ + int ret; + TALLOC_CTX *tmp_ctx; + const char *parent_dir = NULL; + char tmp[PATH_MAX + 1]; + char snaps_path[PATH_MAX + 1]; + struct smb_filename *snaps_dname = NULL; + const char *snapdir = lp_parm_const_string(SNUM(handle->conn), + "ceph", "snapdir", + CEPH_SNAP_SUBDIR_DEFAULT); + + DBG_DEBUG("getting shadow copy data for %s\n", + fsp->fsp_name->base_name); + + tmp_ctx = talloc_new(fsp); + if (tmp_ctx == NULL) { + ret = -ENOMEM; + goto err_out; + } + + if (sc_data == NULL) { + ret = -EINVAL; + goto err_out; + } + + if (fsp->fsp_flags.is_directory) { + parent_dir = fsp->fsp_name->base_name; + } else { + ret = ceph_snap_get_parent_path(handle->conn->connectpath, + fsp->fsp_name->base_name, + tmp, + sizeof(tmp), + NULL); /* trimmed */ + if (ret < 0) { + goto err_out; + } + parent_dir = tmp; + } + + if (strlen(parent_dir) == 0) { + ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path)); + } else { + ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s", + parent_dir, snapdir); + } + if (ret >= sizeof(snaps_path)) { + ret = -EINVAL; + goto err_out; + } + + snaps_dname = synthetic_smb_fname(tmp_ctx, + snaps_path, + NULL, + NULL, + 0, + fsp->fsp_name->flags); + if (snaps_dname == NULL) { + ret = -ENOMEM; + goto err_out; + } + + ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data); + if (ret < 0) { + goto err_out; + } + + talloc_free(tmp_ctx); + return 0; + +err_out: + talloc_free(tmp_ctx); + errno = -ret; + return -1; +} + +static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle, + const struct smb_filename *smb_fname, + time_t *_timestamp, + char *_stripped_buf, + size_t buflen) +{ + size_t len; + + if (smb_fname->twrp == 0) { + goto no_snapshot; + } + + if (_stripped_buf != NULL) { + len = strlcpy(_stripped_buf, smb_fname->base_name, buflen); + if (len >= buflen) { + return -ENAMETOOLONG; + } + } + + *_timestamp = nt_time_to_unix(smb_fname->twrp); + return 0; +no_snapshot: + *_timestamp = 0; + return 0; +} + +static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle, + const char *name, + time_t timestamp, + char *_converted_buf, + size_t buflen) +{ + int ret; + NTSTATUS status; + struct smb_Dir *dir_hnd = NULL; + struct files_struct *dirfsp = NULL; + const char *dname = NULL; + char *talloced = NULL; + struct smb_filename *snaps_dname = NULL; + const char *snapdir = lp_parm_const_string(SNUM(handle->conn), + "ceph", "snapdir", + CEPH_SNAP_SUBDIR_DEFAULT); + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + + if (tmp_ctx == NULL) { + ret = -ENOMEM; + goto err_out; + } + + /* + * Temporally use the caller's return buffer for this. + */ + if (strlen(name) == 0) { + ret = strlcpy(_converted_buf, snapdir, buflen); + } else { + ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir); + } + if (ret >= buflen) { + ret = -EINVAL; + goto err_out; + } + + snaps_dname = synthetic_smb_fname(tmp_ctx, + _converted_buf, + NULL, + NULL, + 0, + 0); /* XXX check? */ + if (snaps_dname == NULL) { + ret = -ENOMEM; + goto err_out; + } + + /* stat first to trigger error fallback in ceph_snap_gmt_convert() */ + ret = SMB_VFS_NEXT_STAT(handle, snaps_dname); + if (ret < 0) { + ret = -errno; + goto err_out; + } + + DBG_DEBUG("enumerating shadow copy dir at %s\n", + snaps_dname->base_name); + + status = OpenDir(tmp_ctx, + handle->conn, + snaps_dname, + NULL, + 0, + &dir_hnd); + if (!NT_STATUS_IS_OK(status)) { + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + /* Check we have SEC_DIR_LIST access on this fsp. */ + dirfsp = dir_hnd_fetch_fsp(dir_hnd); + status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp, + dirfsp, + false, + SEC_DIR_LIST); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("user does not have list permission " + "on snapdir %s\n", + fsp_str_dbg(dirfsp)); + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) { + struct smb_filename *smb_fname = NULL; + struct smb_filename *atname = NULL; + time_t snap_secs = 0; + + if (ISDOT(dname) || ISDOTDOT(dname)) { + TALLOC_FREE(talloced); + continue; + } + + ret = snprintf(_converted_buf, buflen, "%s/%s", + snaps_dname->base_name, dname); + if (ret >= buflen) { + ret = -EINVAL; + goto err_out; + } + + smb_fname = synthetic_smb_fname(tmp_ctx, + _converted_buf, + NULL, + NULL, + 0, + 0); + if (smb_fname == NULL) { + ret = -ENOMEM; + goto err_out; + } + + ret = vfs_stat(handle->conn, smb_fname); + if (ret < 0) { + ret = -errno; + TALLOC_FREE(smb_fname); + goto err_out; + } + + atname = synthetic_smb_fname(tmp_ctx, + dname, + NULL, + &smb_fname->st, + 0, + 0); + if (atname == NULL) { + TALLOC_FREE(smb_fname); + ret = -ENOMEM; + goto err_out; + } + + status = openat_pathref_fsp(dirfsp, atname); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + ret = -map_errno_from_nt_status(status); + goto err_out; + } + + ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs); + if (ret < 0) { + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + goto err_out; + } + + TALLOC_FREE(smb_fname); + TALLOC_FREE(atname); + + /* + * check gmt_snap_time matches @timestamp + */ + if (timestamp == snap_secs) { + break; + } + DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n", + handle->conn->connectpath, name, (long long)timestamp, + dname, (long long)snap_secs); + TALLOC_FREE(talloced); + } + + if (dname == NULL) { + DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n", + handle->conn->connectpath, name, (long long)timestamp); + ret = -ENOENT; + goto err_out; + } + + /* found, _converted_buf already contains path of interest */ + DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n", + handle->conn->connectpath, name, (long long)timestamp, + _converted_buf); + + TALLOC_FREE(talloced); + talloc_free(tmp_ctx); + return 0; + +err_out: + TALLOC_FREE(talloced); + talloc_free(tmp_ctx); + return ret; +} + +static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle, + const char *name, + time_t timestamp, + char *_converted_buf, + size_t buflen) +{ + int ret; + char parent[PATH_MAX + 1]; + const char *trimmed = NULL; + /* + * CephFS Snapshots for a given dir are nested under the ./.snap subdir + * *or* under ../.snap/dir (and subsequent parent dirs). + * Child dirs inherit snapshots created in parent dirs if the child + * exists at the time of snapshot creation. + * + * At this point we don't know whether @name refers to a file or dir, so + * first assume it's a dir (with a corresponding .snaps subdir) + */ + ret = ceph_snap_gmt_convert_dir(handle, + name, + timestamp, + _converted_buf, + buflen); + if (ret >= 0) { + /* all done: .snap subdir exists - @name is a dir */ + DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name); + return ret; + } + + /* @name/.snap access failed, attempt snapshot access via parent */ + DBG_DEBUG("%s/.snap access failed, attempting parent access\n", + name); + + ret = ceph_snap_get_parent_path(handle->conn->connectpath, + name, + parent, + sizeof(parent), + &trimmed); + if (ret < 0) { + return ret; + } + + ret = ceph_snap_gmt_convert_dir(handle, + parent, + timestamp, + _converted_buf, + buflen); + if (ret < 0) { + return ret; + } + + /* + * found snapshot via parent. Append the child path component + * that was trimmed... +1 for path separator + 1 for null termination. + */ + if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) { + return -EINVAL; + } + strlcat(_converted_buf, "/", buflen); + strlcat(_converted_buf, trimmed, buflen); + + return 0; +} + +static int ceph_snap_gmt_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) +{ + int ret; + time_t timestamp_src, timestamp_dst; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname_src, + ×tamp_src, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname_dst, + ×tamp_dst, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp_src != 0) { + errno = EXDEV; + return -1; + } + if (timestamp_dst != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_RENAMEAT(handle, + srcfsp, + smb_fname_src, + dstfsp, + smb_fname_dst); +} + +/* block links from writeable shares to snapshots for now, like other modules */ +static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle, + const struct smb_filename *link_contents, + struct files_struct *dirfsp, + const struct smb_filename *new_smb_fname) +{ + int ret; + time_t timestamp_old = 0; + time_t timestamp_new = 0; + + ret = ceph_snap_gmt_strip_snapshot(handle, + link_contents, + ×tamp_old, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + ret = ceph_snap_gmt_strip_snapshot(handle, + new_smb_fname, + ×tamp_new, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_SYMLINKAT(handle, + link_contents, + dirfsp, + new_smb_fname); +} + +static int ceph_snap_gmt_linkat(vfs_handle_struct *handle, + files_struct *srcfsp, + const struct smb_filename *old_smb_fname, + files_struct *dstfsp, + const struct smb_filename *new_smb_fname, + int flags) +{ + int ret; + time_t timestamp_old = 0; + time_t timestamp_new = 0; + + ret = ceph_snap_gmt_strip_snapshot(handle, + old_smb_fname, + ×tamp_old, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + ret = ceph_snap_gmt_strip_snapshot(handle, + new_smb_fname, + ×tamp_new, + NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if ((timestamp_old != 0) || (timestamp_new != 0)) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_LINKAT(handle, + srcfsp, + old_smb_fname, + dstfsp, + new_smb_fname, + flags); +} + +static int ceph_snap_gmt_stat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + char *tmp; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_STAT(handle, smb_fname); + } + + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + tmp = smb_fname->base_name; + smb_fname->base_name = conv; + + ret = SMB_VFS_NEXT_STAT(handle, smb_fname); + smb_fname->base_name = tmp; + return ret; +} + +static int ceph_snap_gmt_lstat(vfs_handle_struct *handle, + struct smb_filename *smb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + char *tmp; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_LSTAT(handle, smb_fname); + } + + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + tmp = smb_fname->base_name; + smb_fname->base_name = conv; + + ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname); + smb_fname->base_name = tmp; + return ret; +} + +static int ceph_snap_gmt_openat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *smb_fname_in, + files_struct *fsp, + const struct vfs_open_how *how) +{ + time_t timestamp = 0; + struct smb_filename *smb_fname = NULL; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + int saved_errno = 0; + + ret = ceph_snap_gmt_strip_snapshot(handle, + smb_fname_in, + ×tamp, + stripped, + sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname_in, + fsp, + how); + } + + ret = ceph_snap_gmt_convert(handle, + stripped, + timestamp, + conv, + sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in); + if (smb_fname == NULL) { + return -1; + } + smb_fname->base_name = conv; + + ret = SMB_VFS_NEXT_OPENAT(handle, + dirfsp, + smb_fname, + fsp, + how); + if (ret == -1) { + saved_errno = errno; + } + TALLOC_FREE(smb_fname); + if (saved_errno != 0) { + errno = saved_errno; + } + return ret; +} + +static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *csmb_fname, + int flags) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_UNLINKAT(handle, + dirfsp, + csmb_fname, + flags); +} + +static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle, + struct files_struct *fsp, + mode_t mode) +{ + const struct smb_filename *csmb_fname = NULL; + time_t timestamp = 0; + int ret; + + csmb_fname = fsp->fsp_name; + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode); +} + +static int ceph_snap_gmt_chdir(vfs_handle_struct *handle, + const struct smb_filename *csmb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHDIR(handle, csmb_fname); + } + + ret = ceph_snap_gmt_convert_dir(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return -1; + } + new_fname->base_name = conv; + + ret = SMB_VFS_NEXT_CHDIR(handle, new_fname); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return ret; +} + +static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle, + files_struct *fsp, + struct smb_file_time *ft) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + fsp->fsp_name, + ×tamp, + NULL, + 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft); +} + +static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle, + const struct files_struct *dirfsp, + const struct smb_filename *csmb_fname, + char *buf, + size_t bufsiz) +{ + time_t timestamp = 0; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *full_fname = NULL; + int saved_errno; + + /* + * Now this function only looks at csmb_fname->twrp + * we don't need to copy out the path. Just use + * csmb_fname->base_name directly. + */ + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_READLINKAT(handle, + dirfsp, + csmb_fname, + buf, + bufsiz); + } + + full_fname = full_path_from_dirfsp_atname(talloc_tos(), + dirfsp, + csmb_fname); + if (full_fname == NULL) { + return -1; + } + + /* Find the snapshot path from the full pathname. */ + ret = ceph_snap_gmt_convert(handle, + full_fname->base_name, + timestamp, + conv, + sizeof(conv)); + if (ret < 0) { + TALLOC_FREE(full_fname); + errno = -ret; + return -1; + } + full_fname->base_name = conv; + + ret = SMB_VFS_NEXT_READLINKAT(handle, + handle->conn->cwd_fsp, + full_fname, + buf, + bufsiz); + saved_errno = errno; + TALLOC_FREE(full_fname); + errno = saved_errno; + return ret; +} + +static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle, + files_struct *dirfsp, + const struct smb_filename *csmb_fname, + mode_t mode, + SMB_DEV_T dev) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKNODAT(handle, + dirfsp, + csmb_fname, + mode, + dev); +} + +static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle, + TALLOC_CTX *ctx, + const struct smb_filename *csmb_fname) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + struct smb_filename *result_fname; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return NULL; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname); + } + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return NULL; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return NULL; + } + new_fname->base_name = conv; + + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return result_fname; +} + +static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle, + struct files_struct *dirfsp, + const struct smb_filename *csmb_fname, + mode_t mode) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_MKDIRAT(handle, + dirfsp, + csmb_fname, + mode); +} + +static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle, + struct files_struct *fsp, + unsigned int flags) +{ + time_t timestamp = 0; + int ret; + + ret = ceph_snap_gmt_strip_snapshot(handle, + fsp->fsp_name, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags); +} + +static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle, + struct files_struct *fsp, + const char *aname, const void *value, + size_t size, int flags) +{ + const struct smb_filename *csmb_fname = NULL; + time_t timestamp = 0; + int ret; + + csmb_fname = fsp->fsp_name; + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, NULL, 0); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp != 0) { + errno = EROFS; + return -1; + } + return SMB_VFS_NEXT_FSETXATTR(handle, fsp, + aname, value, size, flags); +} + +static NTSTATUS ceph_snap_gmt_get_real_filename_at( + struct vfs_handle_struct *handle, + struct files_struct *dirfsp, + const char *name, + TALLOC_CTX *mem_ctx, + char **found_name) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + struct smb_filename *conv_fname = NULL; + int ret; + NTSTATUS status; + + ret = ceph_snap_gmt_strip_snapshot( + handle, + dirfsp->fsp_name, + ×tamp, + stripped, + sizeof(stripped)); + if (ret < 0) { + return map_nt_error_from_unix(-ret); + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, dirfsp, name, mem_ctx, found_name); + } + ret = ceph_snap_gmt_convert_dir(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + return map_nt_error_from_unix(-ret); + } + + status = synthetic_pathref( + talloc_tos(), + dirfsp->conn->cwd_fsp, + conv, + NULL, + NULL, + 0, + 0, + &conv_fname); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT( + handle, conv_fname->fsp, name, mem_ctx, found_name); + TALLOC_FREE(conv_fname); + return status; +} + +static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle, + const struct smb_filename *csmb_fname, + uint64_t *bsize, + uint64_t *dfree, + uint64_t *dsize) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname, + bsize, dfree, dsize); + } + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return -1; + } + new_fname->base_name = conv; + + ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname, + bsize, dfree, dsize); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return ret; +} + +static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle, + const struct smb_filename *csmb_fname, + enum SMB_QUOTA_TYPE qtype, + unid_t id, + SMB_DISK_QUOTA *dq) +{ + time_t timestamp = 0; + char stripped[PATH_MAX + 1]; + char conv[PATH_MAX + 1]; + int ret; + struct smb_filename *new_fname; + int saved_errno; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq); + } + ret = ceph_snap_gmt_convert(handle, stripped, + timestamp, conv, sizeof(conv)); + if (ret < 0) { + errno = -ret; + return -1; + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + errno = ENOMEM; + return -1; + } + new_fname->base_name = conv; + + ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq); + saved_errno = errno; + TALLOC_FREE(new_fname); + errno = saved_errno; + return ret; +} + +static struct vfs_fn_pointers ceph_snap_fns = { + .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data, + .disk_free_fn = ceph_snap_gmt_disk_free, + .get_quota_fn = ceph_snap_gmt_get_quota, + .renameat_fn = ceph_snap_gmt_renameat, + .linkat_fn = ceph_snap_gmt_linkat, + .symlinkat_fn = ceph_snap_gmt_symlinkat, + .stat_fn = ceph_snap_gmt_stat, + .lstat_fn = ceph_snap_gmt_lstat, + .openat_fn = ceph_snap_gmt_openat, + .unlinkat_fn = ceph_snap_gmt_unlinkat, + .fchmod_fn = ceph_snap_gmt_fchmod, + .chdir_fn = ceph_snap_gmt_chdir, + .fntimes_fn = ceph_snap_gmt_fntimes, + .readlinkat_fn = ceph_snap_gmt_readlinkat, + .mknodat_fn = ceph_snap_gmt_mknodat, + .realpath_fn = ceph_snap_gmt_realpath, + .mkdirat_fn = ceph_snap_gmt_mkdirat, + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, + .fsetxattr_fn = ceph_snap_gmt_fsetxattr, + .fchflags_fn = ceph_snap_gmt_fchflags, + .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at, +}; + +static_decl_vfs; +NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx) +{ + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, + "ceph_snapshots", &ceph_snap_fns); +} -- cgit v1.2.3