summaryrefslogtreecommitdiffstats
path: root/source3/modules/vfs_ceph_snapshots.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3/modules/vfs_ceph_snapshots.c
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3/modules/vfs_ceph_snapshots.c')
-rw-r--r--source3/modules/vfs_ceph_snapshots.c1478
1 files changed, 1478 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <libgen.h>
+#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,
+ &timestamp_src, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname_dst,
+ &timestamp_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,
+ &timestamp_old,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp_old,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ new_smb_fname,
+ &timestamp_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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp, 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,
+ &timestamp,
+ 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,
+ &timestamp, 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,
+ &timestamp, 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);
+}