]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
smb: client: add support for WSL reparse points
authorPaulo Alcantara <pc@manguebit.com>
Fri, 26 Jan 2024 22:26:06 +0000 (19:26 -0300)
committerSteve French <stfrench@microsoft.com>
Mon, 11 Mar 2024 00:33:58 +0000 (19:33 -0500)
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>
fs/smb/client/cifsglob.h
fs/smb/client/fs_context.c
fs/smb/client/reparse.c
fs/smb/client/reparse.h
fs/smb/client/smb2inode.c
fs/smb/client/smb2ops.c
fs/smb/client/smb2pdu.c
fs/smb/client/smb2pdu.h
fs/smb/client/smb2proto.h
fs/smb/common/smbfsctl.h

index 345710ea961680f971f59b1d029b8ab59ec2dc80..395eaed854320e169dcaecffebe5d4a513669cf0 100644 (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 {
index 9f729617fa2087dc055bfa8774b8171eddfe6ceb..4b767efa47f1fa3af0f5a7744a64cf41b319db99 100644 (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;
index c405be47c84d928912050c3fb42a6fde432a56bf..b240ccc9c887ca1495a0ae40d1de0830d2089864 100644 (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,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;
 }
 
index 3ceb90da0df90177765dc746301c243f6de5037c..9816bac98552575e1da5a2c37cb3aed02feebf09 100644 (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,
index e869adb7618969c0b9849ef915c68cb79b9c2df0..4b25c660894cf8b119f7f1ce91e080365bff8087 100644 (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);
 
index 87f2d157c3b8213d2d0da50d142c162984f34a78..6ee22d0dbc006112c23ce6fea483a4db9cd00cf5 100644 (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;
index a500380d1b2e9de3c67b10152f5a0711483a928b..7d0157e0061e070f23d8d72d487c887cf4b9bb98 100644 (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) {
                /*
index db08194484e06cd403af629b52f34d3a483c3fc4..ea63d33e455322fab49173663c35e8dffa856f81 100644 (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 */
index 64a0ef0409a6eedc6e4415ecef911fcf0cb444fd..732169d8a67a32eab76ac5950a3f8c8d30a8b18e 100644 (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,
index edd7fc2a7921b884d5407efe772048d8378b6106..a94d658b88e86bdcfc7ab2c0a7ca2832d9c37552 100644 (file)
 #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