/*
* Module for accessing CephFS snapshots as Previous Versions. This module is
* separate to vfs_ceph, so that it can also be used atop a CephFS kernel backed
* share with vfs_default.
*
* Copyright (C) David Disseldorp 2019
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <dirent.h>
#include <libgen.h>
#include "includes.h"
#include "include/ntioctl.h"
#include "include/smb.h"
#include "system/filesys.h"
#include "smbd/smbd.h"
#include "lib/util/tevent_ntstatus.h"
#include "lib/util/smb_strtox.h"
#include "source3/smbd/dir.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_VFS
/*
* CephFS has a magic snapshots subdirectory in all parts of the directory tree.
* This module automatically makes all snapshots in this subdir visible to SMB
* clients (if permitted by corresponding access control).
*/
#define CEPH_SNAP_SUBDIR_DEFAULT ".snap"
/*
* The ceph.snap.btime (virtual) extended attribute carries the snapshot
* creation time in $secs.$nsecs format. It was added as part of
* https://tracker.ceph.com/issues/38838. Running Samba atop old Ceph versions
* which don't provide this xattr will not be able to enumerate or access
* snapshots using this module. As an alternative, vfs_shadow_copy2 could be
* used instead, alongside special shadow:format snapshot directory names.
*/
#define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime"
static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle,
struct files_struct *fsp,
time_t *_snap_secs)
{
int ret;
char snap_btime[33];
char *s = NULL;
char *endptr = NULL;
struct timespec snap_timespec;
int err;
ret = SMB_VFS_NEXT_FGETXATTR(handle,
fsp,
CEPH_SNAP_BTIME_XATTR,
snap_btime,
sizeof(snap_btime));
if (ret < 0) {
DBG_ERR("failed to get %s xattr: %s\n",
CEPH_SNAP_BTIME_XATTR, strerror(errno));
return -errno;
}
if (ret == 0 || ret >= sizeof(snap_btime) - 1) {
return -EINVAL;
}
/* ensure zero termination */
snap_btime[ret] = '\0';
/* format is sec.nsec */
s = strchr(snap_btime, '.');
if (s == NULL) {
DBG_ERR("invalid %s xattr value: %s\n",
CEPH_SNAP_BTIME_XATTR, snap_btime);
return -EINVAL;
}
/* First component is seconds, extract it */
*s = '\0';
snap_timespec.tv_sec = smb_strtoull(snap_btime,
&endptr,
10,
&err,
SMB_STR_FULL_STR_CONV);
if (err != 0) {
return -err;
}
/* second component is nsecs */
s++;
snap_timespec.tv_nsec = smb_strtoul(s,
&endptr,
10,
&err,
SMB_STR_FULL_STR_CONV);
if (err != 0) {
return -err;
}
/*
* >> 30 is a rough divide by ~10**9. No need to be exact, as @GMT
* tokens only offer 1-second resolution (while twrp is nsec).
*/
*_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30);
return 0;
}
/*
* XXX Ceph snapshots can be created with sub-second granularity, which means
* that multiple snapshots may be mapped to the same @GMT- label.
*
* @this_label is a pre-zeroed buffer to be filled with a @GMT label
* @return 0 if label successfully filled or -errno on error.
*/
static int ceph_snap_fill_label(struct vfs_handle_struct *handle,
TALLOC_CTX *tmp_ctx,
struct files_struct *dirfsp,
const char *subdir,
SHADOW_COPY_LABEL this_label)
{
const char *parent_snapsdir = dirfsp->fsp_name->base_name;
struct smb_filename *smb_fname;
struct smb_filename *atname = NULL;
time_t snap_secs;
struct tm gmt_snap_time;
struct tm *tm_ret;
size_t str_sz;
char snap_path[PATH_MAX + 1];
int ret;
NTSTATUS status;
/*
* CephFS snapshot creation times are available via a special
* xattr - snapshot b/m/ctimes all match the snap source.
*/
ret = snprintf(snap_path, sizeof(snap_path), "%s/%s",
parent_snapsdir, subdir);
if (ret >= sizeof(snap_path)) {
return -EINVAL;
}
smb_fname = cp_smb_basename(tmp_ctx, snap_path);
if (smb_fname == NULL) {
return -ENOMEM;
}
ret = vfs_stat(handle->conn, smb_fname);
if (ret < 0) {
ret = -errno;
TALLOC_FREE(smb_fname);
return ret;
}
atname = synthetic_smb_fname(tmp_ctx,
subdir,
NULL,
&smb_fname->st,
0,
0);
if (atname == NULL) {
TALLOC_FREE(smb_fname);
return -ENOMEM;
}
status = openat_pathref_fsp(dirfsp, atname);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(smb_fname);
TALLOC_FREE(atname);
return -map_errno_from_nt_status(status);
}
ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
if (ret < 0) {
TALLOC_FREE(smb_fname);
TALLOC_FREE(atname);
return ret;
}
TALLOC_FREE(smb_fname);
TALLOC_FREE(atname);
tm_ret = gmtime_r(&snap_secs, &gmt_snap_time);
if (tm_ret == NULL) {
return -EINVAL;
}
str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL),
"@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time);
if (str_sz == 0) {
DBG_ERR("failed to convert tm to @GMT token\n");
return -EINVAL;
}
DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n",
snap_path, this_label);
return 0;
}
static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle,
struct smb_filename *snaps_dname,
bool labels,
struct shadow_copy_data *sc_data)
{
TALLOC_CTX *frame = talloc_stackframe();
struct smb_Dir *dir_hnd = NULL;
struct files_struct *dirfsp = NULL;
const char *dname = NULL;
char *talloced = NULL;
NTSTATUS status;
int ret;
uint32_t slots;
DBG_DEBUG("enumerating shadow copy dir at %s\n",
snaps_dname->base_name);
/*
* CephFS stat(dir).size *normally* returns the number of child entries
* for a given dir, but it unfortunately that's not the case for the one
* place we need it (dir=.snap), so we need to dynamically determine it
* via readdir.
*/
status = OpenDir(frame,
handle->conn,
snaps_dname,
NULL,
0,
&dir_hnd);
if (!NT_STATUS_IS_OK(status)) {
ret = -map_errno_from_nt_status(status);
goto err_out;
}
/* Check we have SEC_DIR_LIST access on this fsp. */
dirfsp = dir_hnd_fetch_fsp(dir_hnd);
status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
dirfsp,
false,
SEC_DIR_LIST);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("user does not have list permission "
"on snapdir %s\n",
fsp_str_dbg(dirfsp));
ret = -map_errno_from_nt_status(status);
goto err_out;
}
slots = 0;
sc_data->num_volumes = 0;
sc_data->labels = NULL;
while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
if (ISDOT(dname) || ISDOTDOT(dname)) {
TALLOC_FREE(talloced);
continue;
}
sc_data->num_volumes++;
if (!labels) {
TALLOC_FREE(talloced);
continue;
}
if (sc_data->num_volumes > slots) {
uint32_t new_slot_count = slots + 10;
SMB_ASSERT(new_slot_count > slots);
sc_data->labels = talloc_realloc(sc_data,
sc_data->labels,
SHADOW_COPY_LABEL,
new_slot_count);
if (sc_data->labels == NULL) {
TALLOC_FREE(talloced);
ret = -ENOMEM;
goto err_closedir;
}
memset(sc_data->labels[slots], 0,
sizeof(SHADOW_COPY_LABEL) * 10);
DBG_DEBUG("%d->%d slots for enum_snaps response\n",
slots, new_slot_count);
slots = new_slot_count;
}
DBG_DEBUG("filling shadow copy label for %s/%s\n",
snaps_dname->base_name, dname);
ret = ceph_sn
|