2 * Module for accessing CephFS snapshots as Previous Versions. This module is
3 * separate to vfs_ceph, so that it can also be used atop a CephFS kernel backed
4 * share with vfs_default.
6 * Copyright (C) David Disseldorp 2019
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
25 #include "include/ntioctl.h"
26 #include "include/smb.h"
27 #include "system/filesys.h"
28 #include "smbd/smbd.h"
29 #include "lib/util/tevent_ntstatus.h"
30 #include "lib/util/smb_strtox.h"
33 #define DBGC_CLASS DBGC_VFS
36 * CephFS has a magic snapshots subdirectory in all parts of the directory tree.
37 * This module automatically makes all snapshots in this subdir visible to SMB
38 * clients (if permitted by corresponding access control).
40 #define CEPH_SNAP_SUBDIR_DEFAULT ".snap"
42 * The ceph.snap.btime (virtual) extended attribute carries the snapshot
43 * creation time in $secs.$nsecs format. It was added as part of
44 * https://tracker.ceph.com/issues/38838. Running Samba atop old Ceph versions
45 * which don't provide this xattr will not be able to enumerate or access
46 * snapshots using this module. As an alternative, vfs_shadow_copy2 could be
47 * used instead, alongside special shadow:format snapshot directory names.
49 #define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime"
51 static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle,
52 struct files_struct *fsp,
59 struct timespec snap_timespec;
62 ret = SMB_VFS_NEXT_FGETXATTR(handle,
64 CEPH_SNAP_BTIME_XATTR,
68 DBG_ERR("failed to get %s xattr: %s\n",
69 CEPH_SNAP_BTIME_XATTR, strerror(errno));
73 if (ret == 0 || ret >= sizeof(snap_btime) - 1) {
77 /* ensure zero termination */
78 snap_btime[ret] = '\0';
80 /* format is sec.nsec */
81 s = strchr(snap_btime, '.');
83 DBG_ERR("invalid %s xattr value: %s\n",
84 CEPH_SNAP_BTIME_XATTR, snap_btime);
88 /* First component is seconds, extract it */
90 snap_timespec.tv_sec = smb_strtoull(snap_btime,
94 SMB_STR_FULL_STR_CONV);
99 /* second component is nsecs */
101 snap_timespec.tv_nsec = smb_strtoul(s,
105 SMB_STR_FULL_STR_CONV);
111 * >> 30 is a rough divide by ~10**9. No need to be exact, as @GMT
112 * tokens only offer 1-second resolution (while twrp is nsec).
114 *_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30);
120 * XXX Ceph snapshots can be created with sub-second granularity, which means
121 * that multiple snapshots may be mapped to the same @GMT- label.
123 * @this_label is a pre-zeroed buffer to be filled with a @GMT label
124 * @return 0 if label successfully filled or -errno on error.
126 static int ceph_snap_fill_label(struct vfs_handle_struct *handle,
128 struct files_struct *dirfsp,
130 SHADOW_COPY_LABEL this_label)
132 const char *parent_snapsdir = dirfsp->fsp_name->base_name;
133 struct smb_filename *smb_fname;
134 struct smb_filename *atname = NULL;
136 struct tm gmt_snap_time;
139 char snap_path[PATH_MAX + 1];
144 * CephFS snapshot creation times are available via a special
145 * xattr - snapshot b/m/ctimes all match the snap source.
147 ret = snprintf(snap_path, sizeof(snap_path), "%s/%s",
148 parent_snapsdir, subdir);
149 if (ret >= sizeof(snap_path)) {
153 smb_fname = synthetic_smb_fname(tmp_ctx,
159 if (smb_fname == NULL) {
163 ret = vfs_stat(handle->conn, smb_fname);
166 TALLOC_FREE(smb_fname);
170 atname = synthetic_smb_fname(tmp_ctx,
176 if (atname == NULL) {
177 TALLOC_FREE(smb_fname);
181 status = openat_pathref_fsp(dirfsp, atname);
182 if (!NT_STATUS_IS_OK(status)) {
183 TALLOC_FREE(smb_fname);
185 return -map_errno_from_nt_status(status);
188 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
190 TALLOC_FREE(smb_fname);
194 TALLOC_FREE(smb_fname);
197 tm_ret = gmtime_r(&snap_secs, &gmt_snap_time);
198 if (tm_ret == NULL) {
201 str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL),
202 "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time);
204 DBG_ERR("failed to convert tm to @GMT token\n");
208 DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n",
209 snap_path, this_label);
214 static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle,
215 struct smb_filename *snaps_dname,
217 struct shadow_copy_data *sc_data)
219 TALLOC_CTX *frame = talloc_stackframe();
220 struct smb_Dir *dir_hnd = NULL;
221 struct files_struct *dirfsp = NULL;
222 const char *dname = NULL;
223 char *talloced = NULL;
228 DBG_DEBUG("enumerating shadow copy dir at %s\n",
229 snaps_dname->base_name);
232 * CephFS stat(dir).size *normally* returns the number of child entries
233 * for a given dir, but it unfortunately that's not the case for the one
234 * place we need it (dir=.snap), so we need to dynamically determine it
238 status = OpenDir(frame,
244 if (!NT_STATUS_IS_OK(status)) {
245 ret = -map_errno_from_nt_status(status);
249 /* Check we have SEC_DIR_LIST access on this fsp. */
250 dirfsp = dir_hnd_fetch_fsp(dir_hnd);
251 status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
255 if (!NT_STATUS_IS_OK(status)) {
256 DBG_ERR("user does not have list permission "
258 fsp_str_dbg(dirfsp));
259 ret = -map_errno_from_nt_status(status);
264 sc_data->num_volumes = 0;
265 sc_data->labels = NULL;
267 while ((dname = ReadDirName(dir_hnd, NULL, &talloced))
270 if (ISDOT(dname) || ISDOTDOT(dname)) {
271 TALLOC_FREE(talloced);
274 sc_data->num_volumes++;
276 TALLOC_FREE(talloced);
279 if (sc_data->num_volumes > slots) {
280 uint32_t new_slot_count = slots + 10;
281 SMB_ASSERT(new_slot_count > slots);
282 sc_data->labels = talloc_realloc(sc_data,
286 if (sc_data->labels == NULL) {
287 TALLOC_FREE(talloced);
291 memset(sc_data->labels[slots], 0,
292 sizeof(SHADOW_COPY_LABEL) * 10);
294 DBG_DEBUG("%d->%d slots for enum_snaps response\n",
295 slots, new_slot_count);
296 slots = new_slot_count;
298 DBG_DEBUG("filling shadow copy label for %s/%s\n",
299 snaps_dname->base_name, dname);
300 ret = ceph_snap_fill_label(handle,
304 sc_data->labels[sc_data->num_volumes - 1]);
306 TALLOC_FREE(talloced);
309 TALLOC_FREE(talloced);
312 DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
313 snaps_dname->base_name, sc_data->num_volumes);
321 TALLOC_FREE(sc_data->labels);
326 * Prior reading: The Meaning of Path Names
327 * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
329 * translate paths so that we can use the parent dir for .snap access:
330 * myfile -> parent= trimmed=myfile
331 * /a -> parent=/ trimmed=a
332 * dir/sub/file -> parent=dir/sub trimmed=file
333 * /dir/sub -> parent=/dir/ trimmed=sub
335 static int ceph_snap_get_parent_path(const char *connectpath,
339 const char **_trimmed)
345 if (!strcmp(path, "/")) {
346 DBG_ERR("can't go past root for %s .snap dir\n", path);
350 p = strrchr_m(path, '/'); /* Find final '/', if any */
352 DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
353 ret = strlcpy(_parent_buf, "", buflen);
357 if (_trimmed != NULL) {
363 SMB_ASSERT(p >= path);
366 ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
371 /* for absolute paths, check that we're not going outside the share */
372 if ((len > 0) && (_parent_buf[0] == '/')) {
373 bool connectpath_match = false;
374 size_t clen = strlen(connectpath);
375 DBG_DEBUG("checking absolute path %s lies within share at %s\n",
376 _parent_buf, connectpath);
377 /* need to check for separator, to avoid /x/abcd vs /x/ab */
378 connectpath_match = (strncmp(connectpath,
381 if (!connectpath_match
382 || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
383 DBG_ERR("%s parent path is outside of share at %s\n",
384 _parent_buf, connectpath);
389 if (_trimmed != NULL) {
391 * point to path component which was trimmed from _parent_buf
392 * excluding path separator.
397 DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
398 path, _parent_buf, p + 1);
403 static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
404 struct files_struct *fsp,
405 struct shadow_copy_data *sc_data,
410 const char *parent_dir = NULL;
411 char tmp[PATH_MAX + 1];
412 char snaps_path[PATH_MAX + 1];
413 struct smb_filename *snaps_dname = NULL;
414 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
416 CEPH_SNAP_SUBDIR_DEFAULT);
418 DBG_DEBUG("getting shadow copy data for %s\n",
419 fsp->fsp_name->base_name);
421 tmp_ctx = talloc_new(fsp);
422 if (tmp_ctx == NULL) {
427 if (sc_data == NULL) {
432 if (fsp->fsp_flags.is_directory) {
433 parent_dir = fsp->fsp_name->base_name;
435 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
436 fsp->fsp_name->base_name,
446 if (strlen(parent_dir) == 0) {
447 ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
449 ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
450 parent_dir, snapdir);
452 if (ret >= sizeof(snaps_path)) {
457 snaps_dname = synthetic_smb_fname(tmp_ctx,
462 fsp->fsp_name->flags);
463 if (snaps_dname == NULL) {
468 ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
473 talloc_free(tmp_ctx);
477 talloc_free(tmp_ctx);
482 static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
483 const struct smb_filename *smb_fname,
490 if (smb_fname->twrp == 0) {
494 if (_stripped_buf != NULL) {
495 len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
497 return -ENAMETOOLONG;
501 *_timestamp = nt_time_to_unix(smb_fname->twrp);
508 static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
511 char *_converted_buf,
516 struct smb_Dir *dir_hnd = NULL;
517 struct files_struct *dirfsp = NULL;
518 const char *dname = NULL;
519 char *talloced = NULL;
520 struct smb_filename *snaps_dname = NULL;
521 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
523 CEPH_SNAP_SUBDIR_DEFAULT);
524 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
526 if (tmp_ctx == NULL) {
532 * Temporally use the caller's return buffer for this.
534 if (strlen(name) == 0) {
535 ret = strlcpy(_converted_buf, snapdir, buflen);
537 ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
544 snaps_dname = synthetic_smb_fname(tmp_ctx,
550 if (snaps_dname == NULL) {
555 /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
556 ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
562 DBG_DEBUG("enumerating shadow copy dir at %s\n",
563 snaps_dname->base_name);
565 status = OpenDir(tmp_ctx,
571 if (!NT_STATUS_IS_OK(status)) {
572 ret = -map_errno_from_nt_status(status);
576 /* Check we have SEC_DIR_LIST access on this fsp. */
577 dirfsp = dir_hnd_fetch_fsp(dir_hnd);
578 status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
582 if (!NT_STATUS_IS_OK(status)) {
583 DBG_ERR("user does not have list permission "
585 fsp_str_dbg(dirfsp));
586 ret = -map_errno_from_nt_status(status);
590 while ((dname = ReadDirName(dir_hnd, NULL, &talloced))
593 struct smb_filename *smb_fname = NULL;
594 struct smb_filename *atname = NULL;
595 time_t snap_secs = 0;
597 if (ISDOT(dname) || ISDOTDOT(dname)) {
598 TALLOC_FREE(talloced);
602 ret = snprintf(_converted_buf, buflen, "%s/%s",
603 snaps_dname->base_name, dname);
609 smb_fname = synthetic_smb_fname(tmp_ctx,
615 if (smb_fname == NULL) {
620 ret = vfs_stat(handle->conn, smb_fname);
623 TALLOC_FREE(smb_fname);
627 atname = synthetic_smb_fname(tmp_ctx,
633 if (atname == NULL) {
634 TALLOC_FREE(smb_fname);
639 status = openat_pathref_fsp(dirfsp, atname);
640 if (!NT_STATUS_IS_OK(status)) {
641 TALLOC_FREE(smb_fname);
643 ret = -map_errno_from_nt_status(status);
647 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
649 TALLOC_FREE(smb_fname);
654 TALLOC_FREE(smb_fname);
658 * check gmt_snap_time matches @timestamp
660 if (timestamp == snap_secs) {
663 DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
664 handle->conn->connectpath, name, (long long)timestamp,
665 dname, (long long)snap_secs);
666 TALLOC_FREE(talloced);
670 DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
671 handle->conn->connectpath, name, (long long)timestamp);
676 /* found, _converted_buf already contains path of interest */
677 DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
678 handle->conn->connectpath, name, (long long)timestamp,
681 TALLOC_FREE(talloced);
682 talloc_free(tmp_ctx);
686 TALLOC_FREE(talloced);
687 talloc_free(tmp_ctx);
691 static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
694 char *_converted_buf,
698 char parent[PATH_MAX + 1];
699 const char *trimmed = NULL;
701 * CephFS Snapshots for a given dir are nested under the ./.snap subdir
702 * *or* under ../.snap/dir (and subsequent parent dirs).
703 * Child dirs inherit snapshots created in parent dirs if the child
704 * exists at the time of snapshot creation.
706 * At this point we don't know whether @name refers to a file or dir, so
707 * first assume it's a dir (with a corresponding .snaps subdir)
709 ret = ceph_snap_gmt_convert_dir(handle,
715 /* all done: .snap subdir exists - @name is a dir */
716 DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
720 /* @name/.snap access failed, attempt snapshot access via parent */
721 DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
724 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
733 ret = ceph_snap_gmt_convert_dir(handle,
743 * found snapshot via parent. Append the child path component
744 * that was trimmed... +1 for path separator + 1 for null termination.
746 if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
749 strlcat(_converted_buf, "/", buflen);
750 strlcat(_converted_buf, trimmed, buflen);
755 static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
756 files_struct *srcfsp,
757 const struct smb_filename *smb_fname_src,
758 files_struct *dstfsp,
759 const struct smb_filename *smb_fname_dst)
762 time_t timestamp_src, timestamp_dst;
764 ret = ceph_snap_gmt_strip_snapshot(handle,
766 ×tamp_src, NULL, 0);
771 ret = ceph_snap_gmt_strip_snapshot(handle,
773 ×tamp_dst, NULL, 0);
778 if (timestamp_src != 0) {
782 if (timestamp_dst != 0) {
786 return SMB_VFS_NEXT_RENAMEAT(handle,
793 /* block links from writeable shares to snapshots for now, like other modules */
794 static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
795 const struct smb_filename *link_contents,
796 struct files_struct *dirfsp,
797 const struct smb_filename *new_smb_fname)
800 time_t timestamp_old = 0;
801 time_t timestamp_new = 0;
803 ret = ceph_snap_gmt_strip_snapshot(handle,
811 ret = ceph_snap_gmt_strip_snapshot(handle,
819 if ((timestamp_old != 0) || (timestamp_new != 0)) {
823 return SMB_VFS_NEXT_SYMLINKAT(handle,
829 static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
830 files_struct *srcfsp,
831 const struct smb_filename *old_smb_fname,
832 files_struct *dstfsp,
833 const struct smb_filename *new_smb_fname,
837 time_t timestamp_old = 0;
838 time_t timestamp_new = 0;
840 ret = ceph_snap_gmt_strip_snapshot(handle,
848 ret = ceph_snap_gmt_strip_snapshot(handle,
856 if ((timestamp_old != 0) || (timestamp_new != 0)) {
860 return SMB_VFS_NEXT_LINKAT(handle,
868 static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
869 struct smb_filename *smb_fname)
871 time_t timestamp = 0;
872 char stripped[PATH_MAX + 1];
873 char conv[PATH_MAX + 1];
877 ret = ceph_snap_gmt_strip_snapshot(handle,
879 ×tamp, stripped, sizeof(stripped));
884 if (timestamp == 0) {
885 return SMB_VFS_NEXT_STAT(handle, smb_fname);
888 ret = ceph_snap_gmt_convert(handle, stripped,
889 timestamp, conv, sizeof(conv));
894 tmp = smb_fname->base_name;
895 smb_fname->base_name = conv;
897 ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
898 smb_fname->base_name = tmp;
902 static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
903 struct smb_filename *smb_fname)
905 time_t timestamp = 0;
906 char stripped[PATH_MAX + 1];
907 char conv[PATH_MAX + 1];
911 ret = ceph_snap_gmt_strip_snapshot(handle,
913 ×tamp, stripped, sizeof(stripped));
918 if (timestamp == 0) {
919 return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
922 ret = ceph_snap_gmt_convert(handle, stripped,
923 timestamp, conv, sizeof(conv));
928 tmp = smb_fname->base_name;
929 smb_fname->base_name = conv;
931 ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
932 smb_fname->base_name = tmp;
936 static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
937 const struct files_struct *dirfsp,
938 const struct smb_filename *smb_fname_in,
940 const struct vfs_open_how *how)
942 time_t timestamp = 0;
943 struct smb_filename *smb_fname = NULL;
944 char stripped[PATH_MAX + 1];
945 char conv[PATH_MAX + 1];
949 ret = ceph_snap_gmt_strip_snapshot(handle,
958 if (timestamp == 0) {
959 return SMB_VFS_NEXT_OPENAT(handle,
966 ret = ceph_snap_gmt_convert(handle,
975 smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
976 if (smb_fname == NULL) {
979 smb_fname->base_name = conv;
981 ret = SMB_VFS_NEXT_OPENAT(handle,
989 TALLOC_FREE(smb_fname);
990 if (saved_errno != 0) {
996 static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
997 struct files_struct *dirfsp,
998 const struct smb_filename *csmb_fname,
1001 time_t timestamp = 0;
1004 ret = ceph_snap_gmt_strip_snapshot(handle,
1006 ×tamp, NULL, 0);
1011 if (timestamp != 0) {
1015 return SMB_VFS_NEXT_UNLINKAT(handle,
1021 static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
1022 struct files_struct *fsp,
1025 const struct smb_filename *csmb_fname = NULL;
1026 time_t timestamp = 0;
1029 csmb_fname = fsp->fsp_name;
1030 ret = ceph_snap_gmt_strip_snapshot(handle,
1032 ×tamp, NULL, 0);
1037 if (timestamp != 0) {
1041 return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
1044 static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
1045 const struct smb_filename *csmb_fname)
1047 time_t timestamp = 0;
1048 char stripped[PATH_MAX + 1];
1049 char conv[PATH_MAX + 1];
1051 struct smb_filename *new_fname;
1054 ret = ceph_snap_gmt_strip_snapshot(handle,
1056 ×tamp, stripped, sizeof(stripped));
1061 if (timestamp == 0) {
1062 return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
1065 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1066 timestamp, conv, sizeof(conv));
1071 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1072 if (new_fname == NULL) {
1076 new_fname->base_name = conv;
1078 ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
1079 saved_errno = errno;
1080 TALLOC_FREE(new_fname);
1081 errno = saved_errno;
1085 static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
1087 struct smb_file_time *ft)
1089 time_t timestamp = 0;
1092 ret = ceph_snap_gmt_strip_snapshot(handle,
1101 if (timestamp != 0) {
1105 return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
1108 static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
1109 const struct files_struct *dirfsp,
1110 const struct smb_filename *csmb_fname,
1114 time_t timestamp = 0;
1115 char conv[PATH_MAX + 1];
1117 struct smb_filename *full_fname = NULL;
1121 * Now this function only looks at csmb_fname->twrp
1122 * we don't need to copy out the path. Just use
1123 * csmb_fname->base_name directly.
1125 ret = ceph_snap_gmt_strip_snapshot(handle,
1127 ×tamp, NULL, 0);
1132 if (timestamp == 0) {
1133 return SMB_VFS_NEXT_READLINKAT(handle,
1140 full_fname = full_path_from_dirfsp_atname(talloc_tos(),
1143 if (full_fname == NULL) {
1147 /* Find the snapshot path from the full pathname. */
1148 ret = ceph_snap_gmt_convert(handle,
1149 full_fname->base_name,
1154 TALLOC_FREE(full_fname);
1158 full_fname->base_name = conv;
1160 ret = SMB_VFS_NEXT_READLINKAT(handle,
1161 handle->conn->cwd_fsp,
1165 saved_errno = errno;
1166 TALLOC_FREE(full_fname);
1167 errno = saved_errno;
1171 static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
1172 files_struct *dirfsp,
1173 const struct smb_filename *csmb_fname,
1177 time_t timestamp = 0;
1180 ret = ceph_snap_gmt_strip_snapshot(handle,
1182 ×tamp, NULL, 0);
1187 if (timestamp != 0) {
1191 return SMB_VFS_NEXT_MKNODAT(handle,
1198 static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
1200 const struct smb_filename *csmb_fname)
1202 time_t timestamp = 0;
1203 char stripped[PATH_MAX + 1];
1204 char conv[PATH_MAX + 1];
1205 struct smb_filename *result_fname;
1207 struct smb_filename *new_fname;
1210 ret = ceph_snap_gmt_strip_snapshot(handle,
1212 ×tamp, stripped, sizeof(stripped));
1217 if (timestamp == 0) {
1218 return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
1220 ret = ceph_snap_gmt_convert(handle, stripped,
1221 timestamp, conv, sizeof(conv));
1226 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1227 if (new_fname == NULL) {
1231 new_fname->base_name = conv;
1233 result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
1234 saved_errno = errno;
1235 TALLOC_FREE(new_fname);
1236 errno = saved_errno;
1237 return result_fname;
1240 static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
1241 struct files_struct *dirfsp,
1242 const struct smb_filename *csmb_fname,
1245 time_t timestamp = 0;
1248 ret = ceph_snap_gmt_strip_snapshot(handle,
1250 ×tamp, NULL, 0);
1255 if (timestamp != 0) {
1259 return SMB_VFS_NEXT_MKDIRAT(handle,
1265 static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
1266 struct files_struct *fsp,
1269 time_t timestamp = 0;
1272 ret = ceph_snap_gmt_strip_snapshot(handle,
1274 ×tamp, NULL, 0);
1279 if (timestamp != 0) {
1283 return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
1286 static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
1287 struct files_struct *fsp,
1288 const char *aname, const void *value,
1289 size_t size, int flags)
1291 const struct smb_filename *csmb_fname = NULL;
1292 time_t timestamp = 0;
1295 csmb_fname = fsp->fsp_name;
1296 ret = ceph_snap_gmt_strip_snapshot(handle,
1298 ×tamp, NULL, 0);
1303 if (timestamp != 0) {
1307 return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
1308 aname, value, size, flags);
1311 static NTSTATUS ceph_snap_gmt_get_real_filename_at(
1312 struct vfs_handle_struct *handle,
1313 struct files_struct *dirfsp,
1315 TALLOC_CTX *mem_ctx,
1318 time_t timestamp = 0;
1319 char stripped[PATH_MAX + 1];
1320 char conv[PATH_MAX + 1];
1321 struct smb_filename *conv_fname = NULL;
1325 ret = ceph_snap_gmt_strip_snapshot(
1332 return map_nt_error_from_unix(-ret);
1334 if (timestamp == 0) {
1335 return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1336 handle, dirfsp, name, mem_ctx, found_name);
1338 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1339 timestamp, conv, sizeof(conv));
1341 return map_nt_error_from_unix(-ret);
1344 status = synthetic_pathref(
1346 dirfsp->conn->cwd_fsp,
1353 if (!NT_STATUS_IS_OK(status)) {
1357 status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1358 handle, conv_fname->fsp, name, mem_ctx, found_name);
1359 TALLOC_FREE(conv_fname);
1363 static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
1364 const struct smb_filename *csmb_fname,
1369 time_t timestamp = 0;
1370 char stripped[PATH_MAX + 1];
1371 char conv[PATH_MAX + 1];
1373 struct smb_filename *new_fname;
1376 ret = ceph_snap_gmt_strip_snapshot(handle,
1378 ×tamp, stripped, sizeof(stripped));
1383 if (timestamp == 0) {
1384 return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
1385 bsize, dfree, dsize);
1387 ret = ceph_snap_gmt_convert(handle, stripped,
1388 timestamp, conv, sizeof(conv));
1393 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1394 if (new_fname == NULL) {
1398 new_fname->base_name = conv;
1400 ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
1401 bsize, dfree, dsize);
1402 saved_errno = errno;
1403 TALLOC_FREE(new_fname);
1404 errno = saved_errno;
1408 static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
1409 const struct smb_filename *csmb_fname,
1410 enum SMB_QUOTA_TYPE qtype,
1414 time_t timestamp = 0;
1415 char stripped[PATH_MAX + 1];
1416 char conv[PATH_MAX + 1];
1418 struct smb_filename *new_fname;
1421 ret = ceph_snap_gmt_strip_snapshot(handle,
1423 ×tamp, stripped, sizeof(stripped));
1428 if (timestamp == 0) {
1429 return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
1431 ret = ceph_snap_gmt_convert(handle, stripped,
1432 timestamp, conv, sizeof(conv));
1437 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1438 if (new_fname == NULL) {
1442 new_fname->base_name = conv;
1444 ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
1445 saved_errno = errno;
1446 TALLOC_FREE(new_fname);
1447 errno = saved_errno;
1451 static struct vfs_fn_pointers ceph_snap_fns = {
1452 .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
1453 .disk_free_fn = ceph_snap_gmt_disk_free,
1454 .get_quota_fn = ceph_snap_gmt_get_quota,
1455 .renameat_fn = ceph_snap_gmt_renameat,
1456 .linkat_fn = ceph_snap_gmt_linkat,
1457 .symlinkat_fn = ceph_snap_gmt_symlinkat,
1458 .stat_fn = ceph_snap_gmt_stat,
1459 .lstat_fn = ceph_snap_gmt_lstat,
1460 .openat_fn = ceph_snap_gmt_openat,
1461 .unlinkat_fn = ceph_snap_gmt_unlinkat,
1462 .fchmod_fn = ceph_snap_gmt_fchmod,
1463 .chdir_fn = ceph_snap_gmt_chdir,
1464 .fntimes_fn = ceph_snap_gmt_fntimes,
1465 .readlinkat_fn = ceph_snap_gmt_readlinkat,
1466 .mknodat_fn = ceph_snap_gmt_mknodat,
1467 .realpath_fn = ceph_snap_gmt_realpath,
1468 .mkdirat_fn = ceph_snap_gmt_mkdirat,
1469 .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
1470 .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
1471 .fsetxattr_fn = ceph_snap_gmt_fsetxattr,
1472 .fchflags_fn = ceph_snap_gmt_fchflags,
1473 .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at,
1477 NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
1479 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
1480 "ceph_snapshots", &ceph_snap_fns);