vfs: Convert get_real_filename() to NTSTATUS
[samba.git] / source3 / modules / vfs_ceph_snapshots.c
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.
5  *
6  * Copyright (C) David Disseldorp 2019
7  *
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.
12  *
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.
17  *
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/>.
20  */
21
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
32 #undef DBGC_CLASS
33 #define DBGC_CLASS DBGC_VFS
34
35 /*
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).
39  */
40 #define CEPH_SNAP_SUBDIR_DEFAULT ".snap"
41 /*
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.
48  */
49 #define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime"
50
51 static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle,
52                                    struct files_struct *fsp,
53                                    time_t *_snap_secs)
54 {
55         int ret;
56         char snap_btime[33];
57         char *s = NULL;
58         char *endptr = NULL;
59         struct timespec snap_timespec;
60         int err;
61
62         ret = SMB_VFS_NEXT_FGETXATTR(handle,
63                                     fsp,
64                                     CEPH_SNAP_BTIME_XATTR,
65                                     snap_btime,
66                                     sizeof(snap_btime));
67         if (ret < 0) {
68                 DBG_ERR("failed to get %s xattr: %s\n",
69                         CEPH_SNAP_BTIME_XATTR, strerror(errno));
70                 return -errno;
71         }
72
73         if (ret == 0 || ret >= sizeof(snap_btime) - 1) {
74                 return -EINVAL;
75         }
76
77         /* ensure zero termination */
78         snap_btime[ret] = '\0';
79
80         /* format is sec.nsec */
81         s = strchr(snap_btime, '.');
82         if (s == NULL) {
83                 DBG_ERR("invalid %s xattr value: %s\n",
84                         CEPH_SNAP_BTIME_XATTR, snap_btime);
85                 return -EINVAL;
86         }
87
88         /* First component is seconds, extract it */
89         *s = '\0';
90         snap_timespec.tv_sec = smb_strtoull(snap_btime,
91                                             &endptr,
92                                             10,
93                                             &err,
94                                             SMB_STR_FULL_STR_CONV);
95         if (err != 0) {
96                 return -err;
97         }
98
99         /* second component is nsecs */
100         s++;
101         snap_timespec.tv_nsec = smb_strtoul(s,
102                                             &endptr,
103                                             10,
104                                             &err,
105                                             SMB_STR_FULL_STR_CONV);
106         if (err != 0) {
107                 return -err;
108         }
109
110         /*
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).
113          */
114         *_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30);
115
116         return 0;
117 }
118
119 /*
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.
122  *
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.
125  */
126 static int ceph_snap_fill_label(struct vfs_handle_struct *handle,
127                                 TALLOC_CTX *tmp_ctx,
128                                 struct files_struct *dirfsp,
129                                 const char *subdir,
130                                 SHADOW_COPY_LABEL this_label)
131 {
132         const char *parent_snapsdir = dirfsp->fsp_name->base_name;
133         struct smb_filename *smb_fname;
134         struct smb_filename *atname = NULL;
135         time_t snap_secs;
136         struct tm gmt_snap_time;
137         struct tm *tm_ret;
138         size_t str_sz;
139         char snap_path[PATH_MAX + 1];
140         int ret;
141         NTSTATUS status;
142
143         /*
144          * CephFS snapshot creation times are available via a special
145          * xattr - snapshot b/m/ctimes all match the snap source.
146          */
147         ret = snprintf(snap_path, sizeof(snap_path), "%s/%s",
148                         parent_snapsdir, subdir);
149         if (ret >= sizeof(snap_path)) {
150                 return -EINVAL;
151         }
152
153         smb_fname = synthetic_smb_fname(tmp_ctx,
154                                         snap_path,
155                                         NULL,
156                                         NULL,
157                                         0,
158                                         0);
159         if (smb_fname == NULL) {
160                 return -ENOMEM;
161         }
162
163         ret = vfs_stat(handle->conn, smb_fname);
164         if (ret < 0) {
165                 ret = -errno;
166                 TALLOC_FREE(smb_fname);
167                 return ret;
168         }
169
170         atname = synthetic_smb_fname(tmp_ctx,
171                                      subdir,
172                                      NULL,
173                                      &smb_fname->st,
174                                      0,
175                                      0);
176         if (atname == NULL) {
177                 TALLOC_FREE(smb_fname);
178                 return -ENOMEM;
179         }
180
181         status = openat_pathref_fsp(dirfsp, atname);
182         if (!NT_STATUS_IS_OK(status)) {
183                 TALLOC_FREE(smb_fname);
184                 TALLOC_FREE(atname);
185                 return -map_errno_from_nt_status(status);
186         }
187
188         ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
189         if (ret < 0) {
190                 TALLOC_FREE(smb_fname);
191                 TALLOC_FREE(atname);
192                 return ret;
193         }
194         TALLOC_FREE(smb_fname);
195         TALLOC_FREE(atname);
196
197         tm_ret = gmtime_r(&snap_secs, &gmt_snap_time);
198         if (tm_ret == NULL) {
199                 return -EINVAL;
200         }
201         str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL),
202                           "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time);
203         if (str_sz == 0) {
204                 DBG_ERR("failed to convert tm to @GMT token\n");
205                 return -EINVAL;
206         }
207
208         DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n",
209                   snap_path, this_label);
210
211         return 0;
212 }
213
214 static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle,
215                                   struct smb_filename *snaps_dname,
216                                   bool labels,
217                                   struct shadow_copy_data *sc_data)
218 {
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;
224         long offset = 0;
225         NTSTATUS status;
226         int ret;
227         uint32_t slots;
228
229         DBG_DEBUG("enumerating shadow copy dir at %s\n",
230                   snaps_dname->base_name);
231
232         /*
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.
237          */
238
239         status = OpenDir(frame,
240                          handle->conn,
241                          snaps_dname,
242                          NULL,
243                          0,
244                          &dir_hnd);
245         if (!NT_STATUS_IS_OK(status)) {
246                 ret = -map_errno_from_nt_status(status);
247                 goto err_out;
248         }
249
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;
262         }
263
264         slots = 0;
265         sc_data->num_volumes = 0;
266         sc_data->labels = NULL;
267
268         while ((dname = ReadDirName(dir_hnd, &offset, NULL, &talloced))
269                != NULL)
270         {
271                 if (ISDOT(dname) || ISDOTDOT(dname)) {
272                         TALLOC_FREE(talloced);
273                         continue;
274                 }
275                 sc_data->num_volumes++;
276                 if (!labels) {
277                         TALLOC_FREE(talloced);
278                         continue;
279                 }
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,
284                                                          sc_data->labels,
285                                                          SHADOW_COPY_LABEL,
286                                                          new_slot_count);
287                         if (sc_data->labels == NULL) {
288                                 TALLOC_FREE(talloced);
289                                 ret = -ENOMEM;
290                                 goto err_closedir;
291                         }
292                         memset(sc_data->labels[slots], 0,
293                                sizeof(SHADOW_COPY_LABEL) * 10);
294
295                         DBG_DEBUG("%d->%d slots for enum_snaps response\n",
296                                   slots, new_slot_count);
297                         slots = new_slot_count;
298                 }
299                 DBG_DEBUG("filling shadow copy label for %s/%s\n",
300                           snaps_dname->base_name, dname);
301                 ret = ceph_snap_fill_label(handle,
302                                 snaps_dname,
303                                 dirfsp,
304                                 dname,
305                                 sc_data->labels[sc_data->num_volumes - 1]);
306                 if (ret < 0) {
307                         TALLOC_FREE(talloced);
308                         goto err_closedir;
309                 }
310                 TALLOC_FREE(talloced);
311         }
312
313         DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
314                   snaps_dname->base_name, sc_data->num_volumes);
315
316         TALLOC_FREE(frame);
317         return 0;
318
319 err_closedir:
320         TALLOC_FREE(frame);
321 err_out:
322         TALLOC_FREE(sc_data->labels);
323         return ret;
324 }
325
326 /*
327  * Prior reading: The Meaning of Path Names
328  *   https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
329  *
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
335  */
336 static int ceph_snap_get_parent_path(const char *connectpath,
337                                      const char *path,
338                                      char *_parent_buf,
339                                      size_t buflen,
340                                      const char **_trimmed)
341 {
342         const char *p;
343         size_t len;
344         int ret;
345
346         if (!strcmp(path, "/")) {
347                 DBG_ERR("can't go past root for %s .snap dir\n", path);
348                 return -EINVAL;
349         }
350
351         p = strrchr_m(path, '/'); /* Find final '/', if any */
352         if (p == NULL) {
353                 DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
354                 ret = strlcpy(_parent_buf, "", buflen);
355                 if (ret >= buflen) {
356                         return -EINVAL;
357                 }
358                 if (_trimmed != NULL) {
359                         *_trimmed = path;
360                 }
361                 return 0;
362         }
363
364         SMB_ASSERT(p >= path);
365         len = p - path;
366
367         ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
368         if (ret >= buflen) {
369                 return -EINVAL;
370         }
371
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,
380                                         _parent_buf,
381                                         clen) == 0);
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);
386                         return -EINVAL;
387                 }
388         }
389
390         if (_trimmed != NULL) {
391                 /*
392                  * point to path component which was trimmed from _parent_buf
393                  * excluding path separator.
394                  */
395                 *_trimmed = p + 1;
396         }
397
398         DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
399                   path, _parent_buf, p + 1);
400
401         return 0;
402 }
403
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,
407                                         bool labels)
408 {
409         int ret;
410         TALLOC_CTX *tmp_ctx;
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),
416                                                    "ceph", "snapdir",
417                                                    CEPH_SNAP_SUBDIR_DEFAULT);
418
419         DBG_DEBUG("getting shadow copy data for %s\n",
420                   fsp->fsp_name->base_name);
421
422         tmp_ctx = talloc_new(fsp);
423         if (tmp_ctx == NULL) {
424                 ret = -ENOMEM;
425                 goto err_out;
426         }
427
428         if (sc_data == NULL) {
429                 ret = -EINVAL;
430                 goto err_out;
431         }
432
433         if (fsp->fsp_flags.is_directory) {
434                 parent_dir = fsp->fsp_name->base_name;
435         } else {
436                 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
437                                                 fsp->fsp_name->base_name,
438                                                 tmp,
439                                                 sizeof(tmp),
440                                                 NULL);  /* trimmed */
441                 if (ret < 0) {
442                         goto err_out;
443                 }
444                 parent_dir = tmp;
445         }
446
447         if (strlen(parent_dir) == 0) {
448                 ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
449         } else {
450                 ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
451                                parent_dir, snapdir);
452         }
453         if (ret >= sizeof(snaps_path)) {
454                 ret = -EINVAL;
455                 goto err_out;
456         }
457
458         snaps_dname = synthetic_smb_fname(tmp_ctx,
459                                 snaps_path,
460                                 NULL,
461                                 NULL,
462                                 0,
463                                 fsp->fsp_name->flags);
464         if (snaps_dname == NULL) {
465                 ret = -ENOMEM;
466                 goto err_out;
467         }
468
469         ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
470         if (ret < 0) {
471                 goto err_out;
472         }
473
474         talloc_free(tmp_ctx);
475         return 0;
476
477 err_out:
478         talloc_free(tmp_ctx);
479         errno = -ret;
480         return -1;
481 }
482
483 static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
484                                          const struct smb_filename *smb_fname,
485                                          time_t *_timestamp,
486                                          char *_stripped_buf,
487                                          size_t buflen)
488 {
489         size_t len;
490
491         if (smb_fname->twrp == 0) {
492                 goto no_snapshot;
493         }
494
495         if (_stripped_buf != NULL) {
496                 len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
497                 if (len >= buflen) {
498                         return -ENAMETOOLONG;
499                 }
500         }
501
502         *_timestamp = nt_time_to_unix(smb_fname->twrp);
503         return 0;
504 no_snapshot:
505         *_timestamp = 0;
506         return 0;
507 }
508
509 static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
510                                      const char *name,
511                                      time_t timestamp,
512                                      char *_converted_buf,
513                                      size_t buflen)
514 {
515         int ret;
516         NTSTATUS status;
517         struct smb_Dir *dir_hnd = NULL;
518         struct files_struct *dirfsp = NULL;
519         const char *dname = NULL;
520         char *talloced = NULL;
521         long offset = 0;
522         struct smb_filename *snaps_dname = NULL;
523         const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
524                                                    "ceph", "snapdir",
525                                                    CEPH_SNAP_SUBDIR_DEFAULT);
526         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
527
528         if (tmp_ctx == NULL) {
529                 ret = -ENOMEM;
530                 goto err_out;
531         }
532
533         /*
534          * Temporally use the caller's return buffer for this.
535          */
536         if (strlen(name) == 0) {
537                 ret = strlcpy(_converted_buf, snapdir, buflen);
538         } else {
539                 ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
540         }
541         if (ret >= buflen) {
542                 ret = -EINVAL;
543                 goto err_out;
544         }
545
546         snaps_dname = synthetic_smb_fname(tmp_ctx,
547                                 _converted_buf,
548                                 NULL,
549                                 NULL,
550                                 0,
551                                 0);     /* XXX check? */
552         if (snaps_dname == NULL) {
553                 ret = -ENOMEM;
554                 goto err_out;
555         }
556
557         /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
558         ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
559         if (ret < 0) {
560                 ret = -errno;
561                 goto err_out;
562         }
563
564         DBG_DEBUG("enumerating shadow copy dir at %s\n",
565                   snaps_dname->base_name);
566
567         status = OpenDir(tmp_ctx,
568                          handle->conn,
569                          snaps_dname,
570                          NULL,
571                          0,
572                          &dir_hnd);
573         if (!NT_STATUS_IS_OK(status)) {
574                 ret = -map_errno_from_nt_status(status);
575                 goto err_out;
576         }
577
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,
581                                               dirfsp,
582                                               false,
583                                               SEC_DIR_LIST);
584         if (!NT_STATUS_IS_OK(status)) {
585                 DBG_ERR("user does not have list permission "
586                         "on snapdir %s\n",
587                         fsp_str_dbg(dirfsp));
588                 ret = -map_errno_from_nt_status(status);
589                 goto err_out;
590         }
591
592         while ((dname = ReadDirName(dir_hnd, &offset, NULL, &talloced))
593                != NULL)
594         {
595                 struct smb_filename *smb_fname = NULL;
596                 struct smb_filename *atname = NULL;
597                 time_t snap_secs = 0;
598
599                 if (ISDOT(dname) || ISDOTDOT(dname)) {
600                         TALLOC_FREE(talloced);
601                         continue;
602                 }
603
604                 ret = snprintf(_converted_buf, buflen, "%s/%s",
605                                snaps_dname->base_name, dname);
606                 if (ret >= buflen) {
607                         ret = -EINVAL;
608                         goto err_out;
609                 }
610
611                 smb_fname = synthetic_smb_fname(tmp_ctx,
612                                                 _converted_buf,
613                                                 NULL,
614                                                 NULL,
615                                                 0,
616                                                 0);
617                 if (smb_fname == NULL) {
618                         ret = -ENOMEM;
619                         goto err_out;
620                 }
621
622                 ret = vfs_stat(handle->conn, smb_fname);
623                 if (ret < 0) {
624                         ret = -errno;
625                         TALLOC_FREE(smb_fname);
626                         goto err_out;
627                 }
628
629                 atname = synthetic_smb_fname(tmp_ctx,
630                                              dname,
631                                              NULL,
632                                              &smb_fname->st,
633                                              0,
634                                              0);
635                 if (atname == NULL) {
636                         TALLOC_FREE(smb_fname);
637                         ret = -ENOMEM;
638                         goto err_out;
639                 }
640
641                 status = openat_pathref_fsp(dirfsp, atname);
642                 if (!NT_STATUS_IS_OK(status)) {
643                         TALLOC_FREE(smb_fname);
644                         TALLOC_FREE(atname);
645                         ret = -map_errno_from_nt_status(status);
646                         goto err_out;
647                 }
648
649                 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
650                 if (ret < 0) {
651                         TALLOC_FREE(smb_fname);
652                         TALLOC_FREE(atname);
653                         goto err_out;
654                 }
655
656                 TALLOC_FREE(smb_fname);
657                 TALLOC_FREE(atname);
658
659                 /*
660                  * check gmt_snap_time matches @timestamp
661                  */
662                 if (timestamp == snap_secs) {
663                         break;
664                 }
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);
669         }
670
671         if (dname == NULL) {
672                 DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
673                          handle->conn->connectpath, name, (long long)timestamp);
674                 ret = -ENOENT;
675                 goto err_out;
676         }
677
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,
681                   _converted_buf);
682
683         TALLOC_FREE(talloced);
684         talloc_free(tmp_ctx);
685         return 0;
686
687 err_out:
688         TALLOC_FREE(talloced);
689         talloc_free(tmp_ctx);
690         return ret;
691 }
692
693 static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
694                                      const char *name,
695                                      time_t timestamp,
696                                      char *_converted_buf,
697                                      size_t buflen)
698 {
699         int ret;
700         char parent[PATH_MAX + 1];
701         const char *trimmed = NULL;
702         /*
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.
707          *
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)
710          */
711         ret = ceph_snap_gmt_convert_dir(handle,
712                                         name,
713                                         timestamp,
714                                         _converted_buf,
715                                         buflen);
716         if (ret >= 0) {
717                 /* all done: .snap subdir exists - @name is a dir */
718                 DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
719                 return ret;
720         }
721
722         /* @name/.snap access failed, attempt snapshot access via parent */
723         DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
724                   name);
725
726         ret = ceph_snap_get_parent_path(handle->conn->connectpath,
727                                         name,
728                                         parent,
729                                         sizeof(parent),
730                                         &trimmed);
731         if (ret < 0) {
732                 return ret;
733         }
734
735         ret = ceph_snap_gmt_convert_dir(handle,
736                                         parent,
737                                         timestamp,
738                                         _converted_buf,
739                                         buflen);
740         if (ret < 0) {
741                 return ret;
742         }
743
744         /*
745          * found snapshot via parent. Append the child path component
746          * that was trimmed... +1 for path separator + 1 for null termination.
747          */
748         if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
749                 return -EINVAL;
750         }
751         strlcat(_converted_buf, "/", buflen);
752         strlcat(_converted_buf, trimmed, buflen);
753
754         return 0;
755 }
756
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)
762 {
763         int ret;
764         time_t timestamp_src, timestamp_dst;
765
766         ret = ceph_snap_gmt_strip_snapshot(handle,
767                                         smb_fname_src,
768                                         &timestamp_src, NULL, 0);
769         if (ret < 0) {
770                 errno = -ret;
771                 return -1;
772         }
773         ret = ceph_snap_gmt_strip_snapshot(handle,
774                                         smb_fname_dst,
775                                         &timestamp_dst, NULL, 0);
776         if (ret < 0) {
777                 errno = -ret;
778                 return -1;
779         }
780         if (timestamp_src != 0) {
781                 errno = EXDEV;
782                 return -1;
783         }
784         if (timestamp_dst != 0) {
785                 errno = EROFS;
786                 return -1;
787         }
788         return SMB_VFS_NEXT_RENAMEAT(handle,
789                                 srcfsp,
790                                 smb_fname_src,
791                                 dstfsp,
792                                 smb_fname_dst);
793 }
794
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)
800 {
801         int ret;
802         time_t timestamp_old = 0;
803         time_t timestamp_new = 0;
804
805         ret = ceph_snap_gmt_strip_snapshot(handle,
806                                 link_contents,
807                                 &timestamp_old,
808                                 NULL, 0);
809         if (ret < 0) {
810                 errno = -ret;
811                 return -1;
812         }
813         ret = ceph_snap_gmt_strip_snapshot(handle,
814                                 new_smb_fname,
815                                 &timestamp_new,
816                                 NULL, 0);
817         if (ret < 0) {
818                 errno = -ret;
819                 return -1;
820         }
821         if ((timestamp_old != 0) || (timestamp_new != 0)) {
822                 errno = EROFS;
823                 return -1;
824         }
825         return SMB_VFS_NEXT_SYMLINKAT(handle,
826                                 link_contents,
827                                 dirfsp,
828                                 new_smb_fname);
829 }
830
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,
836                                 int flags)
837 {
838         int ret;
839         time_t timestamp_old = 0;
840         time_t timestamp_new = 0;
841
842         ret = ceph_snap_gmt_strip_snapshot(handle,
843                                 old_smb_fname,
844                                 &timestamp_old,
845                                 NULL, 0);
846         if (ret < 0) {
847                 errno = -ret;
848                 return -1;
849         }
850         ret = ceph_snap_gmt_strip_snapshot(handle,
851                                 new_smb_fname,
852                                 &timestamp_new,
853                                 NULL, 0);
854         if (ret < 0) {
855                 errno = -ret;
856                 return -1;
857         }
858         if ((timestamp_old != 0) || (timestamp_new != 0)) {
859                 errno = EROFS;
860                 return -1;
861         }
862         return SMB_VFS_NEXT_LINKAT(handle,
863                         srcfsp,
864                         old_smb_fname,
865                         dstfsp,
866                         new_smb_fname,
867                         flags);
868 }
869
870 static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
871                             struct smb_filename *smb_fname)
872 {
873         time_t timestamp = 0;
874         char stripped[PATH_MAX + 1];
875         char conv[PATH_MAX + 1];
876         char *tmp;
877         int ret;
878
879         ret = ceph_snap_gmt_strip_snapshot(handle,
880                                         smb_fname,
881                                         &timestamp, stripped, sizeof(stripped));
882         if (ret < 0) {
883                 errno = -ret;
884                 return -1;
885         }
886         if (timestamp == 0) {
887                 return SMB_VFS_NEXT_STAT(handle, smb_fname);
888         }
889
890         ret = ceph_snap_gmt_convert(handle, stripped,
891                                         timestamp, conv, sizeof(conv));
892         if (ret < 0) {
893                 errno = -ret;
894                 return -1;
895         }
896         tmp = smb_fname->base_name;
897         smb_fname->base_name = conv;
898
899         ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
900         smb_fname->base_name = tmp;
901         return ret;
902 }
903
904 static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
905                              struct smb_filename *smb_fname)
906 {
907         time_t timestamp = 0;
908         char stripped[PATH_MAX + 1];
909         char conv[PATH_MAX + 1];
910         char *tmp;
911         int ret;
912
913         ret = ceph_snap_gmt_strip_snapshot(handle,
914                                         smb_fname,
915                                         &timestamp, stripped, sizeof(stripped));
916         if (ret < 0) {
917                 errno = -ret;
918                 return -1;
919         }
920         if (timestamp == 0) {
921                 return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
922         }
923
924         ret = ceph_snap_gmt_convert(handle, stripped,
925                                         timestamp, conv, sizeof(conv));
926         if (ret < 0) {
927                 errno = -ret;
928                 return -1;
929         }
930         tmp = smb_fname->base_name;
931         smb_fname->base_name = conv;
932
933         ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
934         smb_fname->base_name = tmp;
935         return ret;
936 }
937
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,
941                                 files_struct *fsp,
942                                 int flags,
943                                 mode_t mode)
944 {
945         time_t timestamp = 0;
946         struct smb_filename *smb_fname = NULL;
947         char stripped[PATH_MAX + 1];
948         char conv[PATH_MAX + 1];
949         int ret;
950         int saved_errno = 0;
951
952         ret = ceph_snap_gmt_strip_snapshot(handle,
953                                            smb_fname_in,
954                                            &timestamp,
955                                            stripped,
956                                            sizeof(stripped));
957         if (ret < 0) {
958                 errno = -ret;
959                 return -1;
960         }
961         if (timestamp == 0) {
962                 return SMB_VFS_NEXT_OPENAT(handle,
963                                            dirfsp,
964                                            smb_fname_in,
965                                            fsp,
966                                            flags,
967                                            mode);
968         }
969
970         ret = ceph_snap_gmt_convert(handle,
971                                     stripped,
972                                     timestamp,
973                                     conv,
974                                     sizeof(conv));
975         if (ret < 0) {
976                 errno = -ret;
977                 return -1;
978         }
979         smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
980         if (smb_fname == NULL) {
981                 return -1;
982         }
983         smb_fname->base_name = conv;
984
985         ret = SMB_VFS_NEXT_OPENAT(handle,
986                                   dirfsp,
987                                   smb_fname,
988                                   fsp,
989                                   flags,
990                                   mode);
991         if (ret == -1) {
992                 saved_errno = errno;
993         }
994         TALLOC_FREE(smb_fname);
995         if (saved_errno != 0) {
996                 errno = saved_errno;
997         }
998         return ret;
999 }
1000
1001 static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
1002                         struct files_struct *dirfsp,
1003                         const struct smb_filename *csmb_fname,
1004                         int flags)
1005 {
1006         time_t timestamp = 0;
1007         int ret;
1008
1009         ret = ceph_snap_gmt_strip_snapshot(handle,
1010                                         csmb_fname,
1011                                         &timestamp, NULL, 0);
1012         if (ret < 0) {
1013                 errno = -ret;
1014                 return -1;
1015         }
1016         if (timestamp != 0) {
1017                 errno = EROFS;
1018                 return -1;
1019         }
1020         return SMB_VFS_NEXT_UNLINKAT(handle,
1021                         dirfsp,
1022                         csmb_fname,
1023                         flags);
1024 }
1025
1026 static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
1027                                 struct files_struct *fsp,
1028                                 mode_t mode)
1029 {
1030         const struct smb_filename *csmb_fname = NULL;
1031         time_t timestamp = 0;
1032         int ret;
1033
1034         csmb_fname = fsp->fsp_name;
1035         ret = ceph_snap_gmt_strip_snapshot(handle,
1036                                         csmb_fname,
1037                                         &timestamp, NULL, 0);
1038         if (ret < 0) {
1039                 errno = -ret;
1040                 return -1;
1041         }
1042         if (timestamp != 0) {
1043                 errno = EROFS;
1044                 return -1;
1045         }
1046         return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
1047 }
1048
1049 static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
1050                         const struct smb_filename *csmb_fname)
1051 {
1052         time_t timestamp = 0;
1053         char stripped[PATH_MAX + 1];
1054         char conv[PATH_MAX + 1];
1055         int ret;
1056         struct smb_filename *new_fname;
1057         int saved_errno;
1058
1059         ret = ceph_snap_gmt_strip_snapshot(handle,
1060                                         csmb_fname,
1061                                         &timestamp, stripped, sizeof(stripped));
1062         if (ret < 0) {
1063                 errno = -ret;
1064                 return -1;
1065         }
1066         if (timestamp == 0) {
1067                 return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
1068         }
1069
1070         ret = ceph_snap_gmt_convert_dir(handle, stripped,
1071                                         timestamp, conv, sizeof(conv));
1072         if (ret < 0) {
1073                 errno = -ret;
1074                 return -1;
1075         }
1076         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1077         if (new_fname == NULL) {
1078                 errno = ENOMEM;
1079                 return -1;
1080         }
1081         new_fname->base_name = conv;
1082
1083         ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
1084         saved_errno = errno;
1085         TALLOC_FREE(new_fname);
1086         errno = saved_errno;
1087         return ret;
1088 }
1089
1090 static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
1091                                  files_struct *fsp,
1092                                  struct smb_file_time *ft)
1093 {
1094         time_t timestamp = 0;
1095         int ret;
1096
1097         ret = ceph_snap_gmt_strip_snapshot(handle,
1098                                            fsp->fsp_name,
1099                                            &timestamp,
1100                                            NULL,
1101                                            0);
1102         if (ret < 0) {
1103                 errno = -ret;
1104                 return -1;
1105         }
1106         if (timestamp != 0) {
1107                 errno = EROFS;
1108                 return -1;
1109         }
1110         return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
1111 }
1112
1113 static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
1114                                 const struct files_struct *dirfsp,
1115                                 const struct smb_filename *csmb_fname,
1116                                 char *buf,
1117                                 size_t bufsiz)
1118 {
1119         time_t timestamp = 0;
1120         char conv[PATH_MAX + 1];
1121         int ret;
1122         struct smb_filename *full_fname = NULL;
1123         int saved_errno;
1124
1125         /*
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.
1129          */
1130         ret = ceph_snap_gmt_strip_snapshot(handle,
1131                                         csmb_fname,
1132                                         &timestamp, NULL, 0);
1133         if (ret < 0) {
1134                 errno = -ret;
1135                 return -1;
1136         }
1137         if (timestamp == 0) {
1138                 return SMB_VFS_NEXT_READLINKAT(handle,
1139                                 dirfsp,
1140                                 csmb_fname,
1141                                 buf,
1142                                 bufsiz);
1143         }
1144
1145         full_fname = full_path_from_dirfsp_atname(talloc_tos(),
1146                                                 dirfsp,
1147                                                 csmb_fname);
1148         if (full_fname == NULL) {
1149                 return -1;
1150         }
1151
1152         /* Find the snapshot path from the full pathname. */
1153         ret = ceph_snap_gmt_convert(handle,
1154                                 full_fname->base_name,
1155                                 timestamp,
1156                                 conv,
1157                                 sizeof(conv));
1158         if (ret < 0) {
1159                 TALLOC_FREE(full_fname);
1160                 errno = -ret;
1161                 return -1;
1162         }
1163         full_fname->base_name = conv;
1164
1165         ret = SMB_VFS_NEXT_READLINKAT(handle,
1166                                 handle->conn->cwd_fsp,
1167                                 full_fname,
1168                                 buf,
1169                                 bufsiz);
1170         saved_errno = errno;
1171         TALLOC_FREE(full_fname);
1172         errno = saved_errno;
1173         return ret;
1174 }
1175
1176 static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
1177                         files_struct *dirfsp,
1178                         const struct smb_filename *csmb_fname,
1179                         mode_t mode,
1180                         SMB_DEV_T dev)
1181 {
1182         time_t timestamp = 0;
1183         int ret;
1184
1185         ret = ceph_snap_gmt_strip_snapshot(handle,
1186                                         csmb_fname,
1187                                         &timestamp, NULL, 0);
1188         if (ret < 0) {
1189                 errno = -ret;
1190                 return -1;
1191         }
1192         if (timestamp != 0) {
1193                 errno = EROFS;
1194                 return -1;
1195         }
1196         return SMB_VFS_NEXT_MKNODAT(handle,
1197                         dirfsp,
1198                         csmb_fname,
1199                         mode,
1200                         dev);
1201 }
1202
1203 static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
1204                                 TALLOC_CTX *ctx,
1205                                 const struct smb_filename *csmb_fname)
1206 {
1207         time_t timestamp = 0;
1208         char stripped[PATH_MAX + 1];
1209         char conv[PATH_MAX + 1];
1210         struct smb_filename *result_fname;
1211         int ret;
1212         struct smb_filename *new_fname;
1213         int saved_errno;
1214
1215         ret = ceph_snap_gmt_strip_snapshot(handle,
1216                                         csmb_fname,
1217                                         &timestamp, stripped, sizeof(stripped));
1218         if (ret < 0) {
1219                 errno = -ret;
1220                 return NULL;
1221         }
1222         if (timestamp == 0) {
1223                 return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
1224         }
1225         ret = ceph_snap_gmt_convert(handle, stripped,
1226                                         timestamp, conv, sizeof(conv));
1227         if (ret < 0) {
1228                 errno = -ret;
1229                 return NULL;
1230         }
1231         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1232         if (new_fname == NULL) {
1233                 errno = ENOMEM;
1234                 return NULL;
1235         }
1236         new_fname->base_name = conv;
1237
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;
1243 }
1244
1245 static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
1246                                 struct files_struct *dirfsp,
1247                                 const struct smb_filename *csmb_fname,
1248                                 mode_t mode)
1249 {
1250         time_t timestamp = 0;
1251         int ret;
1252
1253         ret = ceph_snap_gmt_strip_snapshot(handle,
1254                                         csmb_fname,
1255                                         &timestamp, NULL, 0);
1256         if (ret < 0) {
1257                 errno = -ret;
1258                 return -1;
1259         }
1260         if (timestamp != 0) {
1261                 errno = EROFS;
1262                 return -1;
1263         }
1264         return SMB_VFS_NEXT_MKDIRAT(handle,
1265                         dirfsp,
1266                         csmb_fname,
1267                         mode);
1268 }
1269
1270 static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
1271                                 struct files_struct *fsp,
1272                                 unsigned int flags)
1273 {
1274         time_t timestamp = 0;
1275         int ret;
1276
1277         ret = ceph_snap_gmt_strip_snapshot(handle,
1278                                         fsp->fsp_name,
1279                                         &timestamp, NULL, 0);
1280         if (ret < 0) {
1281                 errno = -ret;
1282                 return -1;
1283         }
1284         if (timestamp != 0) {
1285                 errno = EROFS;
1286                 return -1;
1287         }
1288         return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
1289 }
1290
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)
1295 {
1296         const struct smb_filename *csmb_fname = NULL;
1297         time_t timestamp = 0;
1298         int ret;
1299
1300         csmb_fname = fsp->fsp_name;
1301         ret = ceph_snap_gmt_strip_snapshot(handle,
1302                                         csmb_fname,
1303                                         &timestamp, NULL, 0);
1304         if (ret < 0) {
1305                 errno = -ret;
1306                 return -1;
1307         }
1308         if (timestamp != 0) {
1309                 errno = EROFS;
1310                 return -1;
1311         }
1312         return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
1313                                 aname, value, size, flags);
1314 }
1315
1316 static NTSTATUS ceph_snap_gmt_get_real_filename(
1317         struct vfs_handle_struct *handle,
1318         const struct smb_filename *path,
1319         const char *name,
1320         TALLOC_CTX *mem_ctx,
1321         char **found_name)
1322 {
1323         time_t timestamp = 0;
1324         char stripped[PATH_MAX + 1];
1325         char conv[PATH_MAX + 1];
1326         struct smb_filename conv_fname;
1327         int ret;
1328         NTSTATUS status;
1329
1330         ret = ceph_snap_gmt_strip_snapshot(handle, path,
1331                                         &timestamp, stripped, sizeof(stripped));
1332         if (ret < 0) {
1333                 return map_nt_error_from_unix(-ret);
1334         }
1335         if (timestamp == 0) {
1336                 return SMB_VFS_NEXT_GET_REAL_FILENAME(handle, path, name,
1337                                                       mem_ctx, found_name);
1338         }
1339         ret = ceph_snap_gmt_convert_dir(handle, stripped,
1340                                         timestamp, conv, sizeof(conv));
1341         if (ret < 0) {
1342                 return map_nt_error_from_unix(-ret);
1343         }
1344
1345         conv_fname = (struct smb_filename) {
1346                 .base_name = conv,
1347         };
1348
1349         status = SMB_VFS_NEXT_GET_REAL_FILENAME(
1350                 handle, &conv_fname, name, mem_ctx, found_name);
1351         return status;
1352 }
1353
1354 static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
1355                                 const struct smb_filename *csmb_fname,
1356                                 uint64_t *bsize,
1357                                 uint64_t *dfree,
1358                                 uint64_t *dsize)
1359 {
1360         time_t timestamp = 0;
1361         char stripped[PATH_MAX + 1];
1362         char conv[PATH_MAX + 1];
1363         int ret;
1364         struct smb_filename *new_fname;
1365         int saved_errno;
1366
1367         ret = ceph_snap_gmt_strip_snapshot(handle,
1368                                         csmb_fname,
1369                                         &timestamp, stripped, sizeof(stripped));
1370         if (ret < 0) {
1371                 errno = -ret;
1372                 return -1;
1373         }
1374         if (timestamp == 0) {
1375                 return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
1376                                               bsize, dfree, dsize);
1377         }
1378         ret = ceph_snap_gmt_convert(handle, stripped,
1379                                         timestamp, conv, sizeof(conv));
1380         if (ret < 0) {
1381                 errno = -ret;
1382                 return -1;
1383         }
1384         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1385         if (new_fname == NULL) {
1386                 errno = ENOMEM;
1387                 return -1;
1388         }
1389         new_fname->base_name = conv;
1390
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;
1396         return ret;
1397 }
1398
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,
1402                         unid_t id,
1403                         SMB_DISK_QUOTA *dq)
1404 {
1405         time_t timestamp = 0;
1406         char stripped[PATH_MAX + 1];
1407         char conv[PATH_MAX + 1];
1408         int ret;
1409         struct smb_filename *new_fname;
1410         int saved_errno;
1411
1412         ret = ceph_snap_gmt_strip_snapshot(handle,
1413                                         csmb_fname,
1414                                         &timestamp, stripped, sizeof(stripped));
1415         if (ret < 0) {
1416                 errno = -ret;
1417                 return -1;
1418         }
1419         if (timestamp == 0) {
1420                 return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
1421         }
1422         ret = ceph_snap_gmt_convert(handle, stripped,
1423                                         timestamp, conv, sizeof(conv));
1424         if (ret < 0) {
1425                 errno = -ret;
1426                 return -1;
1427         }
1428         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1429         if (new_fname == NULL) {
1430                 errno = ENOMEM;
1431                 return -1;
1432         }
1433         new_fname->base_name = conv;
1434
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;
1439         return ret;
1440 }
1441
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,
1465 };
1466
1467 static_decl_vfs;
1468 NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
1469 {
1470         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
1471                                 "ceph_snapshots", &ceph_snap_fns);
1472 }