From: Hyunchul Lee <hyc.lee@gmail.com>
To: Namjae Jeon <linkinjeon@kernel.org>
Cc: Hyunchul Lee <hyc.lee@gmail.com>,
linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH 6/7] ntfs: support creating Windows native symlinks
Date: Fri, 12 Jun 2026 16:33:36 +0900 [thread overview]
Message-ID: <20260612-topic-symlink-v1-6-cc1ebf9528e1@gmail.com> (raw)
In-Reply-To: <20260612-topic-symlink-v1-0-cc1ebf9528e1@gmail.com>
And introduce the symlink=<value> mount option to configure how symbolic
links are created. The option accepts "wsl" or "native", with "wsl"
being the default.
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
---
fs/ntfs/inode.c | 4 ++
fs/ntfs/namei.c | 5 ++-
fs/ntfs/reparse.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
fs/ntfs/reparse.h | 2 +
fs/ntfs/super.c | 19 +++++++++
fs/ntfs/volume.h | 2 +
6 files changed, 155 insertions(+), 1 deletion(-)
diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 76595f2e30ff..c2715521e562 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -2382,6 +2382,10 @@ int ntfs_show_options(struct seq_file *sf, struct dentry *root)
seq_puts(sf, ",native_symlink=rel");
else
seq_puts(sf, ",native_symlink=raw");
+ if (NVolSymlinkNative(vol))
+ seq_puts(sf, ",symlink=native");
+ else
+ seq_puts(sf, ",symlink=wsl");
if (vol->sb->s_flags & SB_POSIXACL)
seq_puts(sf, ",acl");
return 0;
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 88c0b05dde3b..78c159519f9c 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -608,7 +608,10 @@ static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *d
goto err_out;
if (S_ISLNK(mode)) {
- err = ntfs_reparse_set_wsl_symlink(ni, target, target_len);
+ if (NVolSymlinkNative(vol))
+ err = ntfs_reparse_set_native_symlink(ni, target, target_len);
+ else
+ err = ntfs_reparse_set_wsl_symlink(ni, target, target_len);
if (!err)
rollback_reparse = true;
} else if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISSOCK(mode) ||
diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c
index eb1e4424e50d..f5b2a853bea1 100644
--- a/fs/ntfs/reparse.c
+++ b/fs/ntfs/reparse.c
@@ -786,6 +786,130 @@ int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
return err;
}
+int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
+ const char *target, int target_len)
+{
+ int err = 0;
+ bool is_absolute;
+ char *norm_name = NULL;
+ char *sub_name = NULL;
+ char *prt_name = NULL;
+ __le16 *sub_name_utf16 = NULL;
+ __le16 *prt_name_utf16 = NULL;
+ int sub_len, prt_len;
+ int total_data_len, total_reparse_len;
+ struct reparse_point *reparse = NULL;
+ struct symlink_reparse_data *data;
+ int i;
+
+ /* Determine if target is absolute (starts with drive letter like C:) */
+ is_absolute = (target_len > 1 && target[1] == ':');
+
+ /* Normalize and prepare NLS paths */
+ norm_name = kstrdup(target, GFP_NOFS);
+ if (!norm_name)
+ return -ENOMEM;
+
+ /* Replace '/' with '\' */
+ for (i = 0; i < target_len; i++) {
+ if (norm_name[i] == '/')
+ norm_name[i] = '\\';
+ }
+
+ if (is_absolute) {
+ prt_name = kstrdup(norm_name, GFP_NOFS);
+ if (!prt_name) {
+ err = -ENOMEM;
+ goto out;
+ }
+ /* Prepend '\??\' to Substitutename */
+ sub_name = kmalloc(target_len + 5, GFP_NOFS);
+ if (!sub_name) {
+ err = -ENOMEM;
+ goto out;
+ }
+ strscpy(sub_name, "\\??\\", target_len + 5);
+ strcat(sub_name, norm_name);
+ } else {
+ /* For relative symlinks (including absolute paths without drive letters),
+ * SubstituteName and PrintName are identical.
+ */
+ prt_name = kstrdup(norm_name, GFP_NOFS);
+ sub_name = kstrdup(norm_name, GFP_NOFS);
+ if (!prt_name || !sub_name) {
+ err = -ENOMEM;
+ goto out;
+ }
+ }
+
+ /* Convert NLS paths to UTF-16 */
+ sub_len = ntfs_nlstoucs(ni->vol, sub_name, strlen(sub_name),
+ &sub_name_utf16, PATH_MAX);
+ if (sub_len < 0) {
+ err = sub_len;
+ goto out;
+ }
+
+ prt_len = ntfs_nlstoucs(ni->vol, prt_name, strlen(prt_name),
+ &prt_name_utf16, PATH_MAX);
+ if (prt_len < 0) {
+ err = prt_len;
+ goto out;
+ }
+
+ /* Check for buffer size limits */
+ total_data_len = sizeof(struct symlink_reparse_data) +
+ (sub_len + prt_len) * sizeof(__le16);
+ if (total_data_len > 16384) { /* 16KB max reparse tag size */
+ err = -EFBIG;
+ goto out;
+ }
+
+ total_reparse_len = sizeof(struct reparse_point) + total_data_len;
+ reparse = kvzalloc(total_reparse_len, GFP_NOFS);
+ if (!reparse) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* Pack fields in reparse buffer */
+ reparse->reparse_tag = IO_REPARSE_TAG_SYMLINK;
+ reparse->reparse_data_length = cpu_to_le16(total_data_len);
+ reparse->reserved = 0;
+
+ data = (struct symlink_reparse_data *)reparse->reparse_data;
+ data->substitute_name_offset = 0;
+ data->substitute_name_length = cpu_to_le16(sub_len * sizeof(__le16));
+ data->print_name_offset = data->substitute_name_length;
+ data->print_name_length = cpu_to_le16(prt_len * sizeof(__le16));
+ data->flags = cpu_to_le32(is_absolute ? 0 : SYMLINK_FLAG_RELATIVE);
+
+ /* Copy names to path_buffer */
+ memcpy(data->path_buffer, sub_name_utf16, sub_len * sizeof(__le16));
+ memcpy(data->path_buffer + sub_len, prt_name_utf16, prt_len * sizeof(__le16));
+
+ err = ntfs_set_ntfs_reparse_data(ni, (char *)reparse, total_reparse_len);
+ if (!err) {
+ for (i = 0; i < target_len; i++) {
+ if (norm_name[i] == '\\')
+ norm_name[i] = '/';
+ }
+ ni->target = norm_name;
+ norm_name = NULL;
+ }
+
+out:
+ kfree(norm_name);
+ kfree(sub_name);
+ kfree(prt_name);
+ if (sub_name_utf16)
+ kvfree(sub_name_utf16);
+ if (prt_name_utf16)
+ kvfree(prt_name_utf16);
+ kvfree(reparse);
+ return err;
+}
+
/*
* Set reparse data for a WSL special file other than a symlink
* (socket, fifo, character or block device)
diff --git a/fs/ntfs/reparse.h b/fs/ntfs/reparse.h
index e36557f29677..c11a5bb7e6a5 100644
--- a/fs/ntfs/reparse.h
+++ b/fs/ntfs/reparse.h
@@ -15,6 +15,8 @@ int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
char **translated);
int ntfs_reparse_set_wsl_symlink(struct ntfs_inode *ni,
const char *target, int target_len);
+int ntfs_reparse_set_native_symlink(struct ntfs_inode *ni,
+ const char *symname, int symlen);
int ntfs_reparse_set_wsl_not_symlink(struct ntfs_inode *ni, mode_t mode);
int ntfs_delete_reparse_index(struct ntfs_inode *ni);
int ntfs_remove_ntfs_reparse_data(struct ntfs_inode *ni);
diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index e032a247455c..8abe7bee4c0d 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -54,6 +54,17 @@ static const struct constant_table ntfs_native_symlink_enums[] = {
{}
};
+enum {
+ SYMLINK_WSL,
+ SYMLINK_NATIVE,
+};
+
+static const struct constant_table ntfs_symlink_enums[] = {
+ { "wsl", SYMLINK_WSL },
+ { "native", SYMLINK_NATIVE },
+ {}
+};
+
enum {
Opt_uid,
Opt_gid,
@@ -78,6 +89,7 @@ enum {
Opt_discard,
Opt_nocase,
Opt_native_symlink,
+ Opt_symlink,
};
static const struct fs_parameter_spec ntfs_parameters[] = {
@@ -104,6 +116,7 @@ static const struct fs_parameter_spec ntfs_parameters[] = {
fsparam_flag("sparse", Opt_sparse),
fsparam_flag("nocase", Opt_nocase),
fsparam_enum("native_symlink", Opt_native_symlink, ntfs_native_symlink_enums),
+ fsparam_enum("symlink", Opt_symlink, ntfs_symlink_enums),
{}
};
@@ -234,6 +247,12 @@ static int ntfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
else
NVolClearNativeSymlinkRel(vol);
break;
+ case Opt_symlink:
+ if (result.uint_32 == SYMLINK_NATIVE)
+ NVolSetSymlinkNative(vol);
+ else
+ NVolClearSymlinkNative(vol);
+ break;
case Opt_sparse:
break;
default:
diff --git a/fs/ntfs/volume.h b/fs/ntfs/volume.h
index 55298689a7bb..65fd3908af26 100644
--- a/fs/ntfs/volume.h
+++ b/fs/ntfs/volume.h
@@ -196,6 +196,7 @@ enum {
NV_Discard,
NV_DisableSparse,
NV_NativeSymlinkRel,
+ NV_SymlinkNative,
};
/*
@@ -233,6 +234,7 @@ DEFINE_NVOL_BIT_OPS(CheckWindowsNames)
DEFINE_NVOL_BIT_OPS(Discard)
DEFINE_NVOL_BIT_OPS(DisableSparse)
DEFINE_NVOL_BIT_OPS(NativeSymlinkRel)
+DEFINE_NVOL_BIT_OPS(SymlinkNative)
static inline void ntfs_inc_free_clusters(struct ntfs_volume *vol, s64 nr)
{
--
2.43.0
next prev parent reply other threads:[~2026-06-12 7:34 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-12 7:33 [PATCH 0/7] ntfs: Support Windows native symbolic links Hyunchul Lee
2026-06-12 7:33 ` [PATCH 1/7] ntfs: fix incorrect size of symbolic link Hyunchul Lee
2026-06-12 7:33 ` [PATCH 2/7] ntfs: support following Windows native symlink with relative paths Hyunchul Lee
2026-06-12 16:20 ` CharSyam
2026-06-12 7:33 ` [PATCH 3/7] ntfs: support following Windows native symlink with absolute paths Hyunchul Lee
2026-06-12 7:33 ` [PATCH 4/7] ntfs: add native_symlink mount option Hyunchul Lee
2026-06-12 7:33 ` [PATCH 5/7] ntfs: clean up target name conversion for WSL symlinks Hyunchul Lee
2026-06-12 7:33 ` Hyunchul Lee [this message]
2026-06-12 16:28 ` [PATCH 6/7] ntfs: support creating Windows native symlinks CharSyam
2026-06-12 7:33 ` [PATCH 7/7] docs/fs/ntfs: add mount options to support Windows native symbolic links Hyunchul Lee
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260612-topic-symlink-v1-6-cc1ebf9528e1@gmail.com \
--to=hyc.lee@gmail.com \
--cc=linkinjeon@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®