smb: client: add support for WSL reparse points

Add support for creating special files via WSL reparse points when
using 'reparse=wsl' mount option.  They're faster than NFS reparse
points because they don't require extra roundtrips to figure out what
->d_type a specific dirent is as such information is already stored in
query dir responses and then making getdents() calls faster.

Signed-off-by: Paulo Alcantara <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Paulo Alcantara 2024-01-26 19:26:06 -03:00 committed by Steve French
parent fa792d8d23
commit 5a4b09ecf8
10 changed files with 210 additions and 20 deletions

View file

@ -1374,6 +1374,7 @@ struct cifs_open_parms {
umode_t mode;
bool reconnect:1;
bool replay:1; /* indicates that this open is for a replay */
struct kvec *ea_cctx;
};
struct cifs_fid {

View file

@ -317,8 +317,8 @@ static int parse_reparse_flavor(struct fs_context *fc, char *value,
ctx->reparse_type = CIFS_REPARSE_TYPE_NFS;
break;
case Opt_reparse_wsl:
cifs_errorf(fc, "unsupported reparse= option: %s\n", value);
return 1;
ctx->reparse_type = CIFS_REPARSE_TYPE_WSL;
break;
default:
cifs_errorf(fc, "bad reparse= option: %s\n", value);
return 1;

View file

@ -11,6 +11,7 @@
#include "cifsproto.h"
#include "cifs_unicode.h"
#include "cifs_debug.h"
#include "fs_context.h"
#include "reparse.h"
int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
@ -68,7 +69,7 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
iov.iov_base = buf;
iov.iov_len = len;
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
tcon, full_path, &iov);
tcon, full_path, &iov, NULL);
if (!IS_ERR(new))
d_instantiate(dentry, new);
else
@ -114,9 +115,9 @@ static int nfs_set_reparse_buf(struct reparse_posix_data *buf,
return 0;
}
int smb2_make_nfs_node(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
static int mknod_nfs(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
{
struct cifs_open_info_data data;
struct reparse_posix_data *p;
@ -136,7 +137,7 @@ int smb2_make_nfs_node(unsigned int xid, struct inode *inode,
};
new = smb2_get_reparse_inode(&data, inode->i_sb, xid,
tcon, full_path, &iov);
tcon, full_path, &iov, NULL);
if (!IS_ERR(new))
d_instantiate(dentry, new);
else
@ -145,6 +146,165 @@ int smb2_make_nfs_node(unsigned int xid, struct inode *inode,
return rc;
}
static int wsl_set_reparse_buf(struct reparse_data_buffer *buf,
mode_t mode, struct kvec *iov)
{
u32 tag;
switch ((tag = reparse_mode_wsl_tag(mode))) {
case IO_REPARSE_TAG_LX_BLK:
case IO_REPARSE_TAG_LX_CHR:
case IO_REPARSE_TAG_LX_FIFO:
case IO_REPARSE_TAG_AF_UNIX:
break;
default:
return -EOPNOTSUPP;
}
buf->ReparseTag = cpu_to_le32(tag);
buf->Reserved = 0;
buf->ReparseDataLength = 0;
iov->iov_base = buf;
iov->iov_len = sizeof(*buf);
return 0;
}
static struct smb2_create_ea_ctx *ea_create_context(u32 dlen, size_t *cc_len)
{
struct smb2_create_ea_ctx *cc;
*cc_len = round_up(sizeof(*cc) + dlen, 8);
cc = kzalloc(*cc_len, GFP_KERNEL);
if (!cc)
return ERR_PTR(-ENOMEM);
cc->ctx.NameOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx,
name));
cc->ctx.NameLength = cpu_to_le16(4);
memcpy(cc->name, SMB2_CREATE_EA_BUFFER, strlen(SMB2_CREATE_EA_BUFFER));
cc->ctx.DataOffset = cpu_to_le16(offsetof(struct smb2_create_ea_ctx, ea));
cc->ctx.DataLength = cpu_to_le32(dlen);
return cc;
}
struct wsl_xattr {
const char *name;
__le64 value;
u16 size;
u32 next;
};
static int wsl_set_xattrs(struct inode *inode, umode_t _mode,
dev_t _dev, struct kvec *iov)
{
struct smb2_file_full_ea_info *ea;
struct smb2_create_ea_ctx *cc;
struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx;
__le64 uid = cpu_to_le64(from_kuid(current_user_ns(), ctx->linux_uid));
__le64 gid = cpu_to_le64(from_kgid(current_user_ns(), ctx->linux_gid));
__le64 dev = cpu_to_le64(((u64)MINOR(_dev) << 32) | MAJOR(_dev));
__le64 mode = cpu_to_le64(_mode);
struct wsl_xattr xattrs[] = {
{ .name = "$LXUID", .value = uid, .size = 4, },
{ .name = "$LXGID", .value = gid, .size = 4, },
{ .name = "$LXMOD", .value = mode, .size = 4, },
{ .name = "$LXDEV", .value = dev, .size = 8, },
};
size_t cc_len;
u32 dlen = 0, next = 0;
int i, num_xattrs;
u8 name_size = strlen(xattrs[0].name) + 1;
memset(iov, 0, sizeof(*iov));
/* Exclude $LXDEV xattr for sockets and fifos */
if (S_ISSOCK(_mode) || S_ISFIFO(_mode))
num_xattrs = ARRAY_SIZE(xattrs) - 1;
else
num_xattrs = ARRAY_SIZE(xattrs);
for (i = 0; i < num_xattrs; i++) {
xattrs[i].next = ALIGN(sizeof(*ea) + name_size +
xattrs[i].size, 4);
dlen += xattrs[i].next;
}
cc = ea_create_context(dlen, &cc_len);
if (!cc)
return PTR_ERR(cc);
ea = &cc->ea;
for (i = 0; i < num_xattrs; i++) {
ea = (void *)((u8 *)ea + next);
next = xattrs[i].next;
ea->next_entry_offset = cpu_to_le32(next);
ea->ea_name_length = name_size - 1;
ea->ea_value_length = cpu_to_le16(xattrs[i].size);
memcpy(ea->ea_data, xattrs[i].name, name_size);
memcpy(&ea->ea_data[name_size],
&xattrs[i].value, xattrs[i].size);
}
ea->next_entry_offset = 0;
iov->iov_base = cc;
iov->iov_len = cc_len;
return 0;
}
static int mknod_wsl(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
{
struct cifs_open_info_data data;
struct reparse_data_buffer buf;
struct inode *new;
struct kvec reparse_iov, xattr_iov;
int rc;
rc = wsl_set_reparse_buf(&buf, mode, &reparse_iov);
if (rc)
return rc;
rc = wsl_set_xattrs(inode, mode, dev, &xattr_iov);
if (rc)
return rc;
data = (struct cifs_open_info_data) {
.reparse_point = true,
.reparse = { .tag = le32_to_cpu(buf.ReparseTag), .buf = &buf, },
};
new = smb2_get_reparse_inode(&data, inode->i_sb,
xid, tcon, full_path,
&reparse_iov, &xattr_iov);
if (!IS_ERR(new))
d_instantiate(dentry, new);
else
rc = PTR_ERR(new);
cifs_free_open_info(&data);
kfree(xattr_iov.iov_base);
return rc;
}
int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev)
{
struct smb3_fs_context *ctx = CIFS_SB(inode->i_sb)->ctx;
int rc = -EOPNOTSUPP;
switch (ctx->reparse_type) {
case CIFS_REPARSE_TYPE_NFS:
rc = mknod_nfs(xid, inode, dentry, tcon, full_path, mode, dev);
break;
case CIFS_REPARSE_TYPE_WSL:
rc = mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev);
break;
}
return rc;
}
/* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */
static int parse_reparse_posix(struct reparse_posix_data *buf,
struct cifs_sb_info *cifs_sb,

View file

@ -28,6 +28,17 @@ static inline u64 reparse_mode_nfs_type(mode_t mode)
return 0;
}
static inline u32 reparse_mode_wsl_tag(mode_t mode)
{
switch (mode & S_IFMT) {
case S_IFBLK: return IO_REPARSE_TAG_LX_BLK;
case S_IFCHR: return IO_REPARSE_TAG_LX_CHR;
case S_IFIFO: return IO_REPARSE_TAG_LX_FIFO;
case S_IFSOCK: return IO_REPARSE_TAG_AF_UNIX;
}
return 0;
}
/*
* Match a reparse point inode if reparse tag and ctime haven't changed.
*
@ -64,7 +75,7 @@ bool cifs_reparse_point_to_fattr(struct cifs_sb_info *cifs_sb,
int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, const char *symname);
int smb2_make_nfs_node(unsigned int xid, struct inode *inode,
int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
struct dentry *dentry, struct cifs_tcon *tcon,
const char *full_path, umode_t mode, dev_t dev);
int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov,

View file

@ -1060,7 +1060,8 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
const unsigned int xid,
struct cifs_tcon *tcon,
const char *full_path,
struct kvec *iov)
struct kvec *reparse_iov,
struct kvec *xattr_iov)
{
struct cifs_open_parms oparms;
struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
@ -1077,8 +1078,11 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
FILE_CREATE,
CREATE_NOT_DIR | OPEN_REPARSE_POINT,
ACL_NO_MODE);
if (xattr_iov)
oparms.ea_cctx = xattr_iov;
cmds[0] = SMB2_OP_SET_REPARSE;
in_iov[0] = *iov;
in_iov[0] = *reparse_iov;
in_iov[1].iov_base = data;
in_iov[1].iov_len = sizeof(*data);

View file

@ -5043,7 +5043,7 @@ static int smb2_make_node(unsigned int xid, struct inode *inode,
rc = cifs_sfu_make_node(xid, inode, dentry, tcon,
full_path, mode, dev);
} else {
rc = smb2_make_nfs_node(xid, inode, dentry, tcon,
rc = smb2_mknod_reparse(xid, inode, dentry, tcon,
full_path, mode, dev);
}
return rc;

View file

@ -2720,6 +2720,17 @@ add_query_id_context(struct kvec *iov, unsigned int *num_iovec)
return 0;
}
static void add_ea_context(struct cifs_open_parms *oparms,
struct kvec *rq_iov, unsigned int *num_iovs)
{
struct kvec *iov = oparms->ea_cctx;
if (iov && iov->iov_base && iov->iov_len) {
rq_iov[(*num_iovs)++] = *iov;
memset(iov, 0, sizeof(*iov));
}
}
static int
alloc_path_with_tree_prefix(__le16 **out_path, int *out_size, int *out_len,
const char *treename, const __le16 *path)
@ -3086,6 +3097,7 @@ SMB2_open_init(struct cifs_tcon *tcon, struct TCP_Server_Info *server,
}
add_query_id_context(iov, &n_iov);
add_ea_context(oparms, iov, &n_iov);
if (n_iov > 2) {
/*

View file

@ -117,9 +117,10 @@ struct share_redirect_error_context_rsp {
* [4] : posix context
* [5] : time warp context
* [6] : query id context
* [7] : compound padding
* [7] : create ea context
* [8] : compound padding
*/
#define SMB2_CREATE_IOV_SIZE 8
#define SMB2_CREATE_IOV_SIZE 9
/*
* Maximum size of a SMB2_CREATE response is 64 (smb2 header) +
@ -413,4 +414,10 @@ struct smb2_posix_info_parsed {
const u8 *name;
};
struct smb2_create_ea_ctx {
struct create_context ctx;
__u8 name[8];
struct smb2_file_full_ea_info ea;
} __packed;
#endif /* _SMB2PDU_H */

View file

@ -61,7 +61,8 @@ struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data,
const unsigned int xid,
struct cifs_tcon *tcon,
const char *full_path,
struct kvec *iov);
struct kvec *reparse_iov,
struct kvec *xattr_iov);
int smb2_query_reparse_point(const unsigned int xid,
struct cifs_tcon *tcon,
struct cifs_sb_info *cifs_sb,

View file

@ -158,12 +158,6 @@
#define IO_REPARSE_TAG_LX_CHR 0x80000025
#define IO_REPARSE_TAG_LX_BLK 0x80000026
#define IO_REPARSE_TAG_LX_SYMLINK_LE cpu_to_le32(0xA000001D)
#define IO_REPARSE_TAG_AF_UNIX_LE cpu_to_le32(0x80000023)
#define IO_REPARSE_TAG_LX_FIFO_LE cpu_to_le32(0x80000024)
#define IO_REPARSE_TAG_LX_CHR_LE cpu_to_le32(0x80000025)
#define IO_REPARSE_TAG_LX_BLK_LE cpu_to_le32(0x80000026)
/* fsctl flags */
/* If Flags is set to this value, the request is an FSCTL not ioctl request */
#define SMB2_0_IOCTL_IS_FSCTL 0x00000001