smbd: Remove the offset argument from ReadDirName()
[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         NTSTATUS status;
225         int ret;
226         uint32_t slots;
227
228         DBG_DEBUG("enumerating shadow copy dir at %s\n",
229                   snaps_dname->base_name);
230
231         /*
232          * CephFS stat(dir).size *normally* returns the number of child entries
233          * for a given dir, but it unfortunately that's not the case for the one
234          * place we need it (dir=.snap), so we need to dynamically determine it
235          * via readdir.
236          */
237
238         status = OpenDir(frame,
239                          handle->conn,
240                          snaps_dname,
241                          NULL,
242                          0,
243                          &dir_hnd);
244         if (!NT_STATUS_IS_OK(status)) {
245                 ret = -map_errno_from_nt_status(status);
246                 goto err_out;
247         }
248
249         /* Check we have SEC_DIR_LIST access on this fsp. */
250         dirfsp = dir_hnd_fetch_fsp(dir_hnd);
251         status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
252                                               dirfsp,
253                                               false,
254                                               SEC_DIR_LIST);
255         if (!NT_STATUS_IS_OK(status)) {
256                 DBG_ERR("user does not have list permission "
257                         "on snapdir %s\n",
258                         fsp_str_dbg(dirfsp));
259                 ret = -map_errno_from_nt_status(status);
260                 goto err_out;
261         }
262
263         slots = 0;
264         sc_data->num_volumes = 0;
265         sc_data->labels = NULL;
266
267         while ((dname = ReadDirName(dir_hnd, NULL, &talloced))
268                != NULL)
269         {
270                 if (ISDOT(dname) || ISDOTDOT(dname)) {
271                         TALLOC_FREE(talloced);
272                         continue;
273                 }
274                 sc_data->num_volumes++;
275                 if (!labels) {
276                         TALLOC_FREE(talloced);
277                         continue;
278                 }
279                 if (sc_data->num_volumes > slots) {
280                         uint32_t new_slot_count = slots + 10;
281                         SMB_ASSERT(new_slot_count > slots);
282                         sc_data->labels = talloc_realloc(sc_data,
283                                                          sc_data->labels,
284                                                          SHADOW_COPY_LABEL,
285                                                          new_slot_count);
286                         if (sc_data->labels == NULL) {
287                                 TALLOC_FREE(talloced);
288                                 ret = -ENOMEM;
289                                 goto err_closedir;
290                         }
291                         memset(sc_data->labels[slots], 0,
292                                sizeof(SHADOW_COPY_LABEL) * 10);
293
294                         DBG_DEBUG("%d->%d slots for enum_snaps response\n",
295                                   slots, new_slot_count);
296                         slots = new_slot_count;
297                 }
298                 DBG_DEBUG("filling shadow copy label for %s/%s\n",
299                           snaps_dname->base_name, dname);
300                 ret = ceph_snap_fill_label(handle,
301                                 snaps_dname,
302                                 dirfsp,
303                                 dname,
304                                 sc_data->labels[sc_data->num_volumes - 1]);
305                 if (ret < 0) {
306                         TALLOC_FREE(talloced);
307                         goto err_closedir;
308                 }
309                 TALLOC_FREE(talloced);
310         }
311
312         DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
313                   snaps_dname->base_name, sc_data->num_volumes);
314
315         TALLOC_FREE(frame);
316         return 0;
317
318 err_closedir:
319         TALLOC_FREE(frame);
320 err_out:
321         TALLOC_FREE(sc_data->labels);
322         return ret;
323 }
324
325 /*
326  * Prior reading: The Meaning of Path Names
327  *   https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
328  *
329  * translate paths so that we can use the parent dir for .snap access:
330  *   myfile        -> parent=        trimmed=myfile
331  *   /a            -> parent=/       trimmed=a
332  *   dir/sub/file  -> parent=dir/sub trimmed=file
333  *   /dir/sub      -> parent=/dir/   trimmed=sub
334  */
335 static int ceph_snap_get_parent_path(const char *connectpath,
336                                      const char *path,
337                                      char *_parent_buf,
338                                      size_t buflen,
339                                      const char **_trimmed)
340 {
341         const char *p;
342         size_t len;
343         int ret;
344
345         if (!strcmp(path, "/")) {
346                 DBG_ERR("can't go past root for %s .snap dir\n", path);
347                 return -EINVAL;
348         }
349
350         p = strrchr_m(path, '/'); /* Find final '/', if any */
351         if (p == NULL) {
352                 DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
353                 ret = strlcpy(_parent_buf, "", buflen);
354                 if (ret >= buflen) {
355                         return -EINVAL;
356                 }
357                 if (_trimmed != NULL) {
358                         *_trimmed = path;
359                 }
360                 return 0;
361         }
362
363         SMB_ASSERT(p >= path);
364         len = p - path;
365
366         ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
367         if (ret >= buflen) {
368                 return -EINVAL;
369         }
370
371         /* for absolute paths, check that we're not going outside the share */
372         if ((len > 0) && (_parent_buf[0] == '/')) {
373                 bool connectpath_match = false;
374                 size_t clen = strlen(connectpath);
375                 DBG_DEBUG("checking absolute path %s lies within share at %s\n",
376                           _parent_buf, connectpath);
377                 /* need to check for separator, to avoid /x/abcd vs /x/ab */
378                 connectpath_match = (strncmp(connectpath,
379                                         _parent_buf,
380                                         clen) == 0);
381                 if (!connectpath_match
382                  || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
383                         DBG_ERR("%s parent path is outside of share at %s\n",
384                                 _parent_buf, connectpath);
385                         return -EINVAL;
386                 }
387         }
388
389         if (_trimmed != NULL) {
390                 /*
391                  * point to path component which was trimmed from _parent_buf
392                  * excluding path separator.
393                  */
394                 *_trimmed = p + 1;
395         }
396
397         DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
398                   path, _parent_buf, p + 1);
399
400         return 0;
401 }
402
403 static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
404                                         struct files_struct *fsp,
405                                         struct shadow_copy_data *sc_data,
406                                         bool labels)
407 {
408         int ret;
409         TALLOC_CTX *tmp_ctx;
410         const char *parent_dir = NULL;
411         char tmp[PATH_MAX + 1];
412         char snaps_path[PATH_MAX + 1];
413         struct smb_filename *snaps_dname = NULL;
414         const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
415                                                    "ceph", "snapdir",
416                                                    CEPH_SNAP_SUBDIR_DEFAULT);
417
418         DBG_DEBUG("getting shadow copy data for %s\n",
419                   fsp->fsp_name->base_name);
420
421         tmp_ctx = talloc_new(fsp);
422         if (tmp_ctx == NULL) {
423                 ret = -ENOMEM;
424                 goto err_out;
425         }
426
427         if (sc_data == NULL) {
428                 ret = -EINVAL;
429                 goto err_out;
430         }
431
432         if (fsp->fsp_flags.is_directory) {
433                 parent_dir = fsp->fsp_name->base_name;
434         } else {
435                 ret = ceph_snap_get_parent_path(handle->conn->connectpath,
436                                                 fsp->fsp_name->base_name,
437                                                 tmp,
438                                                 sizeof(tmp),
439                                                 NULL);  /* trimmed */
440                 if (ret < 0) {
441                         goto err_out;
442                 }
443                 parent_dir = tmp;
444         }
445
446         if (strlen(parent_dir) == 0) {
447                 ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
448         } else {
449                 ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
450                                parent_dir, snapdir);
451         }
452         if (ret >= sizeof(snaps_path)) {
453                 ret = -EINVAL;
454                 goto err_out;
455         }
456
457         snaps_dname = synthetic_smb_fname(tmp_ctx,
458                                 snaps_path,
459                                 NULL,
460                                 NULL,
461                                 0,
462                                 fsp->fsp_name->flags);
463         if (snaps_dname == NULL) {
464                 ret = -ENOMEM;
465                 goto err_out;
466         }
467
468         ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
469         if (ret < 0) {
470                 goto err_out;
471         }
472
473         talloc_free(tmp_ctx);
474         return 0;
475
476 err_out:
477         talloc_free(tmp_ctx);
478         errno = -ret;
479         return -1;
480 }
481
482 static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
483                                          const struct smb_filename *smb_fname,
484                                          time_t *_timestamp,
485                                          char *_stripped_buf,
486                                          size_t buflen)
487 {
488         size_t len;
489
490         if (smb_fname->twrp == 0) {
491                 goto no_snapshot;
492         }
493
494         if (_stripped_buf != NULL) {
495                 len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
496                 if (len >= buflen) {
497                         return -ENAMETOOLONG;
498                 }
499         }
500
501         *_timestamp = nt_time_to_unix(smb_fname->twrp);
502         return 0;
503 no_snapshot:
504         *_timestamp = 0;
505         return 0;
506 }
507
508 static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
509                                      const char *name,
510                                      time_t timestamp,
511                                      char *_converted_buf,
512                                      size_t buflen)
513 {
514         int ret;
515         NTSTATUS status;
516         struct smb_Dir *dir_hnd = NULL;
517         struct files_struct *dirfsp = NULL;
518         const char *dname = NULL;
519         char *talloced = NULL;
520         struct smb_filename *snaps_dname = NULL;
521         const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
522                                                    "ceph", "snapdir",
523                                                    CEPH_SNAP_SUBDIR_DEFAULT);
524         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
525
526         if (tmp_ctx == NULL) {
527                 ret = -ENOMEM;
528                 goto err_out;
529         }
530
531         /*
532          * Temporally use the caller's return buffer for this.
533          */
534         if (strlen(name) == 0) {
535                 ret = strlcpy(_converted_buf, snapdir, buflen);
536         } else {
537                 ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
538         }
539         if (ret >= buflen) {
540                 ret = -EINVAL;
541                 goto err_out;
542         }
543
544         snaps_dname = synthetic_smb_fname(tmp_ctx,
545                                 _converted_buf,
546                                 NULL,
547                                 NULL,
548                                 0,
549                                 0);     /* XXX check? */
550         if (snaps_dname == NULL) {
551                 ret = -ENOMEM;
552                 goto err_out;
553         }
554
555         /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
556         ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
557         if (ret < 0) {
558                 ret = -errno;
559                 goto err_out;
560         }
561
562         DBG_DEBUG("enumerating shadow copy dir at %s\n",
563                   snaps_dname->base_name);
564
565         status = OpenDir(tmp_ctx,
566                          handle->conn,
567                          snaps_dname,
568                          NULL,
569                          0,
570                          &dir_hnd);
571         if (!NT_STATUS_IS_OK(status)) {
572                 ret = -map_errno_from_nt_status(status);
573                 goto err_out;
574         }
575
576         /* Check we have SEC_DIR_LIST access on this fsp. */
577         dirfsp = dir_hnd_fetch_fsp(dir_hnd);
578         status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
579                                               dirfsp,
580                                               false,
581                                               SEC_DIR_LIST);
582         if (!NT_STATUS_IS_OK(status)) {
583                 DBG_ERR("user does not have list permission "
584                         "on snapdir %s\n",
585                         fsp_str_dbg(dirfsp));
586                 ret = -map_errno_from_nt_status(status);
587                 goto err_out;
588         }
589
590         while ((dname = ReadDirName(dir_hnd, NULL, &talloced))
591                != NULL)
592         {
593                 struct smb_filename *smb_fname = NULL;
594                 struct smb_filename *atname = NULL;
595                 time_t snap_secs = 0;
596
597                 if (ISDOT(dname) || ISDOTDOT(dname)) {
598                         TALLOC_FREE(talloced);
599                         continue;
600                 }
601
602                 ret = snprintf(_converted_buf, buflen, "%s/%s",
603                                snaps_dname->base_name, dname);
604                 if (ret >= buflen) {
605                         ret = -EINVAL;
606                         goto err_out;
607                 }
608
609                 smb_fname = synthetic_smb_fname(tmp_ctx,
610                                                 _converted_buf,
611                                                 NULL,
612                                                 NULL,
613                                                 0,
614                                                 0);
615                 if (smb_fname == NULL) {
616                         ret = -ENOMEM;
617                         goto err_out;
618                 }
619
620                 ret = vfs_stat(handle->conn, smb_fname);
621                 if (ret < 0) {
622                         ret = -errno;
623                         TALLOC_FREE(smb_fname);
624                         goto err_out;
625                 }
626
627                 atname = synthetic_smb_fname(tmp_ctx,
628                                              dname,
629                                              NULL,
630                                              &smb_fname->st,
631                                              0,
632                                              0);
633                 if (atname == NULL) {
634                         TALLOC_FREE(smb_fname);
635                         ret = -ENOMEM;
636                         goto err_out;
637                 }
638
639                 status = openat_pathref_fsp(dirfsp, atname);
640                 if (!NT_STATUS_IS_OK(status)) {
641                         TALLOC_FREE(smb_fname);
642                         TALLOC_FREE(atname);
643                         ret = -map_errno_from_nt_status(status);
644                         goto err_out;
645                 }
646
647                 ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
648                 if (ret < 0) {
649                         TALLOC_FREE(smb_fname);
650                         TALLOC_FREE(atname);
651                         goto err_out;
652                 }
653
654                 TALLOC_FREE(smb_fname);
655                 TALLOC_FREE(atname);
656
657                 /*
658                  * check gmt_snap_time matches @timestamp
659                  */
660                 if (timestamp == snap_secs) {
661                         break;
662                 }
663                 DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
664                           handle->conn->connectpath, name, (long long)timestamp,
665                           dname, (long long)snap_secs);
666                 TALLOC_FREE(talloced);
667         }
668
669         if (dname == NULL) {
670                 DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
671                          handle->conn->connectpath, name, (long long)timestamp);
672                 ret = -ENOENT;
673                 goto err_out;
674         }
675
676         /* found, _converted_buf already contains path of interest */
677         DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
678                   handle->conn->connectpath, name, (long long)timestamp,
679                   _converted_buf);
680
681         TALLOC_FREE(talloced);
682         talloc_free(tmp_ctx);
683         return 0;
684
685 err_out:
686         TALLOC_FREE(talloced);
687         talloc_free(tmp_ctx);
688         return ret;
689 }
690
691 static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
692                                      const char *name,
693                                      time_t timestamp,
694                                      char *_converted_buf,
695                                      size_t buflen)
696 {
697         int ret;
698         char parent[PATH_MAX + 1];
699         const char *trimmed = NULL;
700         /*
701          * CephFS Snapshots for a given dir are nested under the ./.snap subdir
702          * *or* under ../.snap/dir (and subsequent parent dirs).
703          * Child dirs inherit snapshots created in parent dirs if the child
704          * exists at the time of snapshot creation.
705          *
706          * At this point we don't know whether @name refers to a file or dir, so
707          * first assume it's a dir (with a corresponding .snaps subdir)
708          */
709         ret = ceph_snap_gmt_convert_dir(handle,
710                                         name,
711                                         timestamp,
712                                         _converted_buf,
713                                         buflen);
714         if (ret >= 0) {
715                 /* all done: .snap subdir exists - @name is a dir */
716                 DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
717                 return ret;
718         }
719
720         /* @name/.snap access failed, attempt snapshot access via parent */
721         DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
722                   name);
723
724         ret = ceph_snap_get_parent_path(handle->conn->connectpath,
725                                         name,
726                                         parent,
727                                         sizeof(parent),
728                                         &trimmed);
729         if (ret < 0) {
730                 return ret;
731         }
732
733         ret = ceph_snap_gmt_convert_dir(handle,
734                                         parent,
735                                         timestamp,
736                                         _converted_buf,
737                                         buflen);
738         if (ret < 0) {
739                 return ret;
740         }
741
742         /*
743          * found snapshot via parent. Append the child path component
744          * that was trimmed... +1 for path separator + 1 for null termination.
745          */
746         if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
747                 return -EINVAL;
748         }
749         strlcat(_converted_buf, "/", buflen);
750         strlcat(_converted_buf, trimmed, buflen);
751
752         return 0;
753 }
754
755 static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
756                         files_struct *srcfsp,
757                         const struct smb_filename *smb_fname_src,
758                         files_struct *dstfsp,
759                         const struct smb_filename *smb_fname_dst)
760 {
761         int ret;
762         time_t timestamp_src, timestamp_dst;
763
764         ret = ceph_snap_gmt_strip_snapshot(handle,
765                                         smb_fname_src,
766                                         &timestamp_src, NULL, 0);
767         if (ret < 0) {
768                 errno = -ret;
769                 return -1;
770         }
771         ret = ceph_snap_gmt_strip_snapshot(handle,
772                                         smb_fname_dst,
773                                         &timestamp_dst, NULL, 0);
774         if (ret < 0) {
775                 errno = -ret;
776                 return -1;
777         }
778         if (timestamp_src != 0) {
779                 errno = EXDEV;
780                 return -1;
781         }
782         if (timestamp_dst != 0) {
783                 errno = EROFS;
784                 return -1;
785         }
786         return SMB_VFS_NEXT_RENAMEAT(handle,
787                                 srcfsp,
788                                 smb_fname_src,
789                                 dstfsp,
790                                 smb_fname_dst);
791 }
792
793 /* block links from writeable shares to snapshots for now, like other modules */
794 static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
795                                 const struct smb_filename *link_contents,
796                                 struct files_struct *dirfsp,
797                                 const struct smb_filename *new_smb_fname)
798 {
799         int ret;
800         time_t timestamp_old = 0;
801         time_t timestamp_new = 0;
802
803         ret = ceph_snap_gmt_strip_snapshot(handle,
804                                 link_contents,
805                                 &timestamp_old,
806                                 NULL, 0);
807         if (ret < 0) {
808                 errno = -ret;
809                 return -1;
810         }
811         ret = ceph_snap_gmt_strip_snapshot(handle,
812                                 new_smb_fname,
813                                 &timestamp_new,
814                                 NULL, 0);
815         if (ret < 0) {
816                 errno = -ret;
817                 return -1;
818         }
819         if ((timestamp_old != 0) || (timestamp_new != 0)) {
820                 errno = EROFS;
821                 return -1;
822         }
823         return SMB_VFS_NEXT_SYMLINKAT(handle,
824                                 link_contents,
825                                 dirfsp,
826                                 new_smb_fname);
827 }
828
829 static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
830                                 files_struct *srcfsp,
831                                 const struct smb_filename *old_smb_fname,
832                                 files_struct *dstfsp,
833                                 const struct smb_filename *new_smb_fname,
834                                 int flags)
835 {
836         int ret;
837         time_t timestamp_old = 0;
838         time_t timestamp_new = 0;
839
840         ret = ceph_snap_gmt_strip_snapshot(handle,
841                                 old_smb_fname,
842                                 &timestamp_old,
843                                 NULL, 0);
844         if (ret < 0) {
845                 errno = -ret;
846                 return -1;
847         }
848         ret = ceph_snap_gmt_strip_snapshot(handle,
849                                 new_smb_fname,
850                                 &timestamp_new,
851                                 NULL, 0);
852         if (ret < 0) {
853                 errno = -ret;
854                 return -1;
855         }
856         if ((timestamp_old != 0) || (timestamp_new != 0)) {
857                 errno = EROFS;
858                 return -1;
859         }
860         return SMB_VFS_NEXT_LINKAT(handle,
861                         srcfsp,
862                         old_smb_fname,
863                         dstfsp,
864                         new_smb_fname,
865                         flags);
866 }
867
868 static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
869                             struct smb_filename *smb_fname)
870 {
871         time_t timestamp = 0;
872         char stripped[PATH_MAX + 1];
873         char conv[PATH_MAX + 1];
874         char *tmp;
875         int ret;
876
877         ret = ceph_snap_gmt_strip_snapshot(handle,
878                                         smb_fname,
879                                         &timestamp, stripped, sizeof(stripped));
880         if (ret < 0) {
881                 errno = -ret;
882                 return -1;
883         }
884         if (timestamp == 0) {
885                 return SMB_VFS_NEXT_STAT(handle, smb_fname);
886         }
887
888         ret = ceph_snap_gmt_convert(handle, stripped,
889                                         timestamp, conv, sizeof(conv));
890         if (ret < 0) {
891                 errno = -ret;
892                 return -1;
893         }
894         tmp = smb_fname->base_name;
895         smb_fname->base_name = conv;
896
897         ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
898         smb_fname->base_name = tmp;
899         return ret;
900 }
901
902 static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
903                              struct smb_filename *smb_fname)
904 {
905         time_t timestamp = 0;
906         char stripped[PATH_MAX + 1];
907         char conv[PATH_MAX + 1];
908         char *tmp;
909         int ret;
910
911         ret = ceph_snap_gmt_strip_snapshot(handle,
912                                         smb_fname,
913                                         &timestamp, stripped, sizeof(stripped));
914         if (ret < 0) {
915                 errno = -ret;
916                 return -1;
917         }
918         if (timestamp == 0) {
919                 return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
920         }
921
922         ret = ceph_snap_gmt_convert(handle, stripped,
923                                         timestamp, conv, sizeof(conv));
924         if (ret < 0) {
925                 errno = -ret;
926                 return -1;
927         }
928         tmp = smb_fname->base_name;
929         smb_fname->base_name = conv;
930
931         ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
932         smb_fname->base_name = tmp;
933         return ret;
934 }
935
936 static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
937                                 const struct files_struct *dirfsp,
938                                 const struct smb_filename *smb_fname_in,
939                                 files_struct *fsp,
940                                 const struct vfs_open_how *how)
941 {
942         time_t timestamp = 0;
943         struct smb_filename *smb_fname = NULL;
944         char stripped[PATH_MAX + 1];
945         char conv[PATH_MAX + 1];
946         int ret;
947         int saved_errno = 0;
948
949         ret = ceph_snap_gmt_strip_snapshot(handle,
950                                            smb_fname_in,
951                                            &timestamp,
952                                            stripped,
953                                            sizeof(stripped));
954         if (ret < 0) {
955                 errno = -ret;
956                 return -1;
957         }
958         if (timestamp == 0) {
959                 return SMB_VFS_NEXT_OPENAT(handle,
960                                            dirfsp,
961                                            smb_fname_in,
962                                            fsp,
963                                            how);
964         }
965
966         ret = ceph_snap_gmt_convert(handle,
967                                     stripped,
968                                     timestamp,
969                                     conv,
970                                     sizeof(conv));
971         if (ret < 0) {
972                 errno = -ret;
973                 return -1;
974         }
975         smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
976         if (smb_fname == NULL) {
977                 return -1;
978         }
979         smb_fname->base_name = conv;
980
981         ret = SMB_VFS_NEXT_OPENAT(handle,
982                                   dirfsp,
983                                   smb_fname,
984                                   fsp,
985                                   how);
986         if (ret == -1) {
987                 saved_errno = errno;
988         }
989         TALLOC_FREE(smb_fname);
990         if (saved_errno != 0) {
991                 errno = saved_errno;
992         }
993         return ret;
994 }
995
996 static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
997                         struct files_struct *dirfsp,
998                         const struct smb_filename *csmb_fname,
999                         int flags)
1000 {
1001         time_t timestamp = 0;
1002         int ret;
1003
1004         ret = ceph_snap_gmt_strip_snapshot(handle,
1005                                         csmb_fname,
1006                                         &timestamp, NULL, 0);
1007         if (ret < 0) {
1008                 errno = -ret;
1009                 return -1;
1010         }
1011         if (timestamp != 0) {
1012                 errno = EROFS;
1013                 return -1;
1014         }
1015         return SMB_VFS_NEXT_UNLINKAT(handle,
1016                         dirfsp,
1017                         csmb_fname,
1018                         flags);
1019 }
1020
1021 static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
1022                                 struct files_struct *fsp,
1023                                 mode_t mode)
1024 {
1025         const struct smb_filename *csmb_fname = NULL;
1026         time_t timestamp = 0;
1027         int ret;
1028
1029         csmb_fname = fsp->fsp_name;
1030         ret = ceph_snap_gmt_strip_snapshot(handle,
1031                                         csmb_fname,
1032                                         &timestamp, NULL, 0);
1033         if (ret < 0) {
1034                 errno = -ret;
1035                 return -1;
1036         }
1037         if (timestamp != 0) {
1038                 errno = EROFS;
1039                 return -1;
1040         }
1041         return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
1042 }
1043
1044 static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
1045                         const struct smb_filename *csmb_fname)
1046 {
1047         time_t timestamp = 0;
1048         char stripped[PATH_MAX + 1];
1049         char conv[PATH_MAX + 1];
1050         int ret;
1051         struct smb_filename *new_fname;
1052         int saved_errno;
1053
1054         ret = ceph_snap_gmt_strip_snapshot(handle,
1055                                         csmb_fname,
1056                                         &timestamp, stripped, sizeof(stripped));
1057         if (ret < 0) {
1058                 errno = -ret;
1059                 return -1;
1060         }
1061         if (timestamp == 0) {
1062                 return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
1063         }
1064
1065         ret = ceph_snap_gmt_convert_dir(handle, stripped,
1066                                         timestamp, conv, sizeof(conv));
1067         if (ret < 0) {
1068                 errno = -ret;
1069                 return -1;
1070         }
1071         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1072         if (new_fname == NULL) {
1073                 errno = ENOMEM;
1074                 return -1;
1075         }
1076         new_fname->base_name = conv;
1077
1078         ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
1079         saved_errno = errno;
1080         TALLOC_FREE(new_fname);
1081         errno = saved_errno;
1082         return ret;
1083 }
1084
1085 static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
1086                                  files_struct *fsp,
1087                                  struct smb_file_time *ft)
1088 {
1089         time_t timestamp = 0;
1090         int ret;
1091
1092         ret = ceph_snap_gmt_strip_snapshot(handle,
1093                                            fsp->fsp_name,
1094                                            &timestamp,
1095                                            NULL,
1096                                            0);
1097         if (ret < 0) {
1098                 errno = -ret;
1099                 return -1;
1100         }
1101         if (timestamp != 0) {
1102                 errno = EROFS;
1103                 return -1;
1104         }
1105         return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
1106 }
1107
1108 static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
1109                                 const struct files_struct *dirfsp,
1110                                 const struct smb_filename *csmb_fname,
1111                                 char *buf,
1112                                 size_t bufsiz)
1113 {
1114         time_t timestamp = 0;
1115         char conv[PATH_MAX + 1];
1116         int ret;
1117         struct smb_filename *full_fname = NULL;
1118         int saved_errno;
1119
1120         /*
1121          * Now this function only looks at csmb_fname->twrp
1122          * we don't need to copy out the path. Just use
1123          * csmb_fname->base_name directly.
1124          */
1125         ret = ceph_snap_gmt_strip_snapshot(handle,
1126                                         csmb_fname,
1127                                         &timestamp, NULL, 0);
1128         if (ret < 0) {
1129                 errno = -ret;
1130                 return -1;
1131         }
1132         if (timestamp == 0) {
1133                 return SMB_VFS_NEXT_READLINKAT(handle,
1134                                 dirfsp,
1135                                 csmb_fname,
1136                                 buf,
1137                                 bufsiz);
1138         }
1139
1140         full_fname = full_path_from_dirfsp_atname(talloc_tos(),
1141                                                 dirfsp,
1142                                                 csmb_fname);
1143         if (full_fname == NULL) {
1144                 return -1;
1145         }
1146
1147         /* Find the snapshot path from the full pathname. */
1148         ret = ceph_snap_gmt_convert(handle,
1149                                 full_fname->base_name,
1150                                 timestamp,
1151                                 conv,
1152                                 sizeof(conv));
1153         if (ret < 0) {
1154                 TALLOC_FREE(full_fname);
1155                 errno = -ret;
1156                 return -1;
1157         }
1158         full_fname->base_name = conv;
1159
1160         ret = SMB_VFS_NEXT_READLINKAT(handle,
1161                                 handle->conn->cwd_fsp,
1162                                 full_fname,
1163                                 buf,
1164                                 bufsiz);
1165         saved_errno = errno;
1166         TALLOC_FREE(full_fname);
1167         errno = saved_errno;
1168         return ret;
1169 }
1170
1171 static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
1172                         files_struct *dirfsp,
1173                         const struct smb_filename *csmb_fname,
1174                         mode_t mode,
1175                         SMB_DEV_T dev)
1176 {
1177         time_t timestamp = 0;
1178         int ret;
1179
1180         ret = ceph_snap_gmt_strip_snapshot(handle,
1181                                         csmb_fname,
1182                                         &timestamp, NULL, 0);
1183         if (ret < 0) {
1184                 errno = -ret;
1185                 return -1;
1186         }
1187         if (timestamp != 0) {
1188                 errno = EROFS;
1189                 return -1;
1190         }
1191         return SMB_VFS_NEXT_MKNODAT(handle,
1192                         dirfsp,
1193                         csmb_fname,
1194                         mode,
1195                         dev);
1196 }
1197
1198 static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
1199                                 TALLOC_CTX *ctx,
1200                                 const struct smb_filename *csmb_fname)
1201 {
1202         time_t timestamp = 0;
1203         char stripped[PATH_MAX + 1];
1204         char conv[PATH_MAX + 1];
1205         struct smb_filename *result_fname;
1206         int ret;
1207         struct smb_filename *new_fname;
1208         int saved_errno;
1209
1210         ret = ceph_snap_gmt_strip_snapshot(handle,
1211                                         csmb_fname,
1212                                         &timestamp, stripped, sizeof(stripped));
1213         if (ret < 0) {
1214                 errno = -ret;
1215                 return NULL;
1216         }
1217         if (timestamp == 0) {
1218                 return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
1219         }
1220         ret = ceph_snap_gmt_convert(handle, stripped,
1221                                         timestamp, conv, sizeof(conv));
1222         if (ret < 0) {
1223                 errno = -ret;
1224                 return NULL;
1225         }
1226         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1227         if (new_fname == NULL) {
1228                 errno = ENOMEM;
1229                 return NULL;
1230         }
1231         new_fname->base_name = conv;
1232
1233         result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
1234         saved_errno = errno;
1235         TALLOC_FREE(new_fname);
1236         errno = saved_errno;
1237         return result_fname;
1238 }
1239
1240 static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
1241                                 struct files_struct *dirfsp,
1242                                 const struct smb_filename *csmb_fname,
1243                                 mode_t mode)
1244 {
1245         time_t timestamp = 0;
1246         int ret;
1247
1248         ret = ceph_snap_gmt_strip_snapshot(handle,
1249                                         csmb_fname,
1250                                         &timestamp, NULL, 0);
1251         if (ret < 0) {
1252                 errno = -ret;
1253                 return -1;
1254         }
1255         if (timestamp != 0) {
1256                 errno = EROFS;
1257                 return -1;
1258         }
1259         return SMB_VFS_NEXT_MKDIRAT(handle,
1260                         dirfsp,
1261                         csmb_fname,
1262                         mode);
1263 }
1264
1265 static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
1266                                 struct files_struct *fsp,
1267                                 unsigned int flags)
1268 {
1269         time_t timestamp = 0;
1270         int ret;
1271
1272         ret = ceph_snap_gmt_strip_snapshot(handle,
1273                                         fsp->fsp_name,
1274                                         &timestamp, NULL, 0);
1275         if (ret < 0) {
1276                 errno = -ret;
1277                 return -1;
1278         }
1279         if (timestamp != 0) {
1280                 errno = EROFS;
1281                 return -1;
1282         }
1283         return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
1284 }
1285
1286 static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
1287                                 struct files_struct *fsp,
1288                                 const char *aname, const void *value,
1289                                 size_t size, int flags)
1290 {
1291         const struct smb_filename *csmb_fname = NULL;
1292         time_t timestamp = 0;
1293         int ret;
1294
1295         csmb_fname = fsp->fsp_name;
1296         ret = ceph_snap_gmt_strip_snapshot(handle,
1297                                         csmb_fname,
1298                                         &timestamp, NULL, 0);
1299         if (ret < 0) {
1300                 errno = -ret;
1301                 return -1;
1302         }
1303         if (timestamp != 0) {
1304                 errno = EROFS;
1305                 return -1;
1306         }
1307         return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
1308                                 aname, value, size, flags);
1309 }
1310
1311 static NTSTATUS ceph_snap_gmt_get_real_filename_at(
1312         struct vfs_handle_struct *handle,
1313         struct files_struct *dirfsp,
1314         const char *name,
1315         TALLOC_CTX *mem_ctx,
1316         char **found_name)
1317 {
1318         time_t timestamp = 0;
1319         char stripped[PATH_MAX + 1];
1320         char conv[PATH_MAX + 1];
1321         struct smb_filename *conv_fname = NULL;
1322         int ret;
1323         NTSTATUS status;
1324
1325         ret = ceph_snap_gmt_strip_snapshot(
1326                 handle,
1327                 dirfsp->fsp_name,
1328                 &timestamp,
1329                 stripped,
1330                 sizeof(stripped));
1331         if (ret < 0) {
1332                 return map_nt_error_from_unix(-ret);
1333         }
1334         if (timestamp == 0) {
1335                 return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1336                         handle, dirfsp, name, mem_ctx, found_name);
1337         }
1338         ret = ceph_snap_gmt_convert_dir(handle, stripped,
1339                                         timestamp, conv, sizeof(conv));
1340         if (ret < 0) {
1341                 return map_nt_error_from_unix(-ret);
1342         }
1343
1344         status = synthetic_pathref(
1345                 talloc_tos(),
1346                 dirfsp->conn->cwd_fsp,
1347                 conv,
1348                 NULL,
1349                 NULL,
1350                 0,
1351                 0,
1352                 &conv_fname);
1353         if (!NT_STATUS_IS_OK(status)) {
1354                 return status;
1355         }
1356
1357         status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
1358                 handle, conv_fname->fsp, name, mem_ctx, found_name);
1359         TALLOC_FREE(conv_fname);
1360         return status;
1361 }
1362
1363 static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
1364                                 const struct smb_filename *csmb_fname,
1365                                 uint64_t *bsize,
1366                                 uint64_t *dfree,
1367                                 uint64_t *dsize)
1368 {
1369         time_t timestamp = 0;
1370         char stripped[PATH_MAX + 1];
1371         char conv[PATH_MAX + 1];
1372         int ret;
1373         struct smb_filename *new_fname;
1374         int saved_errno;
1375
1376         ret = ceph_snap_gmt_strip_snapshot(handle,
1377                                         csmb_fname,
1378                                         &timestamp, stripped, sizeof(stripped));
1379         if (ret < 0) {
1380                 errno = -ret;
1381                 return -1;
1382         }
1383         if (timestamp == 0) {
1384                 return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
1385                                               bsize, dfree, dsize);
1386         }
1387         ret = ceph_snap_gmt_convert(handle, stripped,
1388                                         timestamp, conv, sizeof(conv));
1389         if (ret < 0) {
1390                 errno = -ret;
1391                 return -1;
1392         }
1393         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1394         if (new_fname == NULL) {
1395                 errno = ENOMEM;
1396                 return -1;
1397         }
1398         new_fname->base_name = conv;
1399
1400         ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
1401                                 bsize, dfree, dsize);
1402         saved_errno = errno;
1403         TALLOC_FREE(new_fname);
1404         errno = saved_errno;
1405         return ret;
1406 }
1407
1408 static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
1409                         const struct smb_filename *csmb_fname,
1410                         enum SMB_QUOTA_TYPE qtype,
1411                         unid_t id,
1412                         SMB_DISK_QUOTA *dq)
1413 {
1414         time_t timestamp = 0;
1415         char stripped[PATH_MAX + 1];
1416         char conv[PATH_MAX + 1];
1417         int ret;
1418         struct smb_filename *new_fname;
1419         int saved_errno;
1420
1421         ret = ceph_snap_gmt_strip_snapshot(handle,
1422                                         csmb_fname,
1423                                         &timestamp, stripped, sizeof(stripped));
1424         if (ret < 0) {
1425                 errno = -ret;
1426                 return -1;
1427         }
1428         if (timestamp == 0) {
1429                 return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
1430         }
1431         ret = ceph_snap_gmt_convert(handle, stripped,
1432                                         timestamp, conv, sizeof(conv));
1433         if (ret < 0) {
1434                 errno = -ret;
1435                 return -1;
1436         }
1437         new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
1438         if (new_fname == NULL) {
1439                 errno = ENOMEM;
1440                 return -1;
1441         }
1442         new_fname->base_name = conv;
1443
1444         ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
1445         saved_errno = errno;
1446         TALLOC_FREE(new_fname);
1447         errno = saved_errno;
1448         return ret;
1449 }
1450
1451 static struct vfs_fn_pointers ceph_snap_fns = {
1452         .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
1453         .disk_free_fn = ceph_snap_gmt_disk_free,
1454         .get_quota_fn = ceph_snap_gmt_get_quota,
1455         .renameat_fn = ceph_snap_gmt_renameat,
1456         .linkat_fn = ceph_snap_gmt_linkat,
1457         .symlinkat_fn = ceph_snap_gmt_symlinkat,
1458         .stat_fn = ceph_snap_gmt_stat,
1459         .lstat_fn = ceph_snap_gmt_lstat,
1460         .openat_fn = ceph_snap_gmt_openat,
1461         .unlinkat_fn = ceph_snap_gmt_unlinkat,
1462         .fchmod_fn = ceph_snap_gmt_fchmod,
1463         .chdir_fn = ceph_snap_gmt_chdir,
1464         .fntimes_fn = ceph_snap_gmt_fntimes,
1465         .readlinkat_fn = ceph_snap_gmt_readlinkat,
1466         .mknodat_fn = ceph_snap_gmt_mknodat,
1467         .realpath_fn = ceph_snap_gmt_realpath,
1468         .mkdirat_fn = ceph_snap_gmt_mkdirat,
1469         .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
1470         .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
1471         .fsetxattr_fn = ceph_snap_gmt_fsetxattr,
1472         .fchflags_fn = ceph_snap_gmt_fchflags,
1473         .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at,
1474 };
1475
1476 static_decl_vfs;
1477 NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
1478 {
1479         return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
1480                                 "ceph_snapshots", &ceph_snap_fns);
1481 }