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