/*
* 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 .
*/
#include
#include
#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 = synthetic_smb_fname(tmp_ctx,
snap_path,
NULL,
NULL,
0,
0);
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_snap_fill_label(handle,
snaps_dname,
dirfsp,
dname,
sc_data->labels[sc_data->num_volumes - 1]);
if (ret < 0) {
TALLOC_FREE(talloced);
goto err_closedir;
}
TALLOC_FREE(talloced);
}
DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
snaps_dname->base_name, sc_data->num_volumes);
TALLOC_FREE(frame);
return 0;
err_closedir:
TALLOC_FREE(frame);
err_out:
TALLOC_FREE(sc_data->labels);
return ret;
}
/*
* Prior reading: The Meaning of Path Names
* https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
*
* translate paths so that we can use the parent dir for .snap access:
* myfile -> parent= trimmed=myfile
* /a -> parent=/ trimmed=a
* dir/sub/file -> parent=dir/sub trimmed=file
* /dir/sub -> parent=/dir/ trimmed=sub
*/
static int ceph_snap_get_parent_path(const char *connectpath,
const char *path,
char *_parent_buf,
size_t buflen,
const char **_trimmed)
{
const char *p;
size_t len;
int ret;
if (!strcmp(path, "/")) {
DBG_ERR("can't go past root for %s .snap dir\n", path);
return -EINVAL;
}
p = strrchr_m(path, '/'); /* Find final '/', if any */
if (p == NULL) {
DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
ret = strlcpy(_parent_buf, "", buflen);
if (ret >= buflen) {
return -EINVAL;
}
if (_trimmed != NULL) {
*_trimmed = path;
}
return 0;
}
SMB_ASSERT(p >= path);
len = p - path;
ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
if (ret >= buflen) {
return -EINVAL;
}
/* for absolute paths, check that we're not going outside the share */
if ((len > 0) && (_parent_buf[0] == '/')) {
bool connectpath_match = false;
size_t clen = strlen(connectpath);
DBG_DEBUG("checking absolute path %s lies within share at %s\n",
_parent_buf, connectpath);
/* need to check for separator, to avoid /x/abcd vs /x/ab */
connectpath_match = (strncmp(connectpath,
_parent_buf,
clen) == 0);
if (!connectpath_match
|| ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
DBG_ERR("%s parent path is outside of share at %s\n",
_parent_buf, connectpath);
return -EINVAL;
}
}
if (_trimmed != NULL) {
/*
* point to path component which was trimmed from _parent_buf
* excluding path separator.
*/
*_trimmed = p + 1;
}
DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
path, _parent_buf, p + 1);
return 0;
}
static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
struct files_struct *fsp,
struct shadow_copy_data *sc_data,
bool labels)
{
int ret;
TALLOC_CTX *tmp_ctx;
const char *parent_dir = NULL;
char tmp[PATH_MAX + 1];
char snaps_path[PATH_MAX + 1];
struct smb_filename *snaps_dname = NULL;
const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
"ceph", "snapdir",
CEPH_SNAP_SUBDIR_DEFAULT);
DBG_DEBUG("getting shadow copy data for %s\n",
fsp->fsp_name->base_name);
tmp_ctx = talloc_new(fsp);
if (tmp_ctx == NULL) {
ret = -ENOMEM;
goto err_out;
}
if (sc_data == NULL) {
ret = -EINVAL;
goto err_out;
}
if (fsp->fsp_flags.is_directory) {
parent_dir = fsp->fsp_name->base_name;
} else {
ret = ceph_snap_get_parent_path(handle->conn->connectpath,
fsp->fsp_name->base_name,
tmp,
sizeof(tmp),
NULL); /* trimmed */
if (ret < 0) {
goto err_out;
}
parent_dir = tmp;
}
if (strlen(parent_dir) == 0) {
ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
} else {
ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
parent_dir, snapdir);
}
if (ret >= sizeof(snaps_path)) {
ret = -EINVAL;
goto err_out;
}
snaps_dname = synthetic_smb_fname(tmp_ctx,
snaps_path,
NULL,
NULL,
0,
fsp->fsp_name->flags);
if (snaps_dname == NULL) {
ret = -ENOMEM;
goto err_out;
}
ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
if (ret < 0) {
goto err_out;
}
talloc_free(tmp_ctx);
return 0;
err_out:
talloc_free(tmp_ctx);
errno = -ret;
return -1;
}
static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
const struct smb_filename *smb_fname,
time_t *_timestamp,
char *_stripped_buf,
size_t buflen)
{
size_t len;
if (smb_fname->twrp == 0) {
goto no_snapshot;
}
if (_stripped_buf != NULL) {
len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
if (len >= buflen) {
return -ENAMETOOLONG;
}
}
*_timestamp = nt_time_to_unix(smb_fname->twrp);
return 0;
no_snapshot:
*_timestamp = 0;
return 0;
}
static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
const char *name,
time_t timestamp,
char *_converted_buf,
size_t buflen)
{
int ret;
NTSTATUS status;
struct smb_Dir *dir_hnd = NULL;
struct files_struct *dirfsp = NULL;
const char *dname = NULL;
char *talloced = NULL;
struct smb_filename *snaps_dname = NULL;
const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
"ceph", "snapdir",
CEPH_SNAP_SUBDIR_DEFAULT);
TALLOC_CTX *tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
ret = -ENOMEM;
goto err_out;
}
/*
* Temporally use the caller's return buffer for this.
*/
if (strlen(name) == 0) {
ret = strlcpy(_converted_buf, snapdir, buflen);
} else {
ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
}
if (ret >= buflen) {
ret = -EINVAL;
goto err_out;
}
snaps_dname = synthetic_smb_fname(tmp_ctx,
_converted_buf,
NULL,
NULL,
0,
0); /* XXX check? */
if (snaps_dname == NULL) {
ret = -ENOMEM;
goto err_out;
}
/* stat first to trigger error fallback in ceph_snap_gmt_convert() */
ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
if (ret < 0) {
ret = -errno;
goto err_out;
}
DBG_DEBUG("enumerating shadow copy dir at %s\n",
snaps_dname->base_name);
status = OpenDir(tmp_ctx,
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;
}
while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
struct smb_filename *smb_fname = NULL;
struct smb_filename *atname = NULL;
time_t snap_secs = 0;
if (ISDOT(dname) || ISDOTDOT(dname)) {
TALLOC_FREE(talloced);
continue;
}
ret = snprintf(_converted_buf, buflen, "%s/%s",
snaps_dname->base_name, dname);
if (ret >= buflen) {
ret = -EINVAL;
goto err_out;
}
smb_fname = synthetic_smb_fname(tmp_ctx,
_converted_buf,
NULL,
NULL,
0,
0);
if (smb_fname == NULL) {
ret = -ENOMEM;
goto err_out;
}
ret = vfs_stat(handle->conn, smb_fname);
if (ret < 0) {
ret = -errno;
TALLOC_FREE(smb_fname);
goto err_out;
}
atname = synthetic_smb_fname(tmp_ctx,
dname,
NULL,
&smb_fname->st,
0,
0);
if (atname == NULL) {
TALLOC_FREE(smb_fname);
ret = -ENOMEM;
goto err_out;
}
status = openat_pathref_fsp(dirfsp, atname);
if (!NT_STATUS_IS_OK(status)) {
TALLOC_FREE(smb_fname);
TALLOC_FREE(atname);
ret = -map_errno_from_nt_status(status);
goto err_out;
}
ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
if (ret < 0) {
TALLOC_FREE(smb_fname);
TALLOC_FREE(atname);
goto err_out;
}
TALLOC_FREE(smb_fname);
TALLOC_FREE(atname);
/*
* check gmt_snap_time matches @timestamp
*/
if (timestamp == snap_secs) {
break;
}
DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
handle->conn->connectpath, name, (long long)timestamp,
dname, (long long)snap_secs);
TALLOC_FREE(talloced);
}
if (dname == NULL) {
DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
handle->conn->connectpath, name, (long long)timestamp);
ret = -ENOENT;
goto err_out;
}
/* found, _converted_buf already contains path of interest */
DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
handle->conn->connectpath, name, (long long)timestamp,
_converted_buf);
TALLOC_FREE(talloced);
talloc_free(tmp_ctx);
return 0;
err_out:
TALLOC_FREE(talloced);
talloc_free(tmp_ctx);
return ret;
}
static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
const char *name,
time_t timestamp,
char *_converted_buf,
size_t buflen)
{
int ret;
char parent[PATH_MAX + 1];
const char *trimmed = NULL;
/*
* CephFS Snapshots for a given dir are nested under the ./.snap subdir
* *or* under ../.snap/dir (and subsequent parent dirs).
* Child dirs inherit snapshots created in parent dirs if the child
* exists at the time of snapshot creation.
*
* At this point we don't know whether @name refers to a file or dir, so
* first assume it's a dir (with a corresponding .snaps subdir)
*/
ret = ceph_snap_gmt_convert_dir(handle,
name,
timestamp,
_converted_buf,
buflen);
if (ret >= 0) {
/* all done: .snap subdir exists - @name is a dir */
DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
return ret;
}
/* @name/.snap access failed, attempt snapshot access via parent */
DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
name);
ret = ceph_snap_get_parent_path(handle->conn->connectpath,
name,
parent,
sizeof(parent),
&trimmed);
if (ret < 0) {
return ret;
}
ret = ceph_snap_gmt_convert_dir(handle,
parent,
timestamp,
_converted_buf,
buflen);
if (ret < 0) {
return ret;
}
/*
* found snapshot via parent. Append the child path component
* that was trimmed... +1 for path separator + 1 for null termination.
*/
if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
return -EINVAL;
}
strlcat(_converted_buf, "/", buflen);
strlcat(_converted_buf, trimmed, buflen);
return 0;
}
static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
files_struct *srcfsp,
const struct smb_filename *smb_fname_src,
files_struct *dstfsp,
const struct smb_filename *smb_fname_dst)
{
int ret;
time_t timestamp_src, timestamp_dst;
ret = ceph_snap_gmt_strip_snapshot(handle,
smb_fname_src,
×tamp_src, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
ret = ceph_snap_gmt_strip_snapshot(handle,
smb_fname_dst,
×tamp_dst, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp_src != 0) {
errno = EXDEV;
return -1;
}
if (timestamp_dst != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_RENAMEAT(handle,
srcfsp,
smb_fname_src,
dstfsp,
smb_fname_dst);
}
/* block links from writeable shares to snapshots for now, like other modules */
static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
const struct smb_filename *link_contents,
struct files_struct *dirfsp,
const struct smb_filename *new_smb_fname)
{
int ret;
time_t timestamp_old = 0;
time_t timestamp_new = 0;
ret = ceph_snap_gmt_strip_snapshot(handle,
link_contents,
×tamp_old,
NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
ret = ceph_snap_gmt_strip_snapshot(handle,
new_smb_fname,
×tamp_new,
NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if ((timestamp_old != 0) || (timestamp_new != 0)) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_SYMLINKAT(handle,
link_contents,
dirfsp,
new_smb_fname);
}
static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
files_struct *srcfsp,
const struct smb_filename *old_smb_fname,
files_struct *dstfsp,
const struct smb_filename *new_smb_fname,
int flags)
{
int ret;
time_t timestamp_old = 0;
time_t timestamp_new = 0;
ret = ceph_snap_gmt_strip_snapshot(handle,
old_smb_fname,
×tamp_old,
NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
ret = ceph_snap_gmt_strip_snapshot(handle,
new_smb_fname,
×tamp_new,
NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if ((timestamp_old != 0) || (timestamp_new != 0)) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_LINKAT(handle,
srcfsp,
old_smb_fname,
dstfsp,
new_smb_fname,
flags);
}
static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
struct smb_filename *smb_fname)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
char *tmp;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
smb_fname,
×tamp, stripped, sizeof(stripped));
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_STAT(handle, smb_fname);
}
ret = ceph_snap_gmt_convert(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
errno = -ret;
return -1;
}
tmp = smb_fname->base_name;
smb_fname->base_name = conv;
ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
smb_fname->base_name = tmp;
return ret;
}
static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
struct smb_filename *smb_fname)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
char *tmp;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
smb_fname,
×tamp, stripped, sizeof(stripped));
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
}
ret = ceph_snap_gmt_convert(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
errno = -ret;
return -1;
}
tmp = smb_fname->base_name;
smb_fname->base_name = conv;
ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
smb_fname->base_name = tmp;
return ret;
}
static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
const struct files_struct *dirfsp,
const struct smb_filename *smb_fname_in,
files_struct *fsp,
const struct vfs_open_how *how)
{
time_t timestamp = 0;
struct smb_filename *smb_fname = NULL;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
int ret;
int saved_errno = 0;
ret = ceph_snap_gmt_strip_snapshot(handle,
smb_fname_in,
×tamp,
stripped,
sizeof(stripped));
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_OPENAT(handle,
dirfsp,
smb_fname_in,
fsp,
how);
}
ret = ceph_snap_gmt_convert(handle,
stripped,
timestamp,
conv,
sizeof(conv));
if (ret < 0) {
errno = -ret;
return -1;
}
smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
if (smb_fname == NULL) {
return -1;
}
smb_fname->base_name = conv;
ret = SMB_VFS_NEXT_OPENAT(handle,
dirfsp,
smb_fname,
fsp,
how);
if (ret == -1) {
saved_errno = errno;
}
TALLOC_FREE(smb_fname);
if (saved_errno != 0) {
errno = saved_errno;
}
return ret;
}
static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *csmb_fname,
int flags)
{
time_t timestamp = 0;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_UNLINKAT(handle,
dirfsp,
csmb_fname,
flags);
}
static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
struct files_struct *fsp,
mode_t mode)
{
const struct smb_filename *csmb_fname = NULL;
time_t timestamp = 0;
int ret;
csmb_fname = fsp->fsp_name;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
}
static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
const struct smb_filename *csmb_fname)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
int ret;
struct smb_filename *new_fname;
int saved_errno;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, stripped, sizeof(stripped));
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
}
ret = ceph_snap_gmt_convert_dir(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
errno = -ret;
return -1;
}
new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
if (new_fname == NULL) {
errno = ENOMEM;
return -1;
}
new_fname->base_name = conv;
ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
saved_errno = errno;
TALLOC_FREE(new_fname);
errno = saved_errno;
return ret;
}
static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
files_struct *fsp,
struct smb_file_time *ft)
{
time_t timestamp = 0;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
fsp->fsp_name,
×tamp,
NULL,
0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
}
static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
const struct files_struct *dirfsp,
const struct smb_filename *csmb_fname,
char *buf,
size_t bufsiz)
{
time_t timestamp = 0;
char conv[PATH_MAX + 1];
int ret;
struct smb_filename *full_fname = NULL;
int saved_errno;
/*
* Now this function only looks at csmb_fname->twrp
* we don't need to copy out the path. Just use
* csmb_fname->base_name directly.
*/
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_READLINKAT(handle,
dirfsp,
csmb_fname,
buf,
bufsiz);
}
full_fname = full_path_from_dirfsp_atname(talloc_tos(),
dirfsp,
csmb_fname);
if (full_fname == NULL) {
return -1;
}
/* Find the snapshot path from the full pathname. */
ret = ceph_snap_gmt_convert(handle,
full_fname->base_name,
timestamp,
conv,
sizeof(conv));
if (ret < 0) {
TALLOC_FREE(full_fname);
errno = -ret;
return -1;
}
full_fname->base_name = conv;
ret = SMB_VFS_NEXT_READLINKAT(handle,
handle->conn->cwd_fsp,
full_fname,
buf,
bufsiz);
saved_errno = errno;
TALLOC_FREE(full_fname);
errno = saved_errno;
return ret;
}
static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
files_struct *dirfsp,
const struct smb_filename *csmb_fname,
mode_t mode,
SMB_DEV_T dev)
{
time_t timestamp = 0;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_MKNODAT(handle,
dirfsp,
csmb_fname,
mode,
dev);
}
static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
TALLOC_CTX *ctx,
const struct smb_filename *csmb_fname)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
struct smb_filename *result_fname;
int ret;
struct smb_filename *new_fname;
int saved_errno;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, stripped, sizeof(stripped));
if (ret < 0) {
errno = -ret;
return NULL;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
}
ret = ceph_snap_gmt_convert(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
errno = -ret;
return NULL;
}
new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
if (new_fname == NULL) {
errno = ENOMEM;
return NULL;
}
new_fname->base_name = conv;
result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
saved_errno = errno;
TALLOC_FREE(new_fname);
errno = saved_errno;
return result_fname;
}
static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
struct files_struct *dirfsp,
const struct smb_filename *csmb_fname,
mode_t mode)
{
time_t timestamp = 0;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_MKDIRAT(handle,
dirfsp,
csmb_fname,
mode);
}
static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
struct files_struct *fsp,
unsigned int flags)
{
time_t timestamp = 0;
int ret;
ret = ceph_snap_gmt_strip_snapshot(handle,
fsp->fsp_name,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
}
static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
struct files_struct *fsp,
const char *aname, const void *value,
size_t size, int flags)
{
const struct smb_filename *csmb_fname = NULL;
time_t timestamp = 0;
int ret;
csmb_fname = fsp->fsp_name;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, NULL, 0);
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp != 0) {
errno = EROFS;
return -1;
}
return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
aname, value, size, flags);
}
static NTSTATUS ceph_snap_gmt_get_real_filename_at(
struct vfs_handle_struct *handle,
struct files_struct *dirfsp,
const char *name,
TALLOC_CTX *mem_ctx,
char **found_name)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
struct smb_filename *conv_fname = NULL;
int ret;
NTSTATUS status;
ret = ceph_snap_gmt_strip_snapshot(
handle,
dirfsp->fsp_name,
×tamp,
stripped,
sizeof(stripped));
if (ret < 0) {
return map_nt_error_from_unix(-ret);
}
if (timestamp == 0) {
return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
handle, dirfsp, name, mem_ctx, found_name);
}
ret = ceph_snap_gmt_convert_dir(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
return map_nt_error_from_unix(-ret);
}
status = synthetic_pathref(
talloc_tos(),
dirfsp->conn->cwd_fsp,
conv,
NULL,
NULL,
0,
0,
&conv_fname);
if (!NT_STATUS_IS_OK(status)) {
return status;
}
status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
handle, conv_fname->fsp, name, mem_ctx, found_name);
TALLOC_FREE(conv_fname);
return status;
}
static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
const struct smb_filename *csmb_fname,
uint64_t *bsize,
uint64_t *dfree,
uint64_t *dsize)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
int ret;
struct smb_filename *new_fname;
int saved_errno;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, stripped, sizeof(stripped));
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
bsize, dfree, dsize);
}
ret = ceph_snap_gmt_convert(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
errno = -ret;
return -1;
}
new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
if (new_fname == NULL) {
errno = ENOMEM;
return -1;
}
new_fname->base_name = conv;
ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
bsize, dfree, dsize);
saved_errno = errno;
TALLOC_FREE(new_fname);
errno = saved_errno;
return ret;
}
static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
const struct smb_filename *csmb_fname,
enum SMB_QUOTA_TYPE qtype,
unid_t id,
SMB_DISK_QUOTA *dq)
{
time_t timestamp = 0;
char stripped[PATH_MAX + 1];
char conv[PATH_MAX + 1];
int ret;
struct smb_filename *new_fname;
int saved_errno;
ret = ceph_snap_gmt_strip_snapshot(handle,
csmb_fname,
×tamp, stripped, sizeof(stripped));
if (ret < 0) {
errno = -ret;
return -1;
}
if (timestamp == 0) {
return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
}
ret = ceph_snap_gmt_convert(handle, stripped,
timestamp, conv, sizeof(conv));
if (ret < 0) {
errno = -ret;
return -1;
}
new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
if (new_fname == NULL) {
errno = ENOMEM;
return -1;
}
new_fname->base_name = conv;
ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
saved_errno = errno;
TALLOC_FREE(new_fname);
errno = saved_errno;
return ret;
}
static struct vfs_fn_pointers ceph_snap_fns = {
.get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
.disk_free_fn = ceph_snap_gmt_disk_free,
.get_quota_fn = ceph_snap_gmt_get_quota,
.renameat_fn = ceph_snap_gmt_renameat,
.linkat_fn = ceph_snap_gmt_linkat,
.symlinkat_fn = ceph_snap_gmt_symlinkat,
.stat_fn = ceph_snap_gmt_stat,
.lstat_fn = ceph_snap_gmt_lstat,
.openat_fn = ceph_snap_gmt_openat,
.unlinkat_fn = ceph_snap_gmt_unlinkat,
.fchmod_fn = ceph_snap_gmt_fchmod,
.chdir_fn = ceph_snap_gmt_chdir,
.fntimes_fn = ceph_snap_gmt_fntimes,
.readlinkat_fn = ceph_snap_gmt_readlinkat,
.mknodat_fn = ceph_snap_gmt_mknodat,
.realpath_fn = ceph_snap_gmt_realpath,
.mkdirat_fn = ceph_snap_gmt_mkdirat,
.getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
.getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
.fsetxattr_fn = ceph_snap_gmt_fsetxattr,
.fchflags_fn = ceph_snap_gmt_fchflags,
.get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at,
};
static_decl_vfs;
NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
{
return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
"ceph_snapshots", &ceph_snap_fns);
}