WHATSNEW: Add client netlogon ping protocol parameter
[samba4-gss.git] / source3 / modules / vfs_ceph_snapshots.c
blob9514b489237050cebae894935da08f1a4024db8f
1 /*
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/>.
22 #include <dirent.h>
23 #include <libgen.h>
24 #include "includes.h"
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"
31 #include "source3/smbd/dir.h"
33 #undef DBGC_CLASS
34 #define DBGC_CLASS DBGC_VFS
37 * CephFS has a magic snapshots subdirectory in all parts of the directory tree.
38 * This module automatically makes all snapshots in this subdir visible to SMB
39 * clients (if permitted by corresponding access control).
41 #define CEPH_SNAP_SUBDIR_DEFAULT ".snap"
43 * The ceph.snap.btime (virtual) extended attribute carries the snapshot
44 * creation time in $secs.$nsecs format. It was added as part of
45 * https://tracker.ceph.com/issues/38838. Running Samba atop old Ceph versions
46 * which don't provide this xattr will not be able to enumerate or access
47 * snapshots using this module. As an alternative, vfs_shadow_copy2 could be
48 * used instead, alongside special shadow:format snapshot directory names.
50 #define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime"
52 static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle,
53 struct files_struct *fsp,
54 time_t *_snap_secs)
56 int ret;
57 char snap_btime[33];
58 char *s = NULL;
59 char *endptr = NULL;
60 struct timespec snap_timespec;
61 int err;
63 ret = SMB_VFS_NEXT_FGETXATTR(handle,
64 fsp,
65 CEPH_SNAP_BTIME_XATTR,
66 snap_btime,
67 sizeof(snap_btime));
68 if (ret < 0) {
69 DBG_ERR("failed to get %s xattr: %s\n",
70 CEPH_SNAP_BTIME_XATTR, strerror(errno));
71 return -errno;
74 if (ret == 0 || ret >= sizeof(snap_btime) - 1) {
75 return -EINVAL;
78 /* ensure zero termination */
79 snap_btime[ret] = '\0';
81 /* format is sec.nsec */
82 s = strchr(snap_btime, '.');
83 if (s == NULL) {
84 DBG_ERR("invalid %s xattr value: %s\n",
85 CEPH_SNAP_BTIME_XATTR, snap_btime);
86 return -EINVAL;
89 /* First component is seconds, extract it */
90 *s = '\0';
91 snap_timespec.tv_sec = smb_strtoull(snap_btime,
92 &endptr,
93 10,
94 &err,
95 SMB_STR_FULL_STR_CONV);
96 if (err != 0) {
97 return -err;
100 /* second component is nsecs */
101 s++;
102 snap_timespec.tv_nsec = smb_strtoul(s,
103 &endptr,
105 &err,
106 SMB_STR_FULL_STR_CONV);
107 if (err != 0) {
108 return -err;
112 * >> 30 is a rough divide by ~10**9. No need to be exact, as @GMT
113 * tokens only offer 1-second resolution (while twrp is nsec).
115 *_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30);
117 return 0;
121 * XXX Ceph snapshots can be created with sub-second granularity, which means
122 * that multiple snapshots may be mapped to the same @GMT- label.
124 * @this_label is a pre-zeroed buffer to be filled with a @GMT label
125 * @return 0 if label successfully filled or -errno on error.
127 static int ceph_snap_fill_label(struct vfs_handle_struct *handle,
128 TALLOC_CTX *tmp_ctx,
129 struct files_struct *dirfsp,
130 const char *subdir,
131 SHADOW_COPY_LABEL this_label)
133 const char *parent_snapsdir = dirfsp->fsp_name->base_name;
134 struct smb_filename *smb_fname;
135 struct smb_filename *atname = NULL;
136 time_t snap_secs;
137 struct tm gmt_snap_time;
138 struct tm *tm_ret;
139 size_t str_sz;
140 char snap_path[PATH_MAX + 1];
141 int ret;
142 NTSTATUS status;
145 * CephFS snapshot creation times are available via a special
146 * xattr - snapshot b/m/ctimes all match the snap source.
148 ret = snprintf(snap_path, sizeof(snap_path), "%s/%s",
149 parent_snapsdir, subdir);
150 if (ret >= sizeof(snap_path)) {
151 return -EINVAL;
154 smb_fname = synthetic_smb_fname(tmp_ctx,
155 snap_path,
156 NULL,
157 NULL,
160 if (smb_fname == NULL) {
161 return -ENOMEM;
164 ret = vfs_stat(handle->conn, smb_fname);
165 if (ret < 0) {
166 ret = -errno;
167 TALLOC_FREE(smb_fname);
168 return ret;
171 atname = synthetic_smb_fname(tmp_ctx,
172 subdir,
173 NULL,
174 &smb_fname->st,
177 if (atname == NULL) {
178 TALLOC_FREE(smb_fname);
179 return -ENOMEM;
182 status = openat_pathref_fsp(dirfsp, atname);
183 if (!NT_STATUS_IS_OK(status)) {
184 TALLOC_FREE(smb_fname);
185 TALLOC_FREE(atname);
186 return -map_errno_from_nt_status(status);
189 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
190 if (ret < 0) {
191 TALLOC_FREE(smb_fname);
192 TALLOC_FREE(atname);
193 return ret;
195 TALLOC_FREE(smb_fname);
196 TALLOC_FREE(atname);
198 tm_ret = gmtime_r(&snap_secs, &gmt_snap_time);
199 if (tm_ret == NULL) {
200 return -EINVAL;
202 str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL),
203 "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time);
204 if (str_sz == 0) {
205 DBG_ERR("failed to convert tm to @GMT token\n");
206 return -EINVAL;
209 DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n",
210 snap_path, this_label);
212 return 0;
215 static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle,
216 struct smb_filename *snaps_dname,
217 bool labels,
218 struct shadow_copy_data *sc_data)
220 TALLOC_CTX *frame = talloc_stackframe();
221 struct smb_Dir *dir_hnd = NULL;
222 struct files_struct *dirfsp = NULL;
223 const char *dname = NULL;
224 char *talloced = NULL;
225 NTSTATUS status;
226 int ret;
227 uint32_t slots;
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
236 * via readdir.
239 status = OpenDir(frame,
240 handle->conn,
241 snaps_dname,
242 NULL,
244 &dir_hnd);
245 if (!NT_STATUS_IS_OK(status)) {
246 ret = -map_errno_from_nt_status(status);
247 goto err_out;
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,
253 dirfsp,
254 false,
255 SEC_DIR_LIST);
256 if (!NT_STATUS_IS_OK(status)) {
257 DBG_ERR("user does not have list permission "
258 "on snapdir %s\n",
259 fsp_str_dbg(dirfsp));
260 ret = -map_errno_from_nt_status(status);
261 goto err_out;
264 slots = 0;
265 sc_data->num_volumes = 0;
266 sc_data->labels = NULL;
268 while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
269 if (ISDOT(dname) || ISDOTDOT(dname)) {
270 TALLOC_FREE(talloced);
271 continue;
273 sc_data->num_volumes++;
274 if (!labels) {
275 TALLOC_FREE(talloced);
276 continue;
278 if (sc_data->num_volumes > slots) {
279 uint32_t new_slot_count = slots + 10;
280 SMB_ASSERT(new_slot_count > slots);
281 sc_data->labels = talloc_realloc(sc_data,
282 sc_data->labels,
283 SHADOW_COPY_LABEL,
284 new_slot_count);
285 if (sc_data->labels == NULL) {
286 TALLOC_FREE(talloced);
287 ret = -ENOMEM;
288 goto err_closedir;
290 memset(sc_data->labels[slots], 0,
291 sizeof(SHADOW_COPY_LABEL) * 10);
293 DBG_DEBUG("%d->%d slots for enum_snaps response\n",
294 slots, new_slot_count);
295 slots = new_slot_count;
297 DBG_DEBUG("filling shadow copy label for %s/%s\n",
298 snaps_dname->base_name, dname);
299 ret = ceph_snap_fill_label(handle,
300 snaps_dname,
301 dirfsp,
302 dname,
303 sc_data->labels[sc_data->num_volumes - 1]);
304 if (ret < 0) {
305 TALLOC_FREE(talloced);
306 goto err_closedir;
308 TALLOC_FREE(talloced);
311 DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
312 snaps_dname->base_name, sc_data->num_volumes);
314 TALLOC_FREE(frame);
315 return 0;
317 err_closedir:
318 TALLOC_FREE(frame);
319 err_out:
320 TALLOC_FREE(sc_data->labels);
321 return ret;
325 * Prior reading: The Meaning of Path Names
326 * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
328 * translate paths so that we can use the parent dir for .snap access:
329 * myfile -> parent= trimmed=myfile
330 * /a -> parent=/ trimmed=a
331 * dir/sub/file -> parent=dir/sub trimmed=file
332 * /dir/sub -> parent=/dir/ trimmed=sub
334 static int ceph_snap_get_parent_path(const char *connectpath,
335 const char *path,
336 char *_parent_buf,
337 size_t buflen,
338 const char **_trimmed)
340 const char *p;
341 size_t len;
342 int ret;
344 if (!strcmp(path, "/")) {
345 DBG_ERR("can't go past root for %s .snap dir\n", path);
346 return -EINVAL;
349 p = strrchr_m(path, '/'); /* Find final '/', if any */
350 if (p == NULL) {
351 DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
352 ret = strlcpy(_parent_buf, "", buflen);
353 if (ret >= buflen) {
354 return -EINVAL;
356 if (_trimmed != NULL) {
357 *_trimmed = path;
359 return 0;
362 SMB_ASSERT(p >= path);
363 len = p - path;
365 ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
366 if (ret >= buflen) {
367 return -EINVAL;
370 /* for absolute paths, check that we're not going outside the share */
371 if ((len > 0) && (_parent_buf[0] == '/')) {
372 bool connectpath_match = false;
373 size_t clen = strlen(connectpath);
374 DBG_DEBUG("checking absolute path %s lies within share at %s\n",
375 _parent_buf, connectpath);
376 /* need to check for separator, to avoid /x/abcd vs /x/ab */
377 connectpath_match = (strncmp(connectpath,
378 _parent_buf,
379 clen) == 0);
380 if (!connectpath_match
381 || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
382 DBG_ERR("%s parent path is outside of share at %s\n",
383 _parent_buf, connectpath);
384 return -EINVAL;
388 if (_trimmed != NULL) {
390 * point to path component which was trimmed from _parent_buf
391 * excluding path separator.
393 *_trimmed = p + 1;
396 DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
397 path, _parent_buf, p + 1);
399 return 0;
402 static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
403 struct files_struct *fsp,
404 struct shadow_copy_data *sc_data,
405 bool labels)
407 int ret;
408 TALLOC_CTX *tmp_ctx;
409 const char *parent_dir = NULL;
410 char tmp[PATH_MAX + 1];
411 char snaps_path[PATH_MAX + 1];
412 struct smb_filename *snaps_dname = NULL;
413 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
414 "ceph", "snapdir",
415 CEPH_SNAP_SUBDIR_DEFAULT);
417 DBG_DEBUG("getting shadow copy data for %s\n",
418 fsp->fsp_name->base_name);
420 tmp_ctx = talloc_new(fsp);
421 if (tmp_ctx == NULL) {
422 ret = -ENOMEM;
423 goto err_out;
426 if (sc_data == NULL) {
427 ret = -EINVAL;
428 goto err_out;
431 if (fsp->fsp_flags.is_directory) {
432 parent_dir = fsp->fsp_name->base_name;
433 } else {
434 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
435 fsp->fsp_name->base_name,
436 tmp,
437 sizeof(tmp),
438 NULL); /* trimmed */
439 if (ret < 0) {
440 goto err_out;
442 parent_dir = tmp;
445 if (strlen(parent_dir) == 0) {
446 ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
447 } else {
448 ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
449 parent_dir, snapdir);
451 if (ret >= sizeof(snaps_path)) {
452 ret = -EINVAL;
453 goto err_out;
456 snaps_dname = synthetic_smb_fname(tmp_ctx,
457 snaps_path,
458 NULL,
459 NULL,
461 fsp->fsp_name->flags);
462 if (snaps_dname == NULL) {
463 ret = -ENOMEM;
464 goto err_out;
467 ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
468 if (ret < 0) {
469 goto err_out;
472 talloc_free(tmp_ctx);
473 return 0;
475 err_out:
476 talloc_free(tmp_ctx);
477 errno = -ret;
478 return -1;
481 static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
482 const struct smb_filename *smb_fname,
483 time_t *_timestamp,
484 char *_stripped_buf,
485 size_t buflen)
487 size_t len;
489 if (smb_fname->twrp == 0) {
490 goto no_snapshot;
493 if (_stripped_buf != NULL) {
494 len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
495 if (len >= buflen) {
496 return -ENAMETOOLONG;
500 *_timestamp = nt_time_to_unix(smb_fname->twrp);
501 return 0;
502 no_snapshot:
503 *_timestamp = 0;
504 return 0;
507 static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
508 const char *name,
509 time_t timestamp,
510 char *_converted_buf,
511 size_t buflen)
513 int ret;
514 NTSTATUS status;
515 struct smb_Dir *dir_hnd = NULL;
516 struct files_struct *dirfsp = NULL;
517 const char *dname = NULL;
518 char *talloced = NULL;
519 struct smb_filename *snaps_dname = NULL;
520 const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
521 "ceph", "snapdir",
522 CEPH_SNAP_SUBDIR_DEFAULT);
523 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
525 if (tmp_ctx == NULL) {
526 ret = -ENOMEM;
527 goto err_out;
531 * Temporally use the caller's return buffer for this.
533 if (strlen(name) == 0) {
534 ret = strlcpy(_converted_buf, snapdir, buflen);
535 } else {
536 ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
538 if (ret >= buflen) {
539 ret = -EINVAL;
540 goto err_out;
543 snaps_dname = synthetic_smb_fname(tmp_ctx,
544 _converted_buf,
545 NULL,
546 NULL,
548 0); /* XXX check? */
549 if (snaps_dname == NULL) {
550 ret = -ENOMEM;
551 goto err_out;
554 /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
555 ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
556 if (ret < 0) {
557 ret = -errno;
558 goto err_out;
561 DBG_DEBUG("enumerating shadow copy dir at %s\n",
562 snaps_dname->base_name);
564 status = OpenDir(tmp_ctx,
565 handle->conn,
566 snaps_dname,
567 NULL,
569 &dir_hnd);
570 if (!NT_STATUS_IS_OK(status)) {
571 ret = -map_errno_from_nt_status(status);
572 goto err_out;
575 /* Check we have SEC_DIR_LIST access on this fsp. */
576 dirfsp = dir_hnd_fetch_fsp(dir_hnd);
577 status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
578 dirfsp,
579 false,
580 SEC_DIR_LIST);
581 if (!NT_STATUS_IS_OK(status)) {
582 DBG_ERR("user does not have list permission "
583 "on snapdir %s\n",
584 fsp_str_dbg(dirfsp));
585 ret = -map_errno_from_nt_status(status);
586 goto err_out;
589 while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
590 struct smb_filename *smb_fname = NULL;
591 struct smb_filename *atname = NULL;
592 time_t snap_secs = 0;
594 if (ISDOT(dname) || ISDOTDOT(dname)) {
595 TALLOC_FREE(talloced);
596 continue;
599 ret = snprintf(_converted_buf, buflen, "%s/%s",
600 snaps_dname->base_name, dname);
601 if (ret >= buflen) {
602 ret = -EINVAL;
603 goto err_out;
606 smb_fname = synthetic_smb_fname(tmp_ctx,
607 _converted_buf,
608 NULL,
609 NULL,
612 if (smb_fname == NULL) {
613 ret = -ENOMEM;
614 goto err_out;
617 ret = vfs_stat(handle->conn, smb_fname);
618 if (ret < 0) {
619 ret = -errno;
620 TALLOC_FREE(smb_fname);
621 goto err_out;
624 atname = synthetic_smb_fname(tmp_ctx,
625 dname,
626 NULL,
627 &smb_fname->st,
630 if (atname == NULL) {
631 TALLOC_FREE(smb_fname);
632 ret = -ENOMEM;
633 goto err_out;
636 status = openat_pathref_fsp(dirfsp, atname);
637 if (!NT_STATUS_IS_OK(status)) {
638 TALLOC_FREE(smb_fname);
639 TALLOC_FREE(atname);
640 ret = -map_errno_from_nt_status(status);
641 goto err_out;
644 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
645 if (ret < 0) {
646 TALLOC_FREE(smb_fname);
647 TALLOC_FREE(atname);
648 goto err_out;
651 TALLOC_FREE(smb_fname);
652 TALLOC_FREE(atname);
655 * check gmt_snap_time matches @timestamp
657 if (timestamp == snap_secs) {
658 break;
660 DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
661 handle->conn->connectpath, name, (long long)timestamp,
662 dname, (long long)snap_secs);
663 TALLOC_FREE(talloced);
666 if (dname == NULL) {
667 DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
668 handle->conn->connectpath, name, (long long)timestamp);
669 ret = -ENOENT;
670 goto err_out;
673 /* found, _converted_buf already contains path of interest */
674 DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
675 handle->conn->connectpath, name, (long long)timestamp,
676 _converted_buf);
678 TALLOC_FREE(talloced);
679 talloc_free(tmp_ctx);
680 return 0;
682 err_out:
683 TALLOC_FREE(talloced);
684 talloc_free(tmp_ctx);
685 return ret;
688 static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
689 const char *name,
690 time_t timestamp,
691 char *_converted_buf,
692 size_t buflen)
694 int ret;
695 char parent[PATH_MAX + 1];
696 const char *trimmed = NULL;
698 * CephFS Snapshots for a given dir are nested under the ./.snap subdir
699 * *or* under ../.snap/dir (and subsequent parent dirs).
700 * Child dirs inherit snapshots created in parent dirs if the child
701 * exists at the time of snapshot creation.
703 * At this point we don't know whether @name refers to a file or dir, so
704 * first assume it's a dir (with a corresponding .snaps subdir)
706 ret = ceph_snap_gmt_convert_dir(handle,
707 name,
708 timestamp,
709 _converted_buf,
710 buflen);
711 if (ret >= 0) {
712 /* all done: .snap subdir exists - @name is a dir */
713 DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
714 return ret;
717 /* @name/.snap access failed, attempt snapshot access via parent */
718 DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
719 name);
721 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
722 name,
723 parent,
724 sizeof(parent),
725 &trimmed);
726 if (ret < 0) {
727 return ret;
730 ret = ceph_snap_gmt_convert_dir(handle,
731 parent,
732 timestamp,
733 _converted_buf,
734 buflen);
735 if (ret < 0) {
736 return ret;
740 * found snapshot via parent. Append the child path component
741 * that was trimmed... +1 for path separator + 1 for null termination.
743 if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
744 return -EINVAL;
746 strlcat(_converted_buf, "/", buflen);
747 strlcat(_converted_buf, trimmed, buflen);
749 return 0;
752 static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
753 files_struct *srcfsp,
754 const struct smb_filename *smb_fname_src,
755 files_struct *dstfsp,
756 const struct smb_filename *smb_fname_dst,
757 const struct vfs_rename_how *how)
759 int ret;
760 time_t timestamp_src, timestamp_dst;
762 ret = ceph_snap_gmt_strip_snapshot(handle,
763 smb_fname_src,
764 &timestamp_src, NULL, 0);
765 if (ret < 0) {
766 errno = -ret;
767 return -1;
769 ret = ceph_snap_gmt_strip_snapshot(handle,
770 smb_fname_dst,
771 &timestamp_dst, NULL, 0);
772 if (ret < 0) {
773 errno = -ret;
774 return -1;
776 if (timestamp_src != 0) {
777 errno = EXDEV;
778 return -1;
780 if (timestamp_dst != 0) {
781 errno = EROFS;
782 return -1;
784 return SMB_VFS_NEXT_RENAMEAT(handle,
785 srcfsp,
786 smb_fname_src,
787 dstfsp,
788 smb_fname_dst,
789 how);
792 /* block links from writeable shares to snapshots for now, like other modules */
793 static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
794 const struct smb_filename *link_contents,
795 struct files_struct *dirfsp,
796 const struct smb_filename *new_smb_fname)
798 int ret;
799 time_t timestamp_old = 0;
800 time_t timestamp_new = 0;
802 ret = ceph_snap_gmt_strip_snapshot(handle,
803 link_contents,
804 &timestamp_old,
805 NULL, 0);
806 if (ret < 0) {
807 errno = -ret;
808 return -1;
810 ret = ceph_snap_gmt_strip_snapshot(handle,
811 new_smb_fname,
812 &timestamp_new,
813 NULL, 0);
814 if (ret < 0) {
815 errno = -ret;
816 return -1;
818 if ((timestamp_old != 0) || (timestamp_new != 0)) {
819 errno = EROFS;
820 return -1;
822 return SMB_VFS_NEXT_SYMLINKAT(handle,
823 link_contents,
824 dirfsp,
825 new_smb_fname);
828 static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
829 files_struct *srcfsp,
830 const struct smb_filename *old_smb_fname,
831 files_struct *dstfsp,
832 const struct smb_filename *new_smb_fname,
833 int flags)
835 int ret;
836 time_t timestamp_old = 0;
837 time_t timestamp_new = 0;
839 ret = ceph_snap_gmt_strip_snapshot(handle,
840 old_smb_fname,
841 &timestamp_old,
842 NULL, 0);
843 if (ret < 0) {
844 errno = -ret;
845 return -1;
847 ret = ceph_snap_gmt_strip_snapshot(handle,
848 new_smb_fname,
849 &timestamp_new,
850 NULL, 0);
851 if (ret < 0) {
852 errno = -ret;
853 return -1;
855 if ((timestamp_old != 0) || (timestamp_new != 0)) {
856 errno = EROFS;
857 return -1;
859 return SMB_VFS_NEXT_LINKAT(handle,
860 srcfsp,
861 old_smb_fname,
862 dstfsp,
863 new_smb_fname,
864 flags);
867 static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
868 struct smb_filename *smb_fname)
870 time_t timestamp = 0;
871 char stripped[PATH_MAX + 1];
872 char conv[PATH_MAX + 1];
873 char *tmp;
874 int ret;
876 ret = ceph_snap_gmt_strip_snapshot(handle,
877 smb_fname,
878 &timestamp, stripped, sizeof(stripped));
879 if (ret < 0) {
880 errno = -ret;
881 return -1;
883 if (timestamp == 0) {
884 return SMB_VFS_NEXT_STAT(handle, smb_fname);
887 ret = ceph_snap_gmt_convert(handle, stripped,
888 timestamp, conv, sizeof(conv));
889 if (ret < 0) {
890 errno = -ret;
891 return -1;
893 tmp = smb_fname->base_name;
894 smb_fname->base_name = conv;
896 ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
897 smb_fname->base_name = tmp;
898 return ret;
901 static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
902 struct smb_filename *smb_fname)
904 time_t timestamp = 0;
905 char stripped[PATH_MAX + 1];
906 char conv[PATH_MAX + 1];
907 char *tmp;
908 int ret;
910 ret = ceph_snap_gmt_strip_snapshot(handle,
911 smb_fname,
912 &timestamp, stripped, sizeof(stripped));
913 if (ret < 0) {
914 errno = -ret;
915 return -1;
917 if (timestamp == 0) {
918 return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
921 ret = ceph_snap_gmt_convert(handle, stripped,
922 timestamp, conv, sizeof(conv));
923 if (ret < 0) {
924 errno = -ret;
925 return -1;
927 tmp = smb_fname->base_name;
928 smb_fname->base_name = conv;
930 ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
931 smb_fname->base_name = tmp;
932 return ret;
935 static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
936 const struct files_struct *dirfsp,
937 const struct smb_filename *smb_fname_in,
938 files_struct *fsp,
939 const struct vfs_open_how *how)
941 time_t timestamp = 0;
942 struct smb_filename *smb_fname = NULL;
943 char stripped[PATH_MAX + 1];
944 char conv[PATH_MAX + 1];
945 int ret;
946 int saved_errno = 0;
948 ret = ceph_snap_gmt_strip_snapshot(handle,
949 smb_fname_in,
950 &timestamp,
951 stripped,
952 sizeof(stripped));
953 if (ret < 0) {
954 errno = -ret;
955 return -1;
957 if (timestamp == 0) {
958 return SMB_VFS_NEXT_OPENAT(handle,
959 dirfsp,
960 smb_fname_in,
961 fsp,
962 how);
965 ret = ceph_snap_gmt_convert(handle,
966 stripped,
967 timestamp,
968 conv,
969 sizeof(conv));
970 if (ret < 0) {
971 errno = -ret;
972 return -1;
974 smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
975 if (smb_fname == NULL) {
976 return -1;
978 smb_fname->base_name = conv;
980 ret = SMB_VFS_NEXT_OPENAT(handle,
981 dirfsp,
982 smb_fname,
983 fsp,
984 how);
985 if (ret == -1) {
986 saved_errno = errno;
988 TALLOC_FREE(smb_fname);
989 if (saved_errno != 0) {
990 errno = saved_errno;
992 return ret;
995 static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
996 struct files_struct *dirfsp,
997 const struct smb_filename *csmb_fname,
998 int flags)
1000 time_t timestamp = 0;
1001 int ret;
1003 ret = ceph_snap_gmt_strip_snapshot(handle,
1004 csmb_fname,
1005 &timestamp, NULL, 0);
1006 if (ret < 0) {
1007 errno = -ret;
1008 return -1;
1010 if (timestamp != 0) {
1011 errno = EROFS;
1012 return -1;
1014 return SMB_VFS_NEXT_UNLINKAT(handle,
1015 dirfsp,
1016 csmb_fname,
1017 flags);
1020 static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
1021 struct files_struct *fsp,
1022 mode_t mode)
1024 const struct smb_filename *csmb_fname = NULL;
1025 time_t timestamp = 0;
1026 int ret;
1028 csmb_fname = fsp->fsp_name;
1029 ret = ceph_snap_gmt_strip_snapshot(handle,
1030 csmb_fname,
1031 &timestamp, NULL, 0);
1032 if (ret < 0) {
1033 errno = -ret;
1034 return -1;
1036 if (timestamp != 0) {
1037 errno = EROFS;
1038 return -1;
1040 return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
1043 static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
1044 const struct smb_filename *csmb_fname)
1046 time_t timestamp = 0;
1047 char stripped[PATH_MAX + 1];
1048 char conv[PATH_MAX + 1];
1049 int ret;
1050 struct smb_filename *new_fname;
1051 int saved_errno;
1053 ret = ceph_snap_gmt_strip_snapshot(handle,
1054 csmb_fname,
1055 &timestamp, stripped, sizeof(stripped));
1056 if (ret < 0) {
1057 errno = -ret;
1058 return -1;
1060 if (timestamp == 0) {
1061 return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
1064 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1065 timestamp, conv, sizeof(conv));
1066 if (ret < 0) {
1067 errno = -ret;
1068 return -1;
1070 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1071 if (new_fname == NULL) {
1072 errno = ENOMEM;
1073 return -1;
1075 new_fname->base_name = conv;
1077 ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
1078 saved_errno = errno;
1079 TALLOC_FREE(new_fname);
1080 errno = saved_errno;
1081 return ret;
1084 static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
1085 files_struct *fsp,
1086 struct smb_file_time *ft)
1088 time_t timestamp = 0;
1089 int ret;
1091 ret = ceph_snap_gmt_strip_snapshot(handle,
1092 fsp->fsp_name,
1093 &timestamp,
1094 NULL,
1096 if (ret < 0) {
1097 errno = -ret;
1098 return -1;
1100 if (timestamp != 0) {
1101 errno = EROFS;
1102 return -1;
1104 return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
1107 static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
1108 const struct files_struct *dirfsp,
1109 const struct smb_filename *csmb_fname,
1110 char *buf,
1111 size_t bufsiz)
1113 time_t timestamp = 0;
1114 char conv[PATH_MAX + 1];
1115 int ret;
1116 struct smb_filename *full_fname = NULL;
1117 int saved_errno;
1120 * Now this function only looks at csmb_fname->twrp
1121 * we don't need to copy out the path. Just use
1122 * csmb_fname->base_name directly.
1124 ret = ceph_snap_gmt_strip_snapshot(handle,
1125 csmb_fname,
1126 &timestamp, NULL, 0);
1127 if (ret < 0) {
1128 errno = -ret;
1129 return -1;
1131 if (timestamp == 0) {
1132 return SMB_VFS_NEXT_READLINKAT(handle,
1133 dirfsp,
1134 csmb_fname,
1135 buf,
1136 bufsiz);
1139 full_fname = full_path_from_dirfsp_atname(talloc_tos(),
1140 dirfsp,
1141 csmb_fname);
1142 if (full_fname == NULL) {
1143 return -1;
1146 /* Find the snapshot path from the full pathname. */
1147 ret = ceph_snap_gmt_convert(handle,
1148 full_fname->base_name,
1149 timestamp,
1150 conv,
1151 sizeof(conv));
1152 if (ret < 0) {
1153 TALLOC_FREE(full_fname);
1154 errno = -ret;
1155 return -1;
1157 full_fname->base_name = conv;
1159 ret = SMB_VFS_NEXT_READLINKAT(handle,
1160 handle->conn->cwd_fsp,
1161 full_fname,
1162 buf,
1163 bufsiz);
1164 saved_errno = errno;
1165 TALLOC_FREE(full_fname);
1166 errno = saved_errno;
1167 return ret;
1170 static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
1171 files_struct *dirfsp,
1172 const struct smb_filename *csmb_fname,
1173 mode_t mode,
1174 SMB_DEV_T dev)
1176 time_t timestamp = 0;
1177 int ret;
1179 ret = ceph_snap_gmt_strip_snapshot(handle,
1180 csmb_fname,
1181 &timestamp, NULL, 0);
1182 if (ret < 0) {
1183 errno = -ret;
1184 return -1;
1186 if (timestamp != 0) {
1187 errno = EROFS;
1188 return -1;
1190 return SMB_VFS_NEXT_MKNODAT(handle,
1191 dirfsp,
1192 csmb_fname,
1193 mode,
1194 dev);
1197 static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
1198 TALLOC_CTX *ctx,
1199 const struct smb_filename *csmb_fname)
1201 time_t timestamp = 0;
1202 char stripped[PATH_MAX + 1];
1203 char conv[PATH_MAX + 1];
1204 struct smb_filename *result_fname;
1205 int ret;
1206 struct smb_filename *new_fname;
1207 int saved_errno;
1209 ret = ceph_snap_gmt_strip_snapshot(handle,
1210 csmb_fname,
1211 &timestamp, stripped, sizeof(stripped));
1212 if (ret < 0) {
1213 errno = -ret;
1214 return NULL;
1216 if (timestamp == 0) {
1217 return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
1219 ret = ceph_snap_gmt_convert(handle, stripped,
1220 timestamp, conv, sizeof(conv));
1221 if (ret < 0) {
1222 errno = -ret;
1223 return NULL;
1225 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1226 if (new_fname == NULL) {
1227 errno = ENOMEM;
1228 return NULL;
1230 new_fname->base_name = conv;
1232 result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
1233 saved_errno = errno;
1234 TALLOC_FREE(new_fname);
1235 errno = saved_errno;
1236 return result_fname;
1239 static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
1240 struct files_struct *dirfsp,
1241 const struct smb_filename *csmb_fname,
1242 mode_t mode)
1244 time_t timestamp = 0;
1245 int ret;
1247 ret = ceph_snap_gmt_strip_snapshot(handle,
1248 csmb_fname,
1249 &timestamp, NULL, 0);
1250 if (ret < 0) {
1251 errno = -ret;
1252 return -1;
1254 if (timestamp != 0) {
1255 errno = EROFS;
1256 return -1;
1258 return SMB_VFS_NEXT_MKDIRAT(handle,
1259 dirfsp,
1260 csmb_fname,
1261 mode);
1264 static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
1265 struct files_struct *fsp,
1266 unsigned int flags)
1268 time_t timestamp = 0;
1269 int ret;
1271 ret = ceph_snap_gmt_strip_snapshot(handle,
1272 fsp->fsp_name,
1273 &timestamp, NULL, 0);
1274 if (ret < 0) {
1275 errno = -ret;
1276 return -1;
1278 if (timestamp != 0) {
1279 errno = EROFS;
1280 return -1;
1282 return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
1285 static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
1286 struct files_struct *fsp,
1287 const char *aname, const void *value,
1288 size_t size, int flags)
1290 const struct smb_filename *csmb_fname = NULL;
1291 time_t timestamp = 0;
1292 int ret;
1294 csmb_fname = fsp->fsp_name;
1295 ret = ceph_snap_gmt_strip_snapshot(handle,
1296 csmb_fname,
1297 &timestamp, NULL, 0);
1298 if (ret < 0) {
1299 errno = -ret;
1300 return -1;
1302 if (timestamp != 0) {
1303 errno = EROFS;
1304 return -1;
1306 return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
1307 aname, value, size, flags);
1310 static NTSTATUS ceph_snap_gmt_get_real_filename_at(
1311 struct vfs_handle_struct *handle,
1312 struct files_struct *dirfsp,
1313 const char *name,
1314 TALLOC_CTX *mem_ctx,
1315 char **found_name)
1317 time_t timestamp = 0;
1318 char stripped[PATH_MAX + 1];
1319 char conv[PATH_MAX + 1];
1320 struct smb_filename *conv_fname = NULL;
1321 int ret;
1322 NTSTATUS status;
1324 ret = ceph_snap_gmt_strip_snapshot(
1325 handle,
1326 dirfsp->fsp_name,
1327 &timestamp,
1328 stripped,
1329 sizeof(stripped));
1330 if (ret < 0) {
1331 return map_nt_error_from_unix(-ret);
1333 if (timestamp == 0) {
1334 return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1335 handle, dirfsp, name, mem_ctx, found_name);
1337 ret = ceph_snap_gmt_convert_dir(handle, stripped,
1338 timestamp, conv, sizeof(conv));
1339 if (ret < 0) {
1340 return map_nt_error_from_unix(-ret);
1343 status = synthetic_pathref(
1344 talloc_tos(),
1345 dirfsp->conn->cwd_fsp,
1346 conv,
1347 NULL,
1348 NULL,
1351 &conv_fname);
1352 if (!NT_STATUS_IS_OK(status)) {
1353 return status;
1356 status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1357 handle, conv_fname->fsp, name, mem_ctx, found_name);
1358 TALLOC_FREE(conv_fname);
1359 return status;
1362 static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
1363 const struct smb_filename *csmb_fname,
1364 uint64_t *bsize,
1365 uint64_t *dfree,
1366 uint64_t *dsize)
1368 time_t timestamp = 0;
1369 char stripped[PATH_MAX + 1];
1370 char conv[PATH_MAX + 1];
1371 int ret;
1372 struct smb_filename *new_fname;
1373 int saved_errno;
1375 ret = ceph_snap_gmt_strip_snapshot(handle,
1376 csmb_fname,
1377 &timestamp, stripped, sizeof(stripped));
1378 if (ret < 0) {
1379 errno = -ret;
1380 return -1;
1382 if (timestamp == 0) {
1383 return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
1384 bsize, dfree, dsize);
1386 ret = ceph_snap_gmt_convert(handle, stripped,
1387 timestamp, conv, sizeof(conv));
1388 if (ret < 0) {
1389 errno = -ret;
1390 return -1;
1392 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1393 if (new_fname == NULL) {
1394 errno = ENOMEM;
1395 return -1;
1397 new_fname->base_name = conv;
1399 ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
1400 bsize, dfree, dsize);
1401 saved_errno = errno;
1402 TALLOC_FREE(new_fname);
1403 errno = saved_errno;
1404 return ret;
1407 static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
1408 const struct smb_filename *csmb_fname,
1409 enum SMB_QUOTA_TYPE qtype,
1410 unid_t id,
1411 SMB_DISK_QUOTA *dq)
1413 time_t timestamp = 0;
1414 char stripped[PATH_MAX + 1];
1415 char conv[PATH_MAX + 1];
1416 int ret;
1417 struct smb_filename *new_fname;
1418 int saved_errno;
1420 ret = ceph_snap_gmt_strip_snapshot(handle,
1421 csmb_fname,
1422 &timestamp, stripped, sizeof(stripped));
1423 if (ret < 0) {
1424 errno = -ret;
1425 return -1;
1427 if (timestamp == 0) {
1428 return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
1430 ret = ceph_snap_gmt_convert(handle, stripped,
1431 timestamp, conv, sizeof(conv));
1432 if (ret < 0) {
1433 errno = -ret;
1434 return -1;
1436 new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1437 if (new_fname == NULL) {
1438 errno = ENOMEM;
1439 return -1;
1441 new_fname->base_name = conv;
1443 ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
1444 saved_errno = errno;
1445 TALLOC_FREE(new_fname);
1446 errno = saved_errno;
1447 return ret;
1450 static struct vfs_fn_pointers ceph_snap_fns = {
1451 .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
1452 .disk_free_fn = ceph_snap_gmt_disk_free,
1453 .get_quota_fn = ceph_snap_gmt_get_quota,
1454 .renameat_fn = ceph_snap_gmt_renameat,
1455 .linkat_fn = ceph_snap_gmt_linkat,
1456 .symlinkat_fn = ceph_snap_gmt_symlinkat,
1457 .stat_fn = ceph_snap_gmt_stat,
1458 .lstat_fn = ceph_snap_gmt_lstat,
1459 .openat_fn = ceph_snap_gmt_openat,
1460 .unlinkat_fn = ceph_snap_gmt_unlinkat,
1461 .fchmod_fn = ceph_snap_gmt_fchmod,
1462 .chdir_fn = ceph_snap_gmt_chdir,
1463 .fntimes_fn = ceph_snap_gmt_fntimes,
1464 .readlinkat_fn = ceph_snap_gmt_readlinkat,
1465 .mknodat_fn = ceph_snap_gmt_mknodat,
1466 .realpath_fn = ceph_snap_gmt_realpath,
1467 .mkdirat_fn = ceph_snap_gmt_mkdirat,
1468 .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
1469 .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
1470 .fsetxattr_fn = ceph_snap_gmt_fsetxattr,
1471 .fchflags_fn = ceph_snap_gmt_fchflags,
1472 .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at,
1475 static_decl_vfs;
1476 NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
1478 return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
1479 "ceph_snapshots", &ceph_snap_fns);