]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
cifs: Add mount option -o symlink= for choosing symlink create type
authorPali Rohár <pali@kernel.org>
Fri, 11 Oct 2024 09:20:56 +0000 (11:20 +0200)
committerSteve French <stfrench@microsoft.com>
Wed, 29 Jan 2025 23:35:32 +0000 (17:35 -0600)
Currently Linux CIFS client creates a new symlink of the first flavor which
is allowed by mount options, parsed in this order: -o (no)mfsymlinks,
-o (no)sfu, -o (no)unix (+ its aliases) and -o reparse=[type].

Introduce a new mount option -o symlink= for explicitly choosing a symlink
flavor. Possible options are:

  -o symlink=default    - The default behavior, like before this change.
  -o symlink=none       - Disallow creating a new symlinks
  -o symlink=native     - Create as native SMB symlink reparse point
  -o symlink=unix       - Create via SMB1 unix extension command
  -o symlink=mfsymlinks - Create as regular file of mfsymlinks format
  -o symlink=sfu        - Create as regular system file of SFU format
  -o symlink=nfs        - Create as NFS reparse point
  -o symlink=wsl        - Create as WSL reparse point

So for example specifying -o sfu,mfsymlinks,symlink=native will allow to
parse symlinks also of SFU and mfsymlinks types (which are disabled by
default unless mount option is explicitly specified), but new symlinks will
be created under native SMB type (which parsing is always enabled).

Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsfs.c
fs/smb/client/cifsglob.h
fs/smb/client/connect.c
fs/smb/client/fs_context.c
fs/smb/client/fs_context.h
fs/smb/client/link.c
fs/smb/client/reparse.c
fs/smb/client/reparse.h

index b800c9f585d8d1d614c2be401015e30cf5e5b515..f2c852c9d6a11f42966d2a16d4c53f5067923451 100644 (file)
@@ -715,6 +715,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
                                            cifs_sb->ctx->backupgid));
        seq_show_option(s, "reparse",
                        cifs_reparse_type_str(cifs_sb->ctx->reparse_type));
+       seq_show_option(s, "symlink",
+                       cifs_symlink_type_str(get_cifs_symlink_type(cifs_sb)));
 
        seq_printf(s, ",rsize=%u", cifs_sb->ctx->rsize);
        seq_printf(s, ",wsize=%u", cifs_sb->ctx->wsize);
index 2266b5b9a19fb7f77085b53f9e6ec2153a55f2f6..9a96f69e67d0433aaf319ee52a8d289cf351e2c9 100644 (file)
@@ -177,6 +177,39 @@ static inline const char *cifs_reparse_type_str(enum cifs_reparse_type type)
        }
 }
 
+enum cifs_symlink_type {
+       CIFS_SYMLINK_TYPE_DEFAULT,
+       CIFS_SYMLINK_TYPE_NONE,
+       CIFS_SYMLINK_TYPE_NATIVE,
+       CIFS_SYMLINK_TYPE_UNIX,
+       CIFS_SYMLINK_TYPE_MFSYMLINKS,
+       CIFS_SYMLINK_TYPE_SFU,
+       CIFS_SYMLINK_TYPE_NFS,
+       CIFS_SYMLINK_TYPE_WSL,
+};
+
+static inline const char *cifs_symlink_type_str(enum cifs_symlink_type type)
+{
+       switch (type) {
+       case CIFS_SYMLINK_TYPE_NONE:
+               return "none";
+       case CIFS_SYMLINK_TYPE_NATIVE:
+               return "native";
+       case CIFS_SYMLINK_TYPE_UNIX:
+               return "unix";
+       case CIFS_SYMLINK_TYPE_MFSYMLINKS:
+               return "mfsymlinks";
+       case CIFS_SYMLINK_TYPE_SFU:
+               return "sfu";
+       case CIFS_SYMLINK_TYPE_NFS:
+               return "nfs";
+       case CIFS_SYMLINK_TYPE_WSL:
+               return "wsl";
+       default:
+               return "unknown";
+       }
+}
+
 struct session_key {
        unsigned int len;
        char *response;
index 880d7cf8b730df9dd450b640ccb444af75282173..ebd20f48f6aacc2d851407704c1a9b9b0214fc57 100644 (file)
@@ -2849,6 +2849,8 @@ compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data)
                return 0;
        if (old->ctx->reparse_type != new->ctx->reparse_type)
                return 0;
+       if (old->ctx->symlink_type != new->ctx->symlink_type)
+               return 0;
 
        return 1;
 }
