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, &talloced)) != NULL) {
268 if (ISDOT(dname) || ISDOTDOT(dname)) {
269 TALLOC_FREE(talloced);
272 sc_data->num_volumes++;
274 TALLOC_FREE(talloced);
277 if (sc_data->num_volumes > slots) {
278 uint32_t new_slot_count = slots + 10;
279 SMB_ASSERT(new_slot_count > slots);
280 sc_data->labels = talloc_realloc(sc_data,
284 if (sc_data->labels == NULL) {
285 TALLOC_FREE(talloced);
289 memset(sc_data->labels[slots], 0,
290 sizeof(SHADOW_COPY_LABEL) * 10);
292 DBG_DEBUG("%d->%d slots for enum_snaps response\n",
293 slots, new_slot_count);
294 slots = new_slot_count;
296 DBG_DEBUG("filling shadow copy label for %s/%s\n",
297 snaps_dname->base_name, dname);
298 ret = ceph_snap_fill_label(handle,
302 sc_data->labels[sc_data->num_volumes - 1]);
304 TALLOC_FREE(talloced);
307 TALLOC_FREE(talloced);
310 DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
311 snaps_dname->base_name, sc_data->num_volumes);
319 TALLOC_FREE(sc_data->labels);
324 * Prior reading: The Meaning of Path Names
325 * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
327 * translate paths so that we can use the parent dir for .snap access:
328 * myfile -> parent= trimmed=myfile
329 * /a -> parent=/ trimmed=a
330 * dir/sub/file -> parent=dir/sub trimmed=file
331 * /dir/sub -> parent=/dir/ trimmed=sub
333 static int ceph_snap_get_parent_path(const char *connectpath,
337 const char **_trimmed)
343 if (!strcmp(path, "/")) {
344 DBG_ERR("can't go past root for %s .snap dir\n", path);
348 p = strrchr_m(path, '/'); /* Find final '/', if any */
350 DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
351 ret = strlcpy(_parent_buf, "", buflen);
355 if (_trimmed != NULL) {
361 SMB_ASSERT(p >= path);
364 ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
369 /* for absolute paths, check that we're not going outside the share */
370 if ((len > 0) && (_parent_buf[0] == '/')) {
371 bool connectpath_match = false;
372 size_t clen = strlen(connectpath);
373 DBG_DEBUG("checking absolute path %s lies within share at %s\n",
374 _parent_buf, connectpath);
375 /* need to check for separator, to avoid /x/abcd vs /x/ab */
376 connectpath_match = (strncmp(connectpath,
379 if (!connectpath_match
380 || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
381 DBG_ERR("%s parent path is outside of share at %s\n",
382 _parent_buf, connectpath);
387 if (_trimmed != NULL) {
389 * point to path component which was trimmed from _parent_buf
390 * excluding path separator.
395 DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
396 path, _parent_buf, p + 1);
401 static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
402 struct files_struct *fsp,
403 struct shadow_copy_data *sc_data,
408 const char *parent_dir = NULL;
409 char tmp[PATH_MAX + 1];
410 char snaps_path[PATH_MAX + 1];
411 struct smb_filename *snaps_dname = NULL;
412 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
414 CEPH_SNAP_SUBDIR_DEFAULT);
416 DBG_DEBUG("getting shadow copy data for %s\n",
417 fsp->fsp_name->base_name);
419 tmp_ctx = talloc_new(fsp);
420 if (tmp_ctx == NULL) {
425 if (sc_data == NULL) {
430 if (fsp->fsp_flags.is_directory) {
431 parent_dir = fsp->fsp_name->base_name;
433 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
434 fsp->fsp_name->base_name,
444 if (strlen(parent_dir) == 0) {
445 ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
447 ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
448 parent_dir, snapdir);
450 if (ret >= sizeof(snaps_path)) {
455 snaps_dname = synthetic_smb_fname(tmp_ctx,
460 fsp->fsp_name->flags);
461 if (snaps_dname == NULL) {
466 ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
471 talloc_free(tmp_ctx);
475 talloc_free(tmp_ctx);
480 static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
481 const struct smb_filename *smb_fname,
488 if (smb_fname->twrp == 0) {
492 if (_stripped_buf != NULL) {
493 len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
495 return -ENAMETOOLONG;
499 *_timestamp = nt_time_to_unix(smb_fname->twrp);
506 static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
509 char *_converted_buf,
514 struct smb_Dir *dir_hnd = NULL;
515 struct files_struct *dirfsp = NULL;
516 const char *dname = NULL;
517 char *talloced = NULL;
518 struct smb_filename *snaps_dname = NULL;
519 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
521 CEPH_SNAP_SUBDIR_DEFAULT);
522 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
524 if (tmp_ctx == NULL) {
530 * Temporally use the caller's return buffer for this.
532 if (strlen(name) == 0) {
533 ret = strlcpy(_converted_buf, snapdir, buflen);
535 ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
542 snaps_dname = synthetic_smb_fname(tmp_ctx,
548 if (snaps_dname == NULL) {
553 /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
554 ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
560 DBG_DEBUG("enumerating shadow copy dir at %s\n",
561 snaps_dname->base_name);
563 status = OpenDir(tmp_ctx,
569 if (!NT_STATUS_IS_OK(status)) {
570 ret = -map_errno_from_nt_status(status);
574 /* Check we have SEC_DIR_LIST access on this fsp. */
575 dirfsp = dir_hnd_fetch_fsp(dir_hnd);
576 status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
580 if (!NT_STATUS_IS_OK(status)) {
581 DBG_ERR("user does not have list permission "
583 fsp_str_dbg(dirfsp));
584 ret = -map_errno_from_nt_status(status);
588 while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
589 struct smb_filename *smb_fname = NULL;
590 struct smb_filename *atname = NULL;
591 time_t snap_secs = 0;
593 if (ISDOT(dname) || ISDOTDOT(dname)) {
594 TALLOC_FREE(talloced);
598 ret = snprintf(_converted_buf, buflen, "%s/%s",
599 snaps_dname->base_name, dname);
605 smb_fname = synthetic_smb_fname(tmp_ctx,
611 if (smb_fname == NULL) {
616 ret = vfs_stat(handle->conn, smb_fname);
619 TALLOC_FREE(smb_fname);
623 atname = synthetic_smb_fname(tmp_ctx,
629 if (atname == NULL) {
630 TALLOC_FREE(smb_fname);
635 status = openat_pathref_fsp(dirfsp, atname);
636 if (!NT_STATUS_IS_OK(status)) {
637 TALLOC_FREE(smb_fname);
639 ret = -map_errno_from_nt_status(status);
643 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
645 TALLOC_FREE(smb_fname);
650 TALLOC_FREE(smb_fname);
654 * check gmt_snap_time matches @timestamp
656 if (timestamp == snap_secs) {
659 DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
660 handle->conn->connectpath, name, (long long)timestamp,
661 dname, (long long)snap_secs);
662 TALLOC_FREE(talloced);
666 DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
667 handle->conn->connectpath, name, (long long)timestamp);
672 /* found, _converted_buf already contains path of interest */
673 DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
674 handle->conn->connectpath, name, (long long)timestamp,
677 TALLOC_FREE(talloced);
678 talloc_free(tmp_ctx);
682 TALLOC_FREE(talloced);
683 talloc_free(tmp_ctx);
687 static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
690 char *_converted_buf,
694 char parent[PATH_MAX + 1];
695 const char *trimmed = NULL;
697 * CephFS Snapshots for a given dir are nested under the ./.snap subdir
698 * *or* under ../.snap/dir (and subsequent parent dirs).
699 * Child dirs inherit snapshots created in parent dirs if the child
700 * exists at the time of snapshot creation.
702 * At this point we don't know whether @name refers to a file or dir, so
703 * first assume it's a dir (with a corresponding .snaps subdir)
705 ret = ceph_snap_gmt_convert_dir(handle,
711 /* all done: .snap subdir exists - @name is a dir */
712 DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
716 /* @name/.snap access failed, attempt snapshot access via parent */
717 DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
720 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
729 ret = ceph_snap_gmt_convert_dir(handle,
739 * found snapshot via parent. Append the child path component
740 * that was trimmed... +1 for path separator + 1 for null termination.
742 if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
745 strlcat(_converted_buf, "/", buflen);
746 strlcat(_converted_buf, trimmed, buflen);
751 static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
752 files_struct *srcfsp,
753 const struct smb_filename *smb_fname_src,
754 files_struct *dstfsp,
755 const struct smb_filename *smb_fname_dst)
758 time_t timestamp_src, timestamp_dst;
760 ret = ceph_snap_gmt_strip_snapshot(handle,
762 ×tamp_src, NULL, 0);
767 ret = ceph_snap_gmt_strip_snapshot(handle,
769 ×tamp_dst, NULL, 0);
774 if (timestamp_src != 0) {
778 if (timestamp_dst != 0) {
782 return SMB_VFS_NEXT_RENAMEAT(handle,
789 /* block links from writeable shares to snapshots for now, like other modules */
790 static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
791 const struct smb_filename *link_contents,
792 struct files_struct *dirfsp,
793 const struct smb_filename *new_smb_fname)
796 time_t timestamp_old = 0;
797 time_t timestamp_new = 0;
799 ret = ceph_snap_gmt_strip_snapshot(handle,
807 ret = ceph_snap_gmt_strip_snapshot(handle,
815 if ((timestamp_old != 0) || (timestamp_new != 0)) {
819 return SMB_VFS_NEXT_SYMLINKAT(handle,
825 static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
826 files_struct *srcfsp,
827 const struct smb_filename *old_smb_fname,
828 files_struct *dstfsp,
829 const struct smb_filename *new_smb_fname,
833 time_t timestamp_old = 0;
834 time_t timestamp_new = 0;
836 ret = ceph_snap_gmt_strip_snapshot(handle,
844 ret = ceph_snap_gmt_strip_snapshot(handle,
852 if ((timestamp_old != 0) || (timestamp_new != 0)) {
856 return SMB_VFS_NEXT_LINKAT(handle,
864 static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
865 struct smb_filename *smb_fname)
867 time_t timestamp = 0;
868 char stripped[PATH_MAX + 1];
869 char conv[PATH_MAX + 1];
873 ret = ceph_snap_gmt_strip_snapshot(handle,
875 ×tamp, stripped, sizeof(stripped));
880 if (timestamp == 0) {
881 return SMB_VFS_NEXT_STAT(handle, smb_fname);
884 ret = ceph_snap_gmt_convert(handle, stripped,
885 timestamp, conv, sizeof(conv));
890 tmp = smb_fname->base_name;
891 smb_fname->base_name = conv;
893 ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
894 smb_fname->base_name = tmp;
898 static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
899 struct smb_filename *smb_fname)
901 time_t timestamp = 0;
902 char stripped[PATH_MAX + 1];
903 char conv[PATH_MAX + 1];
907 ret = ceph_snap_gmt_strip_snapshot(handle,
909 ×tamp, stripped, sizeof(stripped));
914 if (timestamp == 0) {
915 return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
918 ret = ceph_snap_gmt_convert(handle, stripped,
919 timestamp, conv, sizeof(conv));
924 tmp = smb_fname->base_name;
925 smb_fname->base_name = conv;
927 ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
928 smb_fname->base_name = tmp;
932 static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
933 const struct files_struct *dirfsp,
934 const struct smb_filename *smb_fname_in,
936 const struct vfs_open_how *how)
938 time_t timestamp = 0;
939 struct smb_filename *smb_fname = NULL;
940 char stripped[PATH_MAX + 1];
941 char conv[PATH_MAX + 1];
945 ret = ceph_snap_gmt_strip_snapshot(handle,
954 if (timestamp == 0) {
955 return SMB_VFS_NEXT_OPENAT(handle,
962 ret = ceph_snap_gmt_convert(handle,
971 smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
972 if (smb_fname == NULL) {
975 smb_fname->base_name = conv;
977 ret = SMB_VFS_NEXT_OPENAT(handle,
985 TALLOC_FREE(smb_fname);
986 if (saved_errno != 0) {
992 static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
993 struct files_struct *dirfsp,
994 const struct smb_filename *csmb_fname,
997 time_t timestamp = 0;
1000 ret = ceph_snap_gmt_strip_snapshot(handle,
1002 ×tamp, NULL, 0);
1007 if (timestamp != 0) {
1011 return SMB_VFS_NEXT_UNLINKAT(handle,
1017 static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
1018 struct files_struct *fsp,
1021 const struct smb_filename *csmb_fname = NULL;
1022 time_t timestamp = 0;
1025 csmb_fname = fsp->fsp_name;
1026 ret = ceph_snap_gmt_strip_snapshot(handle,
1028 ×tamp, NULL, 0);
1033 if (timestamp != 0) {
1037 return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
1040 static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
1041 const struct smb_filename *csmb_fname)
1043 time_t timestamp = 0;
1044 char stripped[PATH_MAX + 1];
1045 char conv[PATH_MAX + 1];
1047 struct smb_filename *new_fname;
1050 ret = ceph_snap_gmt_strip_snapshot(handle,
1052 ×tamp, stripped, sizeof(stripped));
1057 if (timestamp == 0) {
1058 return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
1061 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1062 timestamp, conv, sizeof(conv));
1067 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1068 if (new_fname == NULL) {
1072 new_fname->base_name = conv;
1074 ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
1075 saved_errno = errno;
1076 TALLOC_FREE(new_fname);
1077 errno = saved_errno;
1081 static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
1083 struct smb_file_time *ft)
1085 time_t timestamp = 0;
1088 ret = ceph_snap_gmt_strip_snapshot(handle,
1097 if (timestamp != 0) {
1101 return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
1104 static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
1105 const struct files_struct *dirfsp,
1106 const struct smb_filename *csmb_fname,
1110 time_t timestamp = 0;
1111 char conv[PATH_MAX + 1];
1113 struct smb_filename *full_fname = NULL;
1117 * Now this function only looks at csmb_fname->twrp
1118 * we don't need to copy out the path. Just use
1119 * csmb_fname->base_name directly.
1121 ret = ceph_snap_gmt_strip_snapshot(handle,
1123 ×tamp, NULL, 0);
1128 if (timestamp == 0) {
1129 return SMB_VFS_NEXT_READLINKAT(handle,
1136 full_fname = full_path_from_dirfsp_atname(talloc_tos(),
1139 if (full_fname == NULL) {
1143 /* Find the snapshot path from the full pathname. */
1144 ret = ceph_snap_gmt_convert(handle,
1145 full_fname->base_name,
1150 TALLOC_FREE(full_fname);
1154 full_fname->base_name = conv;
1156 ret = SMB_VFS_NEXT_READLINKAT(handle,
1157 handle->conn->cwd_fsp,
1161 saved_errno = errno;
1162 TALLOC_FREE(full_fname);
1163 errno = saved_errno;
1167 static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
1168 files_struct *dirfsp,
1169 const struct smb_filename *csmb_fname,
1173 time_t timestamp = 0;
1176 ret = ceph_snap_gmt_strip_snapshot(handle,
1178 ×tamp, NULL, 0);
1183 if (timestamp != 0) {
1187 return SMB_VFS_NEXT_MKNODAT(handle,
1194 static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
1196 const struct smb_filename *csmb_fname)
1198 time_t timestamp = 0;
1199 char stripped[PATH_MAX + 1];
1200 char conv[PATH_MAX + 1];
1201 struct smb_filename *result_fname;
1203 struct smb_filename *new_fname;
1206 ret = ceph_snap_gmt_strip_snapshot(handle,
1208 ×tamp, stripped, sizeof(stripped));
1213 if (timestamp == 0) {
1214 return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
1216 ret = ceph_snap_gmt_convert(handle, stripped,
1217 timestamp, conv, sizeof(conv));
1222 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1223 if (new_fname == NULL) {
1227 new_fname->base_name = conv;
1229 result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
1230 saved_errno = errno;
1231 TALLOC_FREE(new_fname);
1232 errno = saved_errno;
1233 return result_fname;
1236 static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
1237 struct files_struct *dirfsp,
1238 const struct smb_filename *csmb_fname,
1241 time_t timestamp = 0;
1244 ret = ceph_snap_gmt_strip_snapshot(handle,
1246 ×tamp, NULL, 0);
1251 if (timestamp != 0) {
1255 return SMB_VFS_NEXT_MKDIRAT(handle,
1261 static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
1262 struct files_struct *fsp,
1265 time_t timestamp = 0;
1268 ret = ceph_snap_gmt_strip_snapshot(handle,
1270 ×tamp, NULL, 0);
1275 if (timestamp != 0) {
1279 return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
1282 static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
1283 struct files_struct *fsp,
1284 const char *aname, const void *value,
1285 size_t size, int flags)
1287 const struct smb_filename *csmb_fname = NULL;
1288 time_t timestamp = 0;
1291 csmb_fname = fsp->fsp_name;
1292 ret = ceph_snap_gmt_strip_snapshot(handle,
1294 ×tamp, NULL, 0);
1299 if (timestamp != 0) {
1303 return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
1304 aname, value, size, flags);
1307 static NTSTATUS ceph_snap_gmt_get_real_filename_at(
1308 struct vfs_handle_struct *handle,
1309 struct files_struct *dirfsp,
1311 TALLOC_CTX *mem_ctx,
1314 time_t timestamp = 0;
1315 char stripped[PATH_MAX + 1];
1316 char conv[PATH_MAX + 1];
1317 struct smb_filename *conv_fname = NULL;
1321 ret = ceph_snap_gmt_strip_snapshot(
1328 return map_nt_error_from_unix(-ret);
1330 if (timestamp == 0) {
1331 return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1332 handle, dirfsp, name, mem_ctx, found_name);
1334 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1335 timestamp, conv, sizeof(conv));
1337 return map_nt_error_from_unix(-ret);
1340 status = synthetic_pathref(
1342 dirfsp->conn->cwd_fsp,
1349 if (!NT_STATUS_IS_OK(status)) {
1353 status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1354 handle, conv_fname->fsp, name, mem_ctx, found_name);
1355 TALLOC_FREE(conv_fname);
1359 static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
1360 const struct smb_filename *csmb_fname,
1365 time_t timestamp = 0;
1366 char stripped[PATH_MAX + 1];
1367 char conv[PATH_MAX + 1];
1369 struct smb_filename *new_fname;
1372 ret = ceph_snap_gmt_strip_snapshot(handle,
1374 ×tamp, stripped, sizeof(stripped));
1379 if (timestamp == 0) {
1380 return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
1381 bsize, dfree, dsize);
1383 ret = ceph_snap_gmt_convert(handle, stripped,
1384 timestamp, conv, sizeof(conv));
1389 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1390 if (new_fname == NULL) {
1394 new_fname->base_name = conv;
1396 ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
1397 bsize, dfree, dsize);
1398 saved_errno = errno;
1399 TALLOC_FREE(new_fname);
1400 errno = saved_errno;
1404 static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
1405 const struct smb_filename *csmb_fname,
1406 enum SMB_QUOTA_TYPE qtype,
1410 time_t timestamp = 0;
1411 char stripped[PATH_MAX + 1];
1412 char conv[PATH_MAX + 1];
1414 struct smb_filename *new_fname;
1417 ret = ceph_snap_gmt_strip_snapshot(handle,
1419 ×tamp, stripped, sizeof(stripped));
1424 if (timestamp == 0) {
1425 return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
1427 ret = ceph_snap_gmt_convert(handle, stripped,
1428 timestamp, conv, sizeof(conv));
1433 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1434 if (new_fname == NULL) {
1438 new_fname->base_name = conv;
1440 ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
1441 saved_errno = errno;
1442 TALLOC_FREE(new_fname);
1443 errno = saved_errno;
1447 static struct vfs_fn_pointers ceph_snap_fns = {
1448 .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
1449 .disk_free_fn = ceph_snap_gmt_disk_free,
1450 .get_quota_fn = ceph_snap_gmt_get_quota,
1451 .renameat_fn = ceph_snap_gmt_renameat,
1452 .linkat_fn = ceph_snap_gmt_linkat,
1453 .symlinkat_fn = ceph_snap_gmt_symlinkat,
1454 .stat_fn = ceph_snap_gmt_stat,
1455 .lstat_fn = ceph_snap_gmt_lstat,
1456 .openat_fn = ceph_snap_gmt_openat,
1457 .unlinkat_fn = ceph_snap_gmt_unlinkat,
1458 .fchmod_fn = ceph_snap_gmt_fchmod,
1459 .chdir_fn = ceph_snap_gmt_chdir,
1460 .fntimes_fn = ceph_snap_gmt_fntimes,
1461 .readlinkat_fn = ceph_snap_gmt_readlinkat,
1462 .mknodat_fn = ceph_snap_gmt_mknodat,
1463 .realpath_fn = ceph_snap_gmt_realpath,
1464 .mkdirat_fn = ceph_snap_gmt_mkdirat,
1465 .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
1466 .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
1467 .fsetxattr_fn = ceph_snap_gmt_fsetxattr,
1468 .fchflags_fn = ceph_snap_gmt_fchflags,
1469 .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at,
1473 NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
1475 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
1476 "ceph_snapshots", &ceph_snap_fns);