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