]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ntfs: add native_symlink mount option
authorHyunchul Lee <hyc.lee@gmail.com>
Sun, 14 Jun 2026 23:49:55 +0000 (08:49 +0900)
committerNamjae Jeon <linkinjeon@kernel.org>
Mon, 15 Jun 2026 10:39:39 +0000 (19:39 +0900)
Because bind-mounted subtrees of the volume may resolve to unexpected
locations, change converting junctions and non-relative symbolic links
into paths relative to the NTFS volume to be allowed only if the
native_symlink=rel mount option is specified.

Add the native_symlink=<value> mount option to configure how absolute
symbolic links and mount points (junctions) are handled.
The option accepts "raw" or "rel", with "raw" being the default.

Under "raw", the absolute target path (ni->target) is returned as-is
without translation. Under "rel", ntfs_translate_junction() is called
to rewrite the absolute path as a relative path anchored at the volume
root.

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
fs/ntfs/file.c
fs/ntfs/inode.c
fs/ntfs/super.c
fs/ntfs/volume.h

index 6b0dfc56577ba7c73db8fde3fa449100c234360c..6a7b638e523d91e9aca20ade2b525498d06eca76 100644 (file)
@@ -22,6 +22,7 @@
 #include "ea.h"
 #include "iomap.h"
 #include "bitmap.h"
+#include "volume.h"
 
 #include <linux/filelock.h>
 
@@ -688,12 +689,13 @@ static const char *ntfs_get_link(struct dentry *dentry, struct inode *inode,
        if (ni->reparse_tag == IO_REPARSE_TAG_MOUNT_POINT ||
            (ni->reparse_tag == IO_REPARSE_TAG_SYMLINK &&
             !(ni->reparse_flags & cpu_to_le32(SYMLINK_FLAG_RELATIVE)))) {
-               err = ntfs_translate_symlink_path(dentry, ni->target, &target);
-               if (err < 0)
-                       return ERR_PTR(err);
-
-               set_delayed_call(done, kfree_link, target);
-               return target;
+               if (NVolNativeSymlinkRel(ni->vol)) {
+                       err = ntfs_translate_symlink_path(dentry, ni->target, &target);
+                       if (err < 0)
+                               return ERR_PTR(err);
+                       set_delayed_call(done, kfree_link, target);
+                       return target;
+               }
        }
 
        return ni->target;
index 07ca799a8f9a7d0508fad1a8da85b15177d28abf..76595f2e30ffbe9af015a99e6bb06762ebbcbb53 100644 (file)
@@ -2378,6 +2378,10 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root)
                seq_puts(sf, ",discard");
        if (NVolDisableSparse(vol))
                seq_puts(sf, ",disable_sparse");
+       if (NVolNativeSymlinkRel(vol))
+               seq_puts(sf, ",native_symlink=rel");
+       else
+               seq_puts(sf, ",native_symlink=raw");
        if (vol->sb->s_flags & SB_POSIXACL)
                seq_puts(sf, ",acl");
        return 0;
index ca882e946a22d053c4839a80444a7859a5541c8c..e032a247455c2de4b1e755434b9ab9c975accac3 100644 (file)
@@ -43,6 +43,17 @@ static const struct constant_table ntfs_param_enums[] = {
        {}
 };
 
+enum {
+       NATIVE_SYMLINK_RAW,
+       NATIVE_SYMLINK_REL,
+};
+
+static const struct constant_table ntfs_native_symlink_enums[] = {
+       { "raw",                NATIVE_SYMLINK_RAW },
+       { "rel",                NATIVE_SYMLINK_REL },
+       {}
+};
+
 enum {
        Opt_uid,
        Opt_gid,
@@ -66,6 +77,7 @@ enum {
        Opt_acl,
        Opt_discard,
        Opt_nocase,
+       Opt_native_symlink,
 };
 
 static const struct fs_parameter_spec ntfs_parameters[] = {
@@ -91,6 +103,7 @@ static const struct fs_parameter_spec ntfs_parameters[] = {
        fsparam_flag("discard",                 Opt_discard),
        fsparam_flag("sparse",                  Opt_sparse),
        fsparam_flag("nocase",                  Opt_nocase),
+       fsparam_enum("native_symlink",          Opt_native_symlink, ntfs_native_symlink_enums),
        {}
 };
 
@@ -215,6 +228,12 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
                else
                        NVolClearDisableSparse(vol);
                break;
+       case Opt_native_symlink:
+               if (result.uint_32 == NATIVE_SYMLINK_REL)
+                       NVolSetNativeSymlinkRel(vol);
+               else
+                       NVolClearNativeSymlinkRel(vol);
+               break;
        case Opt_sparse:
                break;
        default:
index 3348394dbc0d3c3f129d96b6f3f27fa0472cf62c..55298689a7bbb45758ac9bb05333389228f2fd52 100644 (file)
@@ -177,6 +177,7 @@ struct ntfs_volume {
  *
  * NV_Discard                  Issue discard/TRIM commands for freed clusters.
  * NV_DisableSparse            Disable creation of sparse regions.
+ * NV_NativeSymlinkRel         Translate absolute Windows reparse targets (native_symlink=rel).
  */
 enum {
        NV_Errors,
@@ -194,6 +195,7 @@ enum {
        NV_CheckWindowsNames,
        NV_Discard,
        NV_DisableSparse,
+       NV_NativeSymlinkRel,
 };
 
 /*
@@ -230,6 +232,7 @@ DEFINE_NVOL_BIT_OPS(HideDotFiles)
 DEFINE_NVOL_BIT_OPS(CheckWindowsNames)
 DEFINE_NVOL_BIT_OPS(Discard)
 DEFINE_NVOL_BIT_OPS(DisableSparse)
+DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
 
 static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
 {