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