On Fri, Mar 29, 2019 at 06:45:31PM +0100, David Disseldorp via samba-technical wrote: > > The attached patchset adds a new ceph_snapshots Samba VFS module which > handles snapshot enumeration and timewarp/@GMT token mapping. > > Feedback appreciated. Mostly looks good - a few comments inline below. Hope you don't think I'm being too picky, push back if so. I really want this functionality, just want to make sure I can maintain it going forward. Cheers, Jeremy. > From dc42eb120b930f9fc8a8f232422812b61eb6ca9c Mon Sep 17 00:00:00 2001 > From: David Disseldorp <ddiss@xxxxxxxxx> > Date: Tue, 26 Mar 2019 16:35:18 +0100 > Subject: [PATCH 3/4] vfs: add ceph_snapshots module > > vfs_ceph_snapshots is a module for accessing CephFS snapshots as > Previous Versions. The module is separate from vfs_ceph, so that it can > also be used atop a CephFS kernel backed share with vfs_default. > > Signed-off-by: David Disseldorp <ddiss@xxxxxxxxx> > --- > source3/modules/vfs_ceph_snapshots.c | 1746 ++++++++++++++++++++++++++++++++++ > source3/modules/wscript_build | 8 + > source3/wscript | 5 + > 3 files changed, 1759 insertions(+) > create mode 100644 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 00000000000..6f4e3f12640 > --- /dev/null > +++ b/source3/modules/vfs_ceph_snapshots.c > @@ -0,0 +1,1746 @@ > +/* > + * 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" > + > +#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(struct vfs_handle_struct *handle, > + struct smb_filename *smb_fname, > + 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_GETXATTR(handle, smb_fname, 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 = strtoull_err(snap_btime, &endptr, 10, &err); > + if (err != 0) { > + return -err; > + } > + if ((endptr == snap_btime) || (*endptr != '\0')) { > + DBG_ERR("couldn't process snap.tv_sec in %s\n", snap_btime); > + return -EINVAL; > + } > + > + /* second component is nsecs */ > + s++; > + snap_timespec.tv_nsec = strtoul_err(s, &endptr, 10, &err); > + if (err != 0) { > + return -err; > + } > + if ((endptr == s) || (*endptr != '\0')) { > + DBG_ERR("couldn't process snap.tv_nsec in %s\n", s); > + return -EINVAL; > + } > + > + /* > + * >> 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. > + * > + * @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, > + const char *parent_snapsdir, > + const char *subdir, > + char *this_label) There is a typedef char SHADOW_COPY_LABEL[25] which described 'this_label'. Can you use that instead of char *, otherwise the memset(this_label, 0, sizeof(SHADOW_COPY_LABEL)); below looks weird and potentially overflow'y (I know it isn't, but using SHADOW_COPY_LABEL makes that clear). > +{ > + struct smb_filename *smb_fname; > + time_t snap_secs; > + struct tm gmt_snap_time; > + struct tm *tm_ret; > + size_t str_sz; > + char snap_path[PATH_MAX + 1]; > + struct timespec snap_timespec; > + int ret; > + > + /* safety first... */ > + memset(this_label, 0, sizeof(SHADOW_COPY_LABEL)); > + > + /* > + * 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); > + if (smb_fname == NULL) { > + return -ENOMEM; > + } > + > + ret = ceph_snap_get_btime(handle, smb_fname, &snap_secs); > + if (ret < 0) { > + return ret; > + } > + > + 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) > +{ > + NTSTATUS status; > + int ret; > + DIR *d = NULL; > + struct dirent *e = NULL; > + int slots; slots should be unsigned, or a size_t. > + > + status = smbd_check_access_rights(handle->conn, > + snaps_dname, > + false, > + SEC_DIR_LIST); > + if (!NT_STATUS_IS_OK(status)) { > + DEBUG(0,("user does not have list permission " > + "on snapdir %s\n", > + snaps_dname->base_name)); > + ret = -map_errno_from_nt_status(status); > + goto err_out; > + } > + > + 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. > + */ > + d = SMB_VFS_NEXT_OPENDIR(handle, snaps_dname, NULL, 0); > + if (d == NULL) { > + ret = -errno; > + goto err_out; > + } > + > + slots = 0; > + sc_data->num_volumes = 0; > + sc_data->labels = NULL; > + > + for (e = SMB_VFS_NEXT_READDIR(handle, d, NULL); > + e != NULL; > + e = SMB_VFS_NEXT_READDIR(handle, d, NULL)) { > + char *this_label; > + > + if (ISDOT(e->d_name) || ISDOTDOT(e->d_name)) { > + continue; > + } > + sc_data->num_volumes++; > + if (!labels) { > + continue; > + } > + if (sc_data->num_volumes > slots) { > + slots += 10; Can you do an overflow check here ? Yes I know it's not possible. I'm still paranoid :-). > + DBG_DEBUG("%d slots for enum_snaps response\n", slots); > + sc_data->labels = talloc_realloc(sc_data, > + sc_data->labels, > + SHADOW_COPY_LABEL, > + slots); > + if (sc_data->labels == NULL) { > + ret = -ENOMEM; > + goto err_closedir; > + } Should you zero-fill the 10 new slots here ? Not strictly needed I think. Your call here. > + } > + DBG_DEBUG("filling shadow copy label for %s/%s\n", > + snaps_dname->base_name, e->d_name); > + ret = ceph_snap_fill_label(handle, snaps_dname, > + snaps_dname->base_name, e->d_name, > + sc_data->labels[sc_data->num_volumes - 1]); > + if (ret < 0) { > + goto err_closedir; > + } > + } > + > + ret = SMB_VFS_NEXT_CLOSEDIR(handle, d); > + if (ret != 0) { > + ret = -errno; > + goto err_out; > + } > + > + DBG_DEBUG("%s shadow copy enumeration found %d labels \n", > + snaps_dname->base_name, sc_data->num_volumes); > + > + return 0; > + > +err_closedir: > + SMB_VFS_NEXT_CLOSEDIR(handle, d); > +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; > + int len; len should be size_t I think. Below, do the assert the p >= path instead of len >= 0. > + 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; > + } > + > + len = p - path; > + SMB_ASSERT(len >= 0); > + > + ret = snprintf(_parent_buf, buflen, "%.*s", 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] == '/')) { > + 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 */ > + if (strncmp(connectpath, _parent_buf, clen) > + || (_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->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; > + } > + > + 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, > + 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 bool ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle, > + const char *name, > + time_t *_timestamp, > + char *_stripped_buf, > + size_t buflen) > +{ > + struct tm tm; > + time_t timestamp; > + const char *p; > + char *q; > + char *stripped; > + size_t rest_len, dst_len; > + ptrdiff_t len_before_gmt; > + > + p = strstr_m(name, "@GMT-"); > + if (p == NULL) { > + goto no_snapshot; > + } > + if ((p > name) && (p[-1] != '/')) { > + goto no_snapshot; > + } > + len_before_gmt = p - name; > + q = strptime(p, GMT_FORMAT, &tm); > + if (q == NULL) { > + goto no_snapshot; > + } > + tm.tm_isdst = -1; > + timestamp = timegm(&tm); > + if (timestamp == (time_t)-1) { > + goto no_snapshot; > + } > + if (q[0] == '\0') { > + /* > + * The name consists of only the GMT token or the GMT > + * token is at the end of the path. > + */ > + if (_stripped_buf != NULL) { > + if (len_before_gmt >= buflen) { > + return -EINVAL; > + } > + if (len_before_gmt > 0) { > + /* > + * There is a slash before the @GMT-. Remove it > + * and copy the result. > + */ > + len_before_gmt -= 1; > + strlcpy(_stripped_buf, name, len_before_gmt); > + } else { > + _stripped_buf[0] = '\0'; /* token only */ > + } > + DBG_DEBUG("GMT token in %s stripped to %s\n", > + name, _stripped_buf); > + } > + *_timestamp = timestamp; > + return 0; > + } > + if (q[0] != '/') { > + /* > + * It is not a complete path component, i.e. the path > + * component continues after the gmt-token. > + */ > + goto no_snapshot; > + } > + q += 1; > + > + rest_len = strlen(q); > + dst_len = len_before_gmt + rest_len; > + SMB_ASSERT(dst_len >= rest_len); > + > + if (_stripped_buf != NULL) { > + if (dst_len >= buflen) { > + return -EINVAL; > + } > + if (p > name) { > + memcpy(_stripped_buf, name, len_before_gmt); > + } > + if (rest_len > 0) { > + memcpy(_stripped_buf + len_before_gmt, q, rest_len); > + } > + _stripped_buf[dst_len] = '\0'; > + } > + *_timestamp = timestamp; > + DBG_DEBUG("GMT token in %s stripped to %s\n", name, _stripped_buf); > + 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; > + DIR *d = NULL; > + struct dirent *e = 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. > + */ > + 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); /* 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; > + } > + > + status = smbd_check_access_rights(handle->conn, > + snaps_dname, > + false, > + SEC_DIR_LIST); > + if (!NT_STATUS_IS_OK(status)) { > + DEBUG(0,("user does not have list permission " > + "on snapdir %s\n", > + snaps_dname->base_name)); > + ret = -map_errno_from_nt_status(status); > + goto err_out; > + } > + > + DBG_DEBUG("enumerating shadow copy dir at %s\n", > + snaps_dname->base_name); > + > + d = SMB_VFS_NEXT_OPENDIR(handle, snaps_dname, NULL, 0); > + if (d == NULL) { > + ret = -errno; > + goto err_out; > + } > + > + for (e = SMB_VFS_NEXT_READDIR(handle, d, NULL); > + e != NULL; > + e = SMB_VFS_NEXT_READDIR(handle, d, NULL)) { > + struct smb_filename *smb_fname; > + time_t snap_secs; > + > + if (ISDOT(e->d_name) || ISDOTDOT(e->d_name)) { > + continue; > + } > + > + ret = snprintf(_converted_buf, buflen, "%s/%s", > + snaps_dname->base_name, e->d_name); > + if (ret >= buflen) { > + ret = -EINVAL; > + goto err_closedir; > + } > + > + smb_fname = synthetic_smb_fname(tmp_ctx, _converted_buf, > + NULL, NULL, 0); > + if (smb_fname == NULL) { > + ret = -ENOMEM; > + goto err_closedir; > + } > + > + ret = ceph_snap_get_btime(handle, smb_fname, &snap_secs); > + if (ret < 0) { > + goto err_closedir; > + } > + > + /* > + * check gmt_snap_time matches @timestamp > + */ > + if (timestamp == snap_secs) { > + break; > + } > + DBG_DEBUG("[connectpath %s] %s@%d no match for snap %s@%d\n", > + handle->conn->connectpath, name, timestamp, > + e->d_name, snap_secs); > + } > + > + if (e == NULL) { > + DBG_INFO("[connectpath %s] failed to find %s @ time %d\n", > + handle->conn->connectpath, name, timestamp); > + ret = -ENOENT; > + goto err_closedir; > + } > + > + /* found, _converted_buf already contains path of interest */ > + DBG_DEBUG("[connectpath %s] converted %s @ time %d to %s\n", > + handle->conn->connectpath, name, timestamp, _converted_buf); > + > + ret = SMB_VFS_NEXT_CLOSEDIR(handle, d); > + if (ret != 0) { > + ret = -errno; > + goto err_out; > + } > + talloc_free(tmp_ctx); > + return 0; > + > +err_closedir: > + SMB_VFS_NEXT_CLOSEDIR(handle, d); > +err_out: > + 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. > + */ > + if (strlen(_converted_buf) + 1 + strlen(trimmed) >= buflen) { > + return -EINVAL; > + } > + strncat(_converted_buf, "/", buflen); > + strncat(_converted_buf, trimmed, buflen); > + > + return 0; > +} > + > +static DIR *ceph_snap_gmt_opendir(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + const char *mask, > + uint32_t attr) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + int ret; > + DIR *dir; > + int saved_errno; > + struct smb_filename *conv_smb_fname = NULL; > + char conv[PATH_MAX + 1]; > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + csmb_fname->base_name, > + ×tamp, > + stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return NULL; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_OPENDIR(handle, csmb_fname, mask, attr); > + } > + ret = ceph_snap_gmt_convert_dir(handle, stripped, > + timestamp, conv, sizeof(conv)); > + if (ret < 0) { > + errno = -ret; > + return NULL; > + } > + conv_smb_fname = synthetic_smb_fname(talloc_tos(), > + conv, > + NULL, > + NULL, > + csmb_fname->flags); > + if (conv_smb_fname == NULL) { > + errno = ENOMEM; > + return NULL; > + } > + > + dir = SMB_VFS_NEXT_OPENDIR(handle, conv_smb_fname, mask, attr); > + saved_errno = errno; > + TALLOC_FREE(conv_smb_fname); > + errno = saved_errno; > + return dir; > +} > + > +static int ceph_snap_gmt_rename(vfs_handle_struct *handle, > + const struct smb_filename *smb_fname_src, > + 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->base_name, > + ×tamp_src, NULL, 0); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname_dst->base_name, > + ×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_RENAME(handle, smb_fname_src, smb_fname_dst); > +} > + > +/* block links from writeable shares to snapshots for now, like other modules */ > +static int ceph_snap_gmt_symlink(vfs_handle_struct *handle, > + const char *link_contents, > + 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->base_name, > + ×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_SYMLINK(handle, link_contents, new_smb_fname); > +} > + > +static int ceph_snap_gmt_link(vfs_handle_struct *handle, > + const struct smb_filename *old_smb_fname, > + 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, > + old_smb_fname->base_name, > + ×tamp_old, > + NULL, 0); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + ret = ceph_snap_gmt_strip_snapshot(handle, > + new_smb_fname->base_name, > + ×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_LINK(handle, old_smb_fname, new_smb_fname); > +} > + > +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->base_name, > + ×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->base_name, > + ×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_open(vfs_handle_struct *handle, > + struct smb_filename *smb_fname, files_struct *fsp, > + int flags, mode_t mode) > +{ > + 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->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_OPEN(handle, smb_fname, fsp, flags, mode); > + } > + > + 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_OPEN(handle, smb_fname, fsp, flags, mode); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +/* > + * XXX play discard_const() games with const smb_filename structs, to avoid > + * allocation of a new struct just for this. > + */ Can you do an allocation instead ? I really hate discard_const_p() tricks, eventually they bite :-). Isn't it possible a real const char is getting passed inside these smb_filenames ? I don't think these are performance critical code paths. Can't you change ceph_snap_gmt_strip_snapshot() to return a talloc'ed struct smb_filename from a passed in const one ? Pass in a talloc context of talloc_tos() and remember to free in exit paths. That's what it's for (sorry I know you hate it, but it's appropriate here IMHO of course). > +static int ceph_snap_gmt_unlink(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_UNLINK(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_UNLINK(handle, smb_fname); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_chmod(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + mode_t mode) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_CHMOD(handle, smb_fname, mode); > + } > + > + 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_CHMOD(handle, smb_fname, mode); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_chown(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + uid_t uid, > + gid_t gid) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_CHOWN(handle, smb_fname, uid, gid); > + } > + > + 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_CHOWN(handle, smb_fname, uid, gid); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +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]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_CHDIR(handle, smb_fname); > + } > + > + ret = ceph_snap_gmt_convert_dir(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_CHDIR(handle, smb_fname); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_ntimes(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + struct smb_file_time *ft) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_NTIMES(handle, smb_fname, ft); > + } > + > + 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_NTIMES(handle, smb_fname, ft); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_readlink(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + char *buf, > + size_t bufsiz) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_READLINK(handle, smb_fname, buf, bufsiz); > + } > + 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_READLINK(handle, smb_fname, buf, bufsiz); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_mknod(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + mode_t mode, > + SMB_DEV_T dev) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_MKNOD(handle, smb_fname, mode, dev); > + } > + 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_MKNOD(handle, smb_fname, mode, dev); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +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]; > + char *tmp; > + struct smb_filename *result_fname; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return NULL; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); > + } > + ret = ceph_snap_gmt_convert(handle, stripped, > + timestamp, conv, sizeof(conv)); > + if (ret < 0) { > + errno = -ret; > + return NULL; > + } > + tmp = smb_fname->base_name; > + smb_fname->base_name = conv; > + > + result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname); > + smb_fname->base_name = tmp; > + return result_fname; > +} > + > +/* > + * XXX this should have gone through open() conversion, so why do we need > + * a handler here? posix_fget_nt_acl() falls back to posix_get_nt_acl() for > + * dirs (or fd == -1). > + */ > +static NTSTATUS ceph_snap_gmt_fget_nt_acl(vfs_handle_struct *handle, > + struct files_struct *fsp, > + uint32_t security_info, > + TALLOC_CTX *mem_ctx, > + struct security_descriptor **ppdesc) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + struct smb_filename *smb_fname; > + int ret; > + NTSTATUS status; > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + fsp->fsp_name->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + return map_nt_error_from_unix(-ret); > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, > + mem_ctx, > + ppdesc); > + } > + ret = ceph_snap_gmt_convert(handle, stripped, > + timestamp, conv, sizeof(conv)); > + if (ret < 0) { > + return map_nt_error_from_unix(-ret); > + } > + > + smb_fname = synthetic_smb_fname(mem_ctx, > + conv, > + NULL, > + NULL, > + fsp->fsp_name->flags); > + if (smb_fname == NULL) { > + return NT_STATUS_NO_MEMORY; > + } > + > + status = SMB_VFS_NEXT_GET_NT_ACL(handle, smb_fname, security_info, > + mem_ctx, ppdesc); > + TALLOC_FREE(smb_fname); > + return status; > +} > + > +static NTSTATUS ceph_snap_gmt_get_nt_acl(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + uint32_t security_info, > + TALLOC_CTX *mem_ctx, > + struct security_descriptor **ppdesc) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + NTSTATUS status; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + return map_nt_error_from_unix(-ret); > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_GET_NT_ACL(handle, smb_fname, security_info, > + mem_ctx, ppdesc); > + } > + ret = ceph_snap_gmt_convert(handle, stripped, > + timestamp, conv, sizeof(conv)); > + if (ret < 0) { > + return map_nt_error_from_unix(-ret); > + } > + tmp = smb_fname->base_name; > + smb_fname->base_name = conv; > + > + status = SMB_VFS_NEXT_GET_NT_ACL(handle, smb_fname, security_info, > + mem_ctx, ppdesc); > + smb_fname->base_name = tmp; > + return status; > +} > + > +static int ceph_snap_gmt_mkdir(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + mode_t mode) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_MKDIR(handle, smb_fname, mode); > + } > + ret = ceph_snap_gmt_convert_dir(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_MKDIR(handle, smb_fname, mode); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_rmdir(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_RMDIR(handle, smb_fname); > + } > + ret = ceph_snap_gmt_convert_dir(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_RMDIR(handle, smb_fname); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_chflags(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + unsigned int flags) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_CHFLAGS(handle, smb_fname, flags); > + } > + 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_CHFLAGS(handle, smb_fname, flags); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static ssize_t ceph_snap_gmt_getxattr(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + const char *aname, > + void *value, > + size_t size) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_GETXATTR(handle, smb_fname, aname, value, > + size); > + } > + 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_GETXATTR(handle, smb_fname, aname, value, size); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static ssize_t ceph_snap_gmt_listxattr(struct vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + char *list, size_t size) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_LISTXATTR(handle, smb_fname, list, size); > + } > + 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_LISTXATTR(handle, smb_fname, list, size); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_removexattr(vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + const char *aname) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_REMOVEXATTR(handle, smb_fname, aname); > + } > + 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_REMOVEXATTR(handle, smb_fname, aname); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_setxattr(struct vfs_handle_struct *handle, > + const struct smb_filename *csmb_fname, > + const char *aname, const void *value, > + size_t size, int flags) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_SETXATTR(handle, smb_fname, > + aname, value, size, flags); > + } > + 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_SETXATTR(handle, smb_fname, > + aname, value, size, flags); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static int ceph_snap_gmt_get_real_filename(struct vfs_handle_struct *handle, > + const char *path, > + const char *name, > + TALLOC_CTX *mem_ctx, > + char **found_name) > +{ > + time_t timestamp = 0; > + char stripped[PATH_MAX + 1]; > + char conv[PATH_MAX + 1]; > + int ret; > + > + ret = ceph_snap_gmt_strip_snapshot(handle, path, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_GET_REAL_FILENAME(handle, path, name, > + mem_ctx, found_name); > + } > + ret = ceph_snap_gmt_convert_dir(handle, stripped, > + timestamp, conv, sizeof(conv)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + ret = SMB_VFS_NEXT_GET_REAL_FILENAME(handle, conv, name, > + mem_ctx, found_name); > + return ret; > +} > + > +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]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, > + bsize, dfree, dsize); > + } > + 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_DISK_FREE(handle, smb_fname, > + bsize, dfree, dsize); > + smb_fname->base_name = tmp; > + 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]; > + char *tmp; > + int ret; > + struct smb_filename *smb_fname = discard_const_p(struct smb_filename, > + csmb_fname); > + > + ret = ceph_snap_gmt_strip_snapshot(handle, > + smb_fname->base_name, > + ×tamp, stripped, sizeof(stripped)); > + if (ret < 0) { > + errno = -ret; > + return -1; > + } > + if (timestamp == 0) { > + return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, dq); > + } > + 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_GET_QUOTA(handle, smb_fname, qtype, id, dq); > + smb_fname->base_name = tmp; > + return ret; > +} > + > +static struct vfs_fn_pointers ceph_snap_fns = { > + .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data, > + .opendir_fn = ceph_snap_gmt_opendir, > + .disk_free_fn = ceph_snap_gmt_disk_free, > + .get_quota_fn = ceph_snap_gmt_get_quota, > + .rename_fn = ceph_snap_gmt_rename, > + .link_fn = ceph_snap_gmt_link, > + .symlink_fn = ceph_snap_gmt_symlink, > + .stat_fn = ceph_snap_gmt_stat, > + .lstat_fn = ceph_snap_gmt_lstat, > + .open_fn = ceph_snap_gmt_open, > + .unlink_fn = ceph_snap_gmt_unlink, > + .chmod_fn = ceph_snap_gmt_chmod, > + .chown_fn = ceph_snap_gmt_chown, > + .chdir_fn = ceph_snap_gmt_chdir, > + .ntimes_fn = ceph_snap_gmt_ntimes, > + .readlink_fn = ceph_snap_gmt_readlink, > + .mknod_fn = ceph_snap_gmt_mknod, > + .realpath_fn = ceph_snap_gmt_realpath, > + .get_nt_acl_fn = ceph_snap_gmt_get_nt_acl, > + .fget_nt_acl_fn = ceph_snap_gmt_fget_nt_acl, > + .get_nt_acl_fn = ceph_snap_gmt_get_nt_acl, > + .mkdir_fn = ceph_snap_gmt_mkdir, > + .rmdir_fn = ceph_snap_gmt_rmdir, > + .getxattr_fn = ceph_snap_gmt_getxattr, > + .getxattrat_send_fn = vfs_not_implemented_getxattrat_send, > + .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv, > + .listxattr_fn = ceph_snap_gmt_listxattr, > + .removexattr_fn = ceph_snap_gmt_removexattr, > + .setxattr_fn = ceph_snap_gmt_setxattr, > + .chflags_fn = ceph_snap_gmt_chflags, > + .get_real_filename_fn = ceph_snap_gmt_get_real_filename, > +}; > + > +static_decl_vfs; > +NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx) > +{ > + return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, > + "ceph_snapshots", &ceph_snap_fns); > +} > diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build > index 8d0e0ee57c1..35010bb0e3b 100644 > --- a/source3/modules/wscript_build > +++ b/source3/modules/wscript_build > @@ -522,6 +522,14 @@ bld.SAMBA3_MODULE('vfs_ceph', > cflags=bld.CONFIG_GET('CFLAGS_CEPHFS'), > includes=bld.CONFIG_GET('CPPPATH_CEPHFS')) > > +bld.SAMBA3_MODULE('vfs_ceph_snapshots', > + subsystem='vfs', > + source='vfs_ceph_snapshots.c', > + deps='samba-util', > + init_function='', > + internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_ceph_snapshots'), > + enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph_snapshots')) > + > bld.SAMBA3_MODULE('vfs_glusterfs', > subsystem='vfs', > source='vfs_glusterfs.c', > diff --git a/source3/wscript b/source3/wscript > index c93b6056f29..6830c6b743a 100644 > --- a/source3/wscript > +++ b/source3/wscript > @@ -1730,6 +1730,11 @@ main() { > > if conf.CONFIG_SET("HAVE_CEPH"): > default_shared_modules.extend(TO_LIST('vfs_ceph')) > + # Unlike vfs_ceph, vfs_ceph_snapshots doesn't depend on libcephfs, so > + # can be enabled atop a kernel CephFS share (with vfs_default) in > + # addition to vfs_ceph. Still, only enable vfs_ceph_snapshots builds > + # if we're building with libcephfs for now. > + default_shared_modules.extend(TO_LIST('vfs_ceph_snapshots')) > > if conf.CONFIG_SET('HAVE_GLUSTERFS'): > default_shared_modules.extend(TO_LIST('vfs_glusterfs')) > -- > 2.16.4 > > > From 03a8d6a1a80394fa6e19cbbc556da7151a6d399e Mon Sep 17 00:00:00 2001 > From: David Disseldorp <ddiss@xxxxxxxxx> > Date: Wed, 27 Mar 2019 15:57:45 +0100 > Subject: [PATCH 4/4] docs: add vfs_ceph_snapshots manpage > > Signed-off-by: David Disseldorp <ddiss@xxxxxxxxx> > --- > docs-xml/manpages/vfs_ceph_snapshots.8.xml | 130 +++++++++++++++++++++++++++++ > docs-xml/wscript_build | 1 + > 2 files changed, 131 insertions(+) > create mode 100644 docs-xml/manpages/vfs_ceph_snapshots.8.xml > > diff --git a/docs-xml/manpages/vfs_ceph_snapshots.8.xml b/docs-xml/manpages/vfs_ceph_snapshots.8.xml > new file mode 100644 > index 00000000000..7fa2806fd95 > --- /dev/null > +++ b/docs-xml/manpages/vfs_ceph_snapshots.8.xml > @@ -0,0 +1,130 @@ > +<?xml version="1.0" encoding="iso-8859-1"?> > +<!DOCTYPE refentry PUBLIC "-//Samba-Team//DTD DocBook V4.2-Based Variant V1.0//EN" "http://www.samba.org/samba/DTD/samba-doc"> > +<refentry id="vfs_ceph_snapshots.8"> > + > +<refmeta> > + <refentrytitle>vfs_ceph_snapshots</refentrytitle> > + <manvolnum>8</manvolnum> > + <refmiscinfo class="source">Samba</refmiscinfo> > + <refmiscinfo class="manual">System Administration tools</refmiscinfo> > + <refmiscinfo class="version">&doc.version;</refmiscinfo> > +</refmeta> > + > + > +<refnamediv> > + <refname>vfs_ceph_snapshots</refname> > + <refpurpose> > + Expose CephFS snapshots as shadow-copies > + </refpurpose> > +</refnamediv> > + > +<refsynopsisdiv> > + <cmdsynopsis> > + <command>vfs objects = ceph_snapshots</command> > + </cmdsynopsis> > +</refsynopsisdiv> > + > +<refsect1> > + <title>DESCRIPTION</title> > + > + <para>This VFS module is part of the > + <citerefentry><refentrytitle>samba</refentrytitle> > + <manvolnum>8</manvolnum></citerefentry> suite.</para> > + > + <para> > + The <command>vfs_ceph_snapshots</command> VFS module exposes > + CephFS snapshots for use by Samba. When enabled, SMB clients > + such as Windows Explorer's Previous Versions dialog, can > + enumerate snaphots and access them via "timewarp" tokens. > + </para> > + > + <para> > + This module can be combined with <command>vfs_ceph</command>, > + but <command>vfs_ceph_snapshots</command> must be listed first > + in the <command>vfs objects</command> parameter list. > + </para> > + > + <para> > + CephFS support for ceph.snap.btime virtual extended attributes > + is required for this module to work properly. This support was > + added via https://tracker.ceph.com/issues/38838. > + </para> > +</refsect1> > + > +<refsect1> > + <title>CONFIGURATION</title> > + > + <para> > + When used atop <command>vfs_ceph</command>, > + <command>path</command> refers to an absolute path within the > + Ceph filesystem and should not be mounted locally: > + </para> > + > + <programlisting> > + <smbconfsection name="[share]"/> > + <smbconfoption name="vfs objects">ceph_snapshots ceph</smbconfoption> > + <smbconfoption name="path">/non-mounted/cephfs/path</smbconfoption> > + <smbconfoption name="kernel share modes">no</smbconfoption> > + </programlisting> > + > + <para> > + <command>vfs_ceph_snapshots</command> can also be used atop a > + kernel CephFS mounted share path, without > + <command>vfs_ceph</command>. In this case Samba's default VFS > + backend <command>vfs_default</command> is used: > + </para> > + > + <programlisting> > + <smbconfsection name="[share]"/> > + <smbconfoption name="vfs objects">ceph_snapshots</smbconfoption> > + <smbconfoption name="path">/mnt/cephfs/</smbconfoption> > + </programlisting> > +</refsect1> > + > +<refsect1> > + <title>OPTIONS</title> > + > + <variablelist> > + <varlistentry> > + <term>ceph:snapdir = subdirectory</term> > + <listitem> > + <para> > + Allows for the configuration of the special CephFS > + snapshot subdirectory name. This parameter should only > + be changed from the ".snap" default if the ceph.conf > + <command>client snapdir</command> or > + <command>snapdirname</command> mount option settings > + are changed from their matching ".snap" defaults. > + </para> > + <para> > + Default: > + <smbconfoption name="ceph:snapdir">.snap</smbconfoption> > + </para> > + <para> > + Example: > + <smbconfoption name="ceph:snapdir">.snapshots</smbconfoption> > + </para> > + </listitem> > + </varlistentry> > + </variablelist> > +</refsect1> > + > +<refsect1> > + <title>VERSION</title> > + > + <para> > + This man page is part of version &doc.version; of the Samba suite. > + </para> > +</refsect1> > + > +<refsect1> > + <title>AUTHOR</title> > + > + <para>The original Samba software and related utilities > + were created by Andrew Tridgell. Samba is now developed > + by the Samba Team as an Open Source project similar > + to the way the Linux kernel is developed.</para> > + > +</refsect1> > + > +</refentry> > diff --git a/docs-xml/wscript_build b/docs-xml/wscript_build > index 796b685c709..575fb702b46 100644 > --- a/docs-xml/wscript_build > +++ b/docs-xml/wscript_build > @@ -72,6 +72,7 @@ vfs_module_manpages = ['vfs_acl_tdb', > 'vfs_cap', > 'vfs_catia', > 'vfs_ceph', > + 'vfs_ceph_snapshots', > 'vfs_commit', > 'vfs_crossrename', > 'vfs_default_quota', > -- > 2.16.4 >