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;
229 DBG_DEBUG("enumerating shadow copy dir at %s\n",
230 snaps_dname->base_name);
233 * CephFS stat(dir).size *normally* returns the number of child entries
234 * for a given dir, but it unfortunately that's not the case for the one
235 * place we need it (dir=.snap), so we need to dynamically determine it
239 status = OpenDir(frame,
245 if (!NT_STATUS_IS_OK(status)) {
246 ret = -map_errno_from_nt_status(status);
250 /* Check we have SEC_DIR_LIST access on this fsp. */
251 dirfsp = dir_hnd_fetch_fsp(dir_hnd);
252 status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
256 if (!NT_STATUS_IS_OK(status)) {
257 DBG_ERR("user does not have list permission "
259 fsp_str_dbg(dirfsp));
260 ret = -map_errno_from_nt_status(status);
265 sc_data->num_volumes = 0;
266 sc_data->labels = NULL;
268 while ((dname = ReadDirName(dir_hnd, &offset, NULL, &talloced))
271 if (ISDOT(dname) || ISDOTDOT(dname)) {
272 TALLOC_FREE(talloced);
275 sc_data->num_volumes++;
277 TALLOC_FREE(talloced);
280 if (sc_data->num_volumes > slots) {
281 uint32_t new_slot_count = slots + 10;
282 SMB_ASSERT(new_slot_count > slots);
283 sc_data->labels = talloc_realloc(sc_data,
287 if (sc_data->labels == NULL) {
288 TALLOC_FREE(talloced);
292 memset(sc_data->labels[slots], 0,
293 sizeof(SHADOW_COPY_LABEL) * 10);
295 DBG_DEBUG("%d->%d slots for enum_snaps response\n",
296 slots, new_slot_count);
297 slots = new_slot_count;
299 DBG_DEBUG("filling shadow copy label for %s/%s\n",
300 snaps_dname->base_name, dname);
301 ret = ceph_snap_fill_label(handle,
305 sc_data->labels[sc_data->num_volumes - 1]);
307 TALLOC_FREE(talloced);
310 TALLOC_FREE(talloced);
313 DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
314 snaps_dname->base_name, sc_data->num_volumes);
322 TALLOC_FREE(sc_data->labels);
327 * Prior reading: The Meaning of Path Names
328 * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
330 * translate paths so that we can use the parent dir for .snap access:
331 * myfile -> parent= trimmed=myfile
332 * /a -> parent=/ trimmed=a
333 * dir/sub/file -> parent=dir/sub trimmed=file
334 * /dir/sub -> parent=/dir/ trimmed=sub
336 static int ceph_snap_get_parent_path(const char *connectpath,
340 const char **_trimmed)
346 if (!strcmp(path, "/")) {
347 DBG_ERR("can't go past root for %s .snap dir\n", path);
351 p = strrchr_m(path, '/'); /* Find final '/', if any */
353 DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
354 ret = strlcpy(_parent_buf, "", buflen);
358 if (_trimmed != NULL) {
364 SMB_ASSERT(p >= path);
367 ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
372 /* for absolute paths, check that we're not going outside the share */
373 if ((len > 0) && (_parent_buf[0] == '/')) {
374 bool connectpath_match = false;
375 size_t clen = strlen(connectpath);
376 DBG_DEBUG("checking absolute path %s lies within share at %s\n",
377 _parent_buf, connectpath);
378 /* need to check for separator, to avoid /x/abcd vs /x/ab */
379 connectpath_match = (strncmp(connectpath,
382 if (!connectpath_match
383 || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
384 DBG_ERR("%s parent path is outside of share at %s\n",
385 _parent_buf, connectpath);
390 if (_trimmed != NULL) {
392 * point to path component which was trimmed from _parent_buf
393 * excluding path separator.
398 DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
399 path, _parent_buf, p + 1);
404 static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
405 struct files_struct *fsp,
406 struct shadow_copy_data *sc_data,
411 const char *parent_dir = NULL;
412 char tmp[PATH_MAX + 1];
413 char snaps_path[PATH_MAX + 1];
414 struct smb_filename *snaps_dname = NULL;
415 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
417 CEPH_SNAP_SUBDIR_DEFAULT);
419 DBG_DEBUG("getting shadow copy data for %s\n",
420 fsp->fsp_name->base_name);
422 tmp_ctx = talloc_new(fsp);
423 if (tmp_ctx == NULL) {
428 if (sc_data == NULL) {
433 if (fsp->fsp_flags.is_directory) {
434 parent_dir = fsp->fsp_name->base_name;
436 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
437 fsp->fsp_name->base_name,
447 if (strlen(parent_dir) == 0) {
448 ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
450 ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
451 parent_dir, snapdir);
453 if (ret >= sizeof(snaps_path)) {
458 snaps_dname = synthetic_smb_fname(tmp_ctx,
463 fsp->fsp_name->flags);
464 if (snaps_dname == NULL) {
469 ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
474 talloc_free(tmp_ctx);
478 talloc_free(tmp_ctx);
483 static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
484 const struct smb_filename *smb_fname,
491 if (smb_fname->twrp == 0) {
495 if (_stripped_buf != NULL) {
496 len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
498 return -ENAMETOOLONG;
502 *_timestamp = nt_time_to_unix(smb_fname->twrp);
509 static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
512 char *_converted_buf,
517 struct smb_Dir *dir_hnd = NULL;
518 struct files_struct *dirfsp = NULL;
519 const char *dname = NULL;
520 char *talloced = NULL;
522 struct smb_filename *snaps_dname = NULL;
523 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
525 CEPH_SNAP_SUBDIR_DEFAULT);
526 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
528 if (tmp_ctx == NULL) {
534 * Temporally use the caller's return buffer for this.
536 if (strlen(name) == 0) {
537 ret = strlcpy(_converted_buf, snapdir, buflen);
539 ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
546 snaps_dname = synthetic_smb_fname(tmp_ctx,
552 if (snaps_dname == NULL) {
557 /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
558 ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
564 DBG_DEBUG("enumerating shadow copy dir at %s\n",
565 snaps_dname->base_name);
567 status = OpenDir(tmp_ctx,
573 if (!NT_STATUS_IS_OK(status)) {
574 ret = -map_errno_from_nt_status(status);
578 /* Check we have SEC_DIR_LIST access on this fsp. */
579 dirfsp = dir_hnd_fetch_fsp(dir_hnd);
580 status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
584 if (!NT_STATUS_IS_OK(status)) {
585 DBG_ERR("user does not have list permission "
587 fsp_str_dbg(dirfsp));
588 ret = -map_errno_from_nt_status(status);
592 while ((dname = ReadDirName(dir_hnd, &offset, NULL, &talloced))
595 struct smb_filename *smb_fname = NULL;
596 struct smb_filename *atname = NULL;
597 time_t snap_secs = 0;
599 if (ISDOT(dname) || ISDOTDOT(dname)) {
600 TALLOC_FREE(talloced);
604 ret = snprintf(_converted_buf, buflen, "%s/%s",
605 snaps_dname->base_name, dname);
611 smb_fname = synthetic_smb_fname(tmp_ctx,
617 if (smb_fname == NULL) {
622 ret = vfs_stat(handle->conn, smb_fname);
625 TALLOC_FREE(smb_fname);
629 atname = synthetic_smb_fname(tmp_ctx,
635 if (atname == NULL) {
636 TALLOC_FREE(smb_fname);
641 status = openat_pathref_fsp(dirfsp, atname);
642 if (!NT_STATUS_IS_OK(status)) {
643 TALLOC_FREE(smb_fname);
645 ret = -map_errno_from_nt_status(status);
649 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
651 TALLOC_FREE(smb_fname);
656 TALLOC_FREE(smb_fname);
660 * check gmt_snap_time matches @timestamp
662 if (timestamp == snap_secs) {
665 DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
666 handle->conn->connectpath, name, (long long)timestamp,
667 dname, (long long)snap_secs);
668 TALLOC_FREE(talloced);
672 DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
673 handle->conn->connectpath, name, (long long)timestamp);
678 /* found, _converted_buf already contains path of interest */
679 DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
680 handle->conn->connectpath, name, (long long)timestamp,
683 TALLOC_FREE(talloced);
684 talloc_free(tmp_ctx);
688 TALLOC_FREE(talloced);
689 talloc_free(tmp_ctx);
693 static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
696 char *_converted_buf,
700 char parent[PATH_MAX + 1];
701 const char *trimmed = NULL;
703 * CephFS Snapshots for a given dir are nested under the ./.snap subdir
704 * *or* under ../.snap/dir (and subsequent parent dirs).
705 * Child dirs inherit snapshots created in parent dirs if the child
706 * exists at the time of snapshot creation.
708 * At this point we don't know whether @name refers to a file or dir, so
709 * first assume it's a dir (with a corresponding .snaps subdir)
711 ret = ceph_snap_gmt_convert_dir(handle,
717 /* all done: .snap subdir exists - @name is a dir */
718 DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
722 /* @name/.snap access failed, attempt snapshot access via parent */
723 DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
726 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
735 ret = ceph_snap_gmt_convert_dir(handle,
745 * found snapshot via parent. Append the child path component
746 * that was trimmed... +1 for path separator + 1 for null termination.
748 if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
751 strlcat(_converted_buf, "/", buflen);
752 strlcat(_converted_buf, trimmed, buflen);
757 static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
758 files_struct *srcfsp,
759 const struct smb_filename *smb_fname_src,
760 files_struct *dstfsp,
761 const struct smb_filename *smb_fname_dst)
764 time_t timestamp_src, timestamp_dst;
766 ret = ceph_snap_gmt_strip_snapshot(handle,
768 ×tamp_src, NULL, 0);
773 ret = ceph_snap_gmt_strip_snapshot(handle,
775 ×tamp_dst, NULL, 0);
780 if (timestamp_src != 0) {
784 if (timestamp_dst != 0) {
788 return SMB_VFS_NEXT_RENAMEAT(handle,
795 /* block links from writeable shares to snapshots for now, like other modules */
796 static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
797 const struct smb_filename *link_contents,
798 struct files_struct *dirfsp,
799 const struct smb_filename *new_smb_fname)
802 time_t timestamp_old = 0;
803 time_t timestamp_new = 0;
805 ret = ceph_snap_gmt_strip_snapshot(handle,
813 ret = ceph_snap_gmt_strip_snapshot(handle,
821 if ((timestamp_old != 0) || (timestamp_new != 0)) {
825 return SMB_VFS_NEXT_SYMLINKAT(handle,
831 static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
832 files_struct *srcfsp,
833 const struct smb_filename *old_smb_fname,
834 files_struct *dstfsp,
835 const struct smb_filename *new_smb_fname,
839 time_t timestamp_old = 0;
840 time_t timestamp_new = 0;
842 ret = ceph_snap_gmt_strip_snapshot(handle,
850 ret = ceph_snap_gmt_strip_snapshot(handle,
858 if ((timestamp_old != 0) || (timestamp_new != 0)) {
862 return SMB_VFS_NEXT_LINKAT(handle,
870 static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
871 struct smb_filename *smb_fname)
873 time_t timestamp = 0;
874 char stripped[PATH_MAX + 1];
875 char conv[PATH_MAX + 1];
879 ret = ceph_snap_gmt_strip_snapshot(handle,
881 ×tamp, stripped, sizeof(stripped));
886 if (timestamp == 0) {
887 return SMB_VFS_NEXT_STAT(handle, smb_fname);
890 ret = ceph_snap_gmt_convert(handle, stripped,
891 timestamp, conv, sizeof(conv));
896 tmp = smb_fname->base_name;
897 smb_fname->base_name = conv;
899 ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
900 smb_fname->base_name = tmp;
904 static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
905 struct smb_filename *smb_fname)
907 time_t timestamp = 0;
908 char stripped[PATH_MAX + 1];
909 char conv[PATH_MAX + 1];
913 ret = ceph_snap_gmt_strip_snapshot(handle,
915 ×tamp, stripped, sizeof(stripped));
920 if (timestamp == 0) {
921 return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
924 ret = ceph_snap_gmt_convert(handle, stripped,
925 timestamp, conv, sizeof(conv));
930 tmp = smb_fname->base_name;
931 smb_fname->base_name = conv;
933 ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
934 smb_fname->base_name = tmp;
938 static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
939 const struct files_struct *dirfsp,
940 const struct smb_filename *smb_fname_in,
945 time_t timestamp = 0;
946 struct smb_filename *smb_fname = NULL;
947 char stripped[PATH_MAX + 1];
948 char conv[PATH_MAX + 1];
952 ret = ceph_snap_gmt_strip_snapshot(handle,
961 if (timestamp == 0) {
962 return SMB_VFS_NEXT_OPENAT(handle,
970 ret = ceph_snap_gmt_convert(handle,
979 smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
980 if (smb_fname == NULL) {
983 smb_fname->base_name = conv;
985 ret = SMB_VFS_NEXT_OPENAT(handle,
994 TALLOC_FREE(smb_fname);
995 if (saved_errno != 0) {
1001 static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
1002 struct files_struct *dirfsp,
1003 const struct smb_filename *csmb_fname,
1006 time_t timestamp = 0;
1009 ret = ceph_snap_gmt_strip_snapshot(handle,
1011 ×tamp, NULL, 0);
1016 if (timestamp != 0) {
1020 return SMB_VFS_NEXT_UNLINKAT(handle,
1026 static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
1027 struct files_struct *fsp,
1030 const struct smb_filename *csmb_fname = NULL;
1031 time_t timestamp = 0;
1034 csmb_fname = fsp->fsp_name;
1035 ret = ceph_snap_gmt_strip_snapshot(handle,
1037 ×tamp, NULL, 0);
1042 if (timestamp != 0) {
1046 return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
1049 static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
1050 const struct smb_filename *csmb_fname)
1052 time_t timestamp = 0;
1053 char stripped[PATH_MAX + 1];
1054 char conv[PATH_MAX + 1];
1056 struct smb_filename *new_fname;
1059 ret = ceph_snap_gmt_strip_snapshot(handle,
1061 ×tamp, stripped, sizeof(stripped));
1066 if (timestamp == 0) {
1067 return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
1070 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1071 timestamp, conv, sizeof(conv));
1076 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1077 if (new_fname == NULL) {
1081 new_fname->base_name = conv;
1083 ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
1084 saved_errno = errno;
1085 TALLOC_FREE(new_fname);
1086 errno = saved_errno;
1090 static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
1092 struct smb_file_time *ft)
1094 time_t timestamp = 0;
1097 ret = ceph_snap_gmt_strip_snapshot(handle,
1106 if (timestamp != 0) {
1110 return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
1113 static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
1114 const struct files_struct *dirfsp,
1115 const struct smb_filename *csmb_fname,
1119 time_t timestamp = 0;
1120 char conv[PATH_MAX + 1];
1122 struct smb_filename *full_fname = NULL;
1126 * Now this function only looks at csmb_fname->twrp
1127 * we don't need to copy out the path. Just use
1128 * csmb_fname->base_name directly.
1130 ret = ceph_snap_gmt_strip_snapshot(handle,
1132 ×tamp, NULL, 0);
1137 if (timestamp == 0) {
1138 return SMB_VFS_NEXT_READLINKAT(handle,
1145 full_fname = full_path_from_dirfsp_atname(talloc_tos(),
1148 if (full_fname == NULL) {
1152 /* Find the snapshot path from the full pathname. */
1153 ret = ceph_snap_gmt_convert(handle,
1154 full_fname->base_name,
1159 TALLOC_FREE(full_fname);
1163 full_fname->base_name = conv;
1165 ret = SMB_VFS_NEXT_READLINKAT(handle,
1166 handle->conn->cwd_fsp,
1170 saved_errno = errno;
1171 TALLOC_FREE(full_fname);
1172 errno = saved_errno;
1176 static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
1177 files_struct *dirfsp,
1178 const struct smb_filename *csmb_fname,
1182 time_t timestamp = 0;
1185 ret = ceph_snap_gmt_strip_snapshot(handle,
1187 ×tamp, NULL, 0);
1192 if (timestamp != 0) {
1196 return SMB_VFS_NEXT_MKNODAT(handle,
1203 static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
1205 const struct smb_filename *csmb_fname)
1207 time_t timestamp = 0;
1208 char stripped[PATH_MAX + 1];
1209 char conv[PATH_MAX + 1];
1210 struct smb_filename *result_fname;
1212 struct smb_filename *new_fname;
1215 ret = ceph_snap_gmt_strip_snapshot(handle,
1217 ×tamp, stripped, sizeof(stripped));
1222 if (timestamp == 0) {
1223 return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
1225 ret = ceph_snap_gmt_convert(handle, stripped,
1226 timestamp, conv, sizeof(conv));
1231 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1232 if (new_fname == NULL) {
1236 new_fname->base_name = conv;
1238 result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
1239 saved_errno = errno;
1240 TALLOC_FREE(new_fname);
1241 errno = saved_errno;
1242 return result_fname;
1245 static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
1246 struct files_struct *dirfsp,
1247 const struct smb_filename *csmb_fname,
1250 time_t timestamp = 0;
1253 ret = ceph_snap_gmt_strip_snapshot(handle,
1255 ×tamp, NULL, 0);
1260 if (timestamp != 0) {
1264 return SMB_VFS_NEXT_MKDIRAT(handle,
1270 static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
1271 struct files_struct *fsp,
1274 time_t timestamp = 0;
1277 ret = ceph_snap_gmt_strip_snapshot(handle,
1279 ×tamp, NULL, 0);
1284 if (timestamp != 0) {
1288 return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
1291 static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
1292 struct files_struct *fsp,
1293 const char *aname, const void *value,
1294 size_t size, int flags)
1296 const struct smb_filename *csmb_fname = NULL;
1297 time_t timestamp = 0;
1300 csmb_fname = fsp->fsp_name;
1301 ret = ceph_snap_gmt_strip_snapshot(handle,
1303 ×tamp, NULL, 0);
1308 if (timestamp != 0) {
1312 return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
1313 aname, value, size, flags);
1316 static NTSTATUS ceph_snap_gmt_get_real_filename(
1317 struct vfs_handle_struct *handle,
1318 const struct smb_filename *path,
1320 TALLOC_CTX *mem_ctx,
1323 time_t timestamp = 0;
1324 char stripped[PATH_MAX + 1];
1325 char conv[PATH_MAX + 1];
1326 struct smb_filename conv_fname;
1330 ret = ceph_snap_gmt_strip_snapshot(handle, path,
1331 ×tamp, stripped, sizeof(stripped));
1333 return map_nt_error_from_unix(-ret);
1335 if (timestamp == 0) {
1336 return SMB_VFS_NEXT_GET_REAL_FILENAME(handle, path, name,
1337 mem_ctx, found_name);
1339 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1340 timestamp, conv, sizeof(conv));
1342 return map_nt_error_from_unix(-ret);
1345 conv_fname = (struct smb_filename) {
1349 status = SMB_VFS_NEXT_GET_REAL_FILENAME(
1350 handle, &conv_fname, name, mem_ctx, found_name);
1354 static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
1355 const struct smb_filename *csmb_fname,
1360 time_t timestamp = 0;
1361 char stripped[PATH_MAX + 1];
1362 char conv[PATH_MAX + 1];
1364 struct smb_filename *new_fname;
1367 ret = ceph_snap_gmt_strip_snapshot(handle,
1369 ×tamp, stripped, sizeof(stripped));
1374 if (timestamp == 0) {
1375 return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
1376 bsize, dfree, dsize);
1378 ret = ceph_snap_gmt_convert(handle, stripped,
1379 timestamp, conv, sizeof(conv));
1384 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1385 if (new_fname == NULL) {
1389 new_fname->base_name = conv;
1391 ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
1392 bsize, dfree, dsize);
1393 saved_errno = errno;
1394 TALLOC_FREE(new_fname);
1395 errno = saved_errno;
1399 static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
1400 const struct smb_filename *csmb_fname,
1401 enum SMB_QUOTA_TYPE qtype,
1405 time_t timestamp = 0;
1406 char stripped[PATH_MAX + 1];
1407 char conv[PATH_MAX + 1];
1409 struct smb_filename *new_fname;
1412 ret = ceph_snap_gmt_strip_snapshot(handle,
1414 ×tamp, stripped, sizeof(stripped));
1419 if (timestamp == 0) {
1420 return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
1422 ret = ceph_snap_gmt_convert(handle, stripped,
1423 timestamp, conv, sizeof(conv));
1428 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1429 if (new_fname == NULL) {
1433 new_fname->base_name = conv;
1435 ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
1436 saved_errno = errno;
1437 TALLOC_FREE(new_fname);
1438 errno = saved_errno;
1442 static struct vfs_fn_pointers ceph_snap_fns = {
1443 .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
1444 .disk_free_fn = ceph_snap_gmt_disk_free,
1445 .get_quota_fn = ceph_snap_gmt_get_quota,
1446 .renameat_fn = ceph_snap_gmt_renameat,
1447 .linkat_fn = ceph_snap_gmt_linkat,
1448 .symlinkat_fn = ceph_snap_gmt_symlinkat,
1449 .stat_fn = ceph_snap_gmt_stat,
1450 .lstat_fn = ceph_snap_gmt_lstat,
1451 .openat_fn = ceph_snap_gmt_openat,
1452 .unlinkat_fn = ceph_snap_gmt_unlinkat,
1453 .fchmod_fn = ceph_snap_gmt_fchmod,
1454 .chdir_fn = ceph_snap_gmt_chdir,
1455 .fntimes_fn = ceph_snap_gmt_fntimes,
1456 .readlinkat_fn = ceph_snap_gmt_readlinkat,
1457 .mknodat_fn = ceph_snap_gmt_mknodat,
1458 .realpath_fn = ceph_snap_gmt_realpath,
1459 .mkdirat_fn = ceph_snap_gmt_mkdirat,
1460 .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
1461 .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
1462 .fsetxattr_fn = ceph_snap_gmt_fsetxattr,
1463 .fchflags_fn = ceph_snap_gmt_fchflags,
1464 .get_real_filename_fn = ceph_snap_gmt_get_real_filename,
1468 NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
1470 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
1471 "ceph_snapshots", &ceph_snap_fns);