On Wed, 8 May 2019 15:47:40 -0700, Jeremy Allison via samba-technical wrote: > 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. Thanks for the review, Jeremy. I've attached a V2 patchset with the changes below squashed in... > > +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). Done. FWIW I've dropped the memset now that it's done alongside allocation. ... > > +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. Changed to uint32_t. ... > > + 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 :-). Added. > > > + 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. Makes sense. I prefer zero-fill alongside allocation - added. ... > > +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. Fixed. ... > > +/* > > + * 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). I've changed the const smb_filename handlers to use cp_smb_filename() to avoid the discard_const ugliness. The copy only occurs if a @GMT token is present, so it indeed shouldn't be performance critical. Cheers, David
From 826a248c25115eaf54adf5bb35e8eb8d93f0f555 Mon Sep 17 00:00:00 2001 From: David Disseldorp <ddiss@xxxxxxxxx> Date: Wed, 27 Mar 2019 13:10:04 +0100 Subject: [PATCH 1/3] vfs_ceph: drop fdopendir handler libcephfs doesn't currently offer an fdopendir equivalent, so the existing implementation peeks at fsp->fsp_name->base_name, which can break if vfs_ceph is used under a separate path-munging VFS module. Return ENOSYS instead and rely on existing OpenDir_fsp() fallback. Signed-off-by: David Disseldorp <ddiss@xxxxxxxxx> --- source3/modules/vfs_ceph.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/source3/modules/vfs_ceph.c b/source3/modules/vfs_ceph.c index 57de8bc891a..f62fef05614 100644 --- a/source3/modules/vfs_ceph.c +++ b/source3/modules/vfs_ceph.c @@ -328,18 +328,9 @@ static DIR *cephwrap_fdopendir(struct vfs_handle_struct *handle, const char *mask, uint32_t attributes) { - int ret = 0; - struct ceph_dir_result *result; - DBG_DEBUG("[CEPH] fdopendir(%p, %p)\n", handle, fsp); - - ret = ceph_opendir(handle->data, fsp->fsp_name->base_name, &result); - if (ret < 0) { - result = NULL; - errno = -ret; /* We return result which is NULL in this case */ - } - - DBG_DEBUG("[CEPH] fdopendir(...) = %d\n", ret); - return (DIR *) result; + /* OpenDir_fsp() falls back to regular open */ + errno = ENOSYS; + return NULL; } static struct dirent *cephwrap_readdir(struct vfs_handle_struct *handle, -- 2.16.4 From c12a7d0a0037b7936e3488b8c68550872752d021 Mon Sep 17 00:00:00 2001 From: David Disseldorp <ddiss@xxxxxxxxx> Date: Tue, 26 Mar 2019 16:35:18 +0100 Subject: [PATCH 2/3] 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 | 1799 ++++++++++++++++++++++++++++++++++ source3/modules/wscript_build | 8 + source3/wscript | 5 + 3 files changed, 1812 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..b5b4211e44c --- /dev/null +++ b/source3/modules/vfs_ceph_snapshots.c @@ -0,0 +1,1799 @@ +/* + * 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. + * + * @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, + const char *parent_snapsdir, + const char *subdir, + SHADOW_COPY_LABEL this_label) +{ + 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; + + /* + * 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; + uint32_t slots; + + 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) { + 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) { + 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, 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; + 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", 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; +} + +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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_UNLINK(handle, csmb_fname); + } + + 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_UNLINK(handle, new_fname); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHMOD(handle, csmb_fname, mode); + } + + 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_CHMOD(handle, new_fname, mode); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHOWN(handle, csmb_fname, uid, gid); + } + + 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_CHOWN(handle, new_fname, uid, gid); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×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); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_NTIMES(handle, csmb_fname, ft); + } + + 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_NTIMES(handle, new_fname, ft); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_READLINK(handle, csmb_fname, buf, bufsiz); + } + 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_READLINK(handle, new_fname, buf, bufsiz); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_MKNOD(handle, csmb_fname, mode, dev); + } + 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_MKNOD(handle, new_fname, mode, dev); + TALLOC_FREE(new_fname); + 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 *new_fname; + + 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_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); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_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, csmb_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); + } + new_fname = cp_smb_filename(talloc_tos(), csmb_fname); + if (new_fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + new_fname->base_name = conv; + + status = SMB_VFS_NEXT_GET_NT_ACL(handle, new_fname, security_info, + mem_ctx, ppdesc); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_MKDIR(handle, csmb_fname, mode); + } + 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_MKDIR(handle, new_fname, mode); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_RMDIR(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_RMDIR(handle, new_fname); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_CHFLAGS(handle, csmb_fname, flags); + } + 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_CHFLAGS(handle, new_fname, flags); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_GETXATTR(handle, csmb_fname, aname, value, + size); + } + 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_GETXATTR(handle, new_fname, aname, value, size); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_LISTXATTR(handle, csmb_fname, list, size); + } + 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_LISTXATTR(handle, new_fname, list, size); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_REMOVEXATTR(handle, csmb_fname, aname); + } + 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_REMOVEXATTR(handle, new_fname, aname); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×tamp, stripped, sizeof(stripped)); + if (ret < 0) { + errno = -ret; + return -1; + } + if (timestamp == 0) { + return SMB_VFS_NEXT_SETXATTR(handle, csmb_fname, + aname, value, size, flags); + } + 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_SETXATTR(handle, new_fname, + aname, value, size, flags); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×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); + TALLOC_FREE(new_fname); + 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 *new_fname; + + ret = ceph_snap_gmt_strip_snapshot(handle, + csmb_fname->base_name, + ×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); + TALLOC_FREE(new_fname); + 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 cd0673a94c7..ff72a173a4b 100644 --- a/source3/wscript +++ b/source3/wscript @@ -1766,6 +1766,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 13cb5bbc3be020f1f0d08ea290a84f4213e31b00 Mon Sep 17 00:00:00 2001 From: David Disseldorp <ddiss@xxxxxxxxx> Date: Wed, 27 Mar 2019 15:57:45 +0100 Subject: [PATCH 3/3] 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