From 5a4b09ecf8e8ad26ea03a37e52e310fe13f15b49 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Fri, 26 Jan 2024 19:26:06 -0300 Subject: 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 Signed-off-by: Steve French --- fs/smb/client/reparse.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 165 insertions(+), 5 deletions(-) (limited to 'fs/smb/client/reparse.c') diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index c405be47c84d..b240ccc9c887 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -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,12 +137,171 @@ 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 + rc = PTR_ERR(new); + cifs_free_open_info(&data); + 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; } -- cgit v1.2.3