index d7d2f6c607b52b5b554b6825793b0e38a99acb9a..5a9a5e04fb0513d3e87167b59332e072f3d4f4cb 100644 (file)
@@ -185,6 +185,7 @@ const struct fs_parameter_spec smb3_fs_parameters[] = {
        fsparam_string("cache", Opt_cache),
        fsparam_string("reparse", Opt_reparse),
        fsparam_string("upcall_target", Opt_upcalltarget),
+       fsparam_string("symlink", Opt_symlink),
        fsparam_string("symlinkroot", Opt_symlinkroot),
 
        /* Arguments that should be ignored */
@@ -360,6 +361,55 @@ static int parse_reparse_flavor(struct fs_context *fc, char *value,
        return 0;
 }
 
+static const match_table_t symlink_flavor_tokens = {
+       { Opt_symlink_default,          "default" },
+       { Opt_symlink_none,             "none" },
+       { Opt_symlink_native,           "native" },
+       { Opt_symlink_unix,             "unix" },
+       { Opt_symlink_mfsymlinks,       "mfsymlinks" },
+       { Opt_symlink_sfu,              "sfu" },
+       { Opt_symlink_nfs,              "nfs" },
+       { Opt_symlink_wsl,              "wsl" },
+       { Opt_symlink_err,              NULL },
+};
+
+static int parse_symlink_flavor(struct fs_context *fc, char *value,
+                               struct smb3_fs_context *ctx)
+{
+       substring_t args[MAX_OPT_ARGS];
+
+       switch (match_token(value, symlink_flavor_tokens, args)) {
+       case Opt_symlink_default:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_DEFAULT;
+               break;
+       case Opt_symlink_none:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_NONE;
+               break;
+       case Opt_symlink_native:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_NATIVE;
+               break;
+       case Opt_symlink_unix:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_UNIX;
+               break;
+       case Opt_symlink_mfsymlinks:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_MFSYMLINKS;
+               break;
+       case Opt_symlink_sfu:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_SFU;
+               break;
+       case Opt_symlink_nfs:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_NFS;
+               break;
+       case Opt_symlink_wsl:
+               ctx->symlink_type = CIFS_SYMLINK_TYPE_WSL;
+               break;
+       default:
+               cifs_errorf(fc, "bad symlink= option: %s\n", value);
+               return 1;
+       }
+       return 0;
+}
+
 #define DUP_CTX_STR(field)                                             \
 do {                                                                   \
        if (ctx->field) {                                               \
@@ -1730,6 +1780,10 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
                if (parse_reparse_flavor(fc, param->string, ctx))
                        goto cifs_parse_mount_err;
                break;
+       case Opt_symlink:
+               if (parse_symlink_flavor(fc, param->string, ctx))
+                       goto cifs_parse_mount_err;
+               break;
        case Opt_symlinkroot:
                if (param->string[0] != '/') {
                        cifs_errorf(fc, "symlinkroot mount options must be absolute path\n");
@@ -1765,6 +1819,22 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
        return -EINVAL;
 }
 
+enum cifs_symlink_type get_cifs_symlink_type(struct cifs_sb_info *cifs_sb)
+{
+       if (cifs_sb->ctx->symlink_type == CIFS_SYMLINK_TYPE_DEFAULT) {
+               if (cifs_sb->ctx->mfsymlinks)
+                       return CIFS_SYMLINK_TYPE_MFSYMLINKS;
+               else if (cifs_sb->ctx->sfu_emul)
+                       return CIFS_SYMLINK_TYPE_SFU;
+               else if (cifs_sb->ctx->linux_ext && !cifs_sb->ctx->no_linux_ext)
+                       return CIFS_SYMLINK_TYPE_UNIX;
+               else
+                       return CIFS_SYMLINK_TYPE_NATIVE;
+       } else {
+               return cifs_sb->ctx->symlink_type;
+       }
+}
+
 int smb3_init_fs_context(struct fs_context *fc)
 {
        struct smb3_fs_context *ctx;
@@ -1841,6 +1911,7 @@ int smb3_init_fs_context(struct fs_context *fc)
 
        ctx->retrans = 1;
        ctx->reparse_type = CIFS_REPARSE_TYPE_DEFAULT;
+       ctx->symlink_type = CIFS_SYMLINK_TYPE_DEFAULT;
 
 /*
  *     short int override_uid = -1;
index 43bc3119af218cf746b4fd3790b54329dd0664e9..204643428068cbb1ad4a2bf6c45dbe9d843828b0 100644 (file)
@@ -48,6 +48,18 @@ enum cifs_reparse_parm {
        Opt_reparse_err
 };
 
+enum cifs_symlink_parm {
+       Opt_symlink_default,
+       Opt_symlink_none,
+       Opt_symlink_native,
+       Opt_symlink_unix,
+       Opt_symlink_mfsymlinks,
+       Opt_symlink_sfu,
+       Opt_symlink_nfs,
+       Opt_symlink_wsl,
+       Opt_symlink_err
+};
+
 enum cifs_sec_param {
        Opt_sec_krb5,
        Opt_sec_krb5i,
@@ -166,6 +178,7 @@ enum cifs_param {
        Opt_cache,
        Opt_reparse,
        Opt_upcalltarget,
+       Opt_symlink,
        Opt_symlinkroot,
 
        /* Mount options to be ignored */
@@ -295,6 +308,7 @@ struct smb3_fs_context {
        struct cifs_ses *dfs_root_ses;
        bool dfs_automount:1; /* set for dfs automount only */
        enum cifs_reparse_type reparse_type;
+       enum cifs_symlink_type symlink_type;
        bool dfs_conn:1; /* set for dfs mounts */
        char *dns_dom;
        char *symlinkroot; /* top level directory for native SMB symlinks in absolute format */
@@ -302,6 +316,8 @@ struct smb3_fs_context {
 
 extern const struct fs_parameter_spec smb3_fs_parameters[];
 
+extern enum cifs_symlink_type get_cifs_symlink_type(struct cifs_sb_info *cifs_sb);
+
 extern int smb3_init_fs_context(struct fs_context *fc);
 extern void smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx);
 extern void smb3_cleanup_fs_context(struct smb3_fs_context *ctx);
index 47ddeb7fa1116b3eb50b5f8732b77aa35d8737d8..6e6c09cc5ce7abad829282923730c114de212cd7 100644 (file)
@@ -18,6 +18,7 @@
 #include "cifs_unicode.h"
 #include "smb2proto.h"
 #include "cifs_ioctl.h"
+#include "fs_context.h"
 
 /*
  * M-F Symlink Functions - Begin
@@ -604,22 +605,53 @@ cifs_symlink(struct mnt_idmap *idmap, struct inode *inode,
        cifs_dbg(FYI, "symname is %s\n", symname);
 
        /* BB what if DFS and this volume is on different share? BB */
-       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) {
-               rc = create_mf_symlink(xid, pTcon, cifs_sb, full_path, symname);
-       } else if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
-               rc = __cifs_sfu_make_node(xid, inode, direntry, pTcon,
-                                         full_path, S_IFLNK, 0, symname);
+       rc = -EOPNOTSUPP;
+       switch (get_cifs_symlink_type(cifs_sb)) {
+       case CIFS_SYMLINK_TYPE_DEFAULT:
+               /* should not happen, get_cifs_symlink_type() resolves the default */
+               break;
+
+       case CIFS_SYMLINK_TYPE_NONE:
+               break;
+
+       case CIFS_SYMLINK_TYPE_UNIX:
 #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY
-       } else if (pTcon->unix_ext) {
-               rc = CIFSUnixCreateSymLink(xid, pTcon, full_path, symname,
-                                          cifs_sb->local_nls,
-                                          cifs_remap(cifs_sb));
+               if (pTcon->unix_ext) {
+                       rc = CIFSUnixCreateSymLink(xid, pTcon, full_path,
+                                                  symname,
+                                                  cifs_sb->local_nls,
+                                                  cifs_remap(cifs_sb));
+               }
 #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
-       } else if (server->ops->create_reparse_symlink) {
-               rc =  server->ops->create_reparse_symlink(xid, inode, direntry,
-                                                         pTcon, full_path,
-                                                         symname);
-               goto symlink_exit;
+               break;
+
+       case CIFS_SYMLINK_TYPE_MFSYMLINKS:
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MF_SYMLINKS) {
+                       rc = create_mf_symlink(xid, pTcon, cifs_sb,
+                                              full_path, symname);
+               }
+               break;
+
+       case CIFS_SYMLINK_TYPE_SFU:
+               if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_UNX_EMUL) {
+                       rc = __cifs_sfu_make_node(xid, inode, direntry, pTcon,
+                                                 full_path, S_IFLNK,
+                                                 0, symname);
+               }
+               break;
+
+       case CIFS_SYMLINK_TYPE_NATIVE:
+       case CIFS_SYMLINK_TYPE_NFS:
+       case CIFS_SYMLINK_TYPE_WSL:
+               if (server->ops->create_reparse_symlink) {
+                       rc = server->ops->create_reparse_symlink(xid, inode,
+                                                                direntry,
+                                                                pTcon,
+                                                                full_path,
+                                                                symname);
+                       goto symlink_exit;
+               }
+               break;
        }
 
        if (rc == 0) {
index 344371dd895cbdc603dc3c47d6199423f728aaac..24a5f563df26afbe30f86078b152dec32b14e8bb 100644 (file)
 #include "fs_context.h"
 #include "reparse.h"
 
+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,
+                    const char *symname);
+
+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,
+                    const char *symname);
+
+static int create_native_symlink(const unsigned int xid, struct inode *inode,
+                                struct dentry *dentry, struct cifs_tcon *tcon,
+                                const char *full_path, const char *symname);
+
 static int detect_directory_symlink_target(struct cifs_sb_info *cifs_sb,
                                           const unsigned int xid,
                                           const char *full_path,
@@ -23,6 +37,22 @@ static int detect_directory_symlink_target(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)
+{
+       switch (get_cifs_symlink_type(CIFS_SB(inode->i_sb))) {
+       case CIFS_SYMLINK_TYPE_NATIVE:
+               return create_native_symlink(xid, inode, dentry, tcon, full_path, symname);
+       case CIFS_SYMLINK_TYPE_NFS:
+               return mknod_nfs(xid, inode, dentry, tcon, full_path, S_IFLNK, 0, symname);
+       case CIFS_SYMLINK_TYPE_WSL:
+               return mknod_wsl(xid, inode, dentry, tcon, full_path, S_IFLNK, 0, symname);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int create_native_symlink(const unsigned int xid, struct inode *inode,
+                                struct dentry *dentry, struct cifs_tcon *tcon,
+                                const char *full_path, const char *symname)
 {
        struct reparse_symlink_data_buffer *buf = NULL;
        struct cifs_open_info_data data = {};
@@ -366,6 +396,7 @@ static int nfs_set_reparse_buf(struct reparse_nfs_data_buffer *buf,
        case NFS_SPECFILE_SOCK:
                dlen = 0;
                break;
+       case NFS_SPECFILE_LNK: /* TODO: add support for NFS symlinks */
        default:
                return -EOPNOTSUPP;
        }
@@ -384,7 +415,8 @@ static int nfs_set_reparse_buf(struct reparse_nfs_data_buffer *buf,
 
 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)
+                    const char *full_path, umode_t mode, dev_t dev,
+                    const char *symname)
 {
        struct cifs_open_info_data data;
        struct reparse_nfs_data_buffer *p;
@@ -424,6 +456,7 @@ static int wsl_set_reparse_buf(struct reparse_data_buffer *buf,
        case IO_REPARSE_TAG_LX_FIFO:
        case IO_REPARSE_TAG_AF_UNIX:
                break;
+       case IO_REPARSE_TAG_LX_SYMLINK: /* TODO: add support for WSL symlinks */
        default:
                return -EOPNOTSUPP;
        }
@@ -521,7 +554,8 @@ static int wsl_set_xattrs(struct inode *inode, umode_t _mode,
 
 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)
+                    const char *full_path, umode_t mode, dev_t dev,
+                    const char *symname)
 {
        struct cifs_open_info_data data;
        struct reparse_data_buffer buf;
@@ -566,17 +600,15 @@ int smb2_mknod_reparse(unsigned int xid, struct inode *inode,
                       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;
+               return mknod_nfs(xid, inode, dentry, tcon, full_path, mode, dev, NULL);
        case CIFS_REPARSE_TYPE_WSL:
-               rc = mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev);
-               break;
+               return mknod_wsl(xid, inode, dentry, tcon, full_path, mode, dev, NULL);
+       default:
+               return -EOPNOTSUPP;
        }
-       return rc;
 }
 
 /* See MS-FSCC 2.1.2.6 for the 'NFS' style reparse tags */
@@ -849,7 +881,7 @@ out:
        return rc;
 }
 
-static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym,
+static int parse_reparse_native_symlink(struct reparse_symlink_data_buffer *sym,
                                 u32 plen,
                                 struct cifs_sb_info *cifs_sb,
                                 const char *full_path,
@@ -936,7 +968,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf,
                return parse_reparse_nfs((struct reparse_nfs_data_buffer *)buf,
                                           cifs_sb, data);
        case IO_REPARSE_TAG_SYMLINK:
-               return parse_reparse_symlink(
+               return parse_reparse_native_symlink(
                        (struct reparse_symlink_data_buffer *)buf,
                        plen, cifs_sb, full_path, data);
        case IO_REPARSE_TAG_LX_SYMLINK:
index ff05b0e75c92840668b3798843c8d624b4dcd498..5a753fec7e2c2cc548f64fb9876a8db489480e82 100644 (file)
@@ -50,6 +50,7 @@ static inline kgid_t wsl_make_kgid(struct cifs_sb_info *cifs_sb,
 static inline u64 reparse_mode_nfs_type(mode_t mode)
 {
        switch (mode & S_IFMT) {
+       case S_IFLNK: return NFS_SPECFILE_LNK;
        case S_IFBLK: return NFS_SPECFILE_BLK;
        case S_IFCHR: return NFS_SPECFILE_CHR;
        case S_IFIFO: return NFS_SPECFILE_FIFO;
@@ -61,6 +62,7 @@ static inline u64 reparse_mode_nfs_type(mode_t mode)
 static inline u32 reparse_mode_wsl_tag(mode_t mode)
 {
        switch (mode & S_IFMT) {
+       case S_IFLNK: return IO_REPARSE_TAG_LX_SYMLINK;
        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;