mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 02:17:36 -04:00
ntfs: support creating Windows native symlinks
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> Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
committed by
Namjae Jeon
parent
7266767f67
commit
d8f1df2e13
@@ -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;
|
||||
|
||||
@@ -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) ||
|
||||
|
||||
@@ -365,6 +365,13 @@ unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mr
|
||||
return dt_type;
|
||||
}
|
||||
|
||||
static bool ntfs_is_drive_letter(const char *target)
|
||||
{
|
||||
return ((target[0] >= 'A' && target[0] <= 'Z') ||
|
||||
(target[0] >= 'a' && target[0] <= 'z')) &&
|
||||
target[1] == ':';
|
||||
}
|
||||
|
||||
/*
|
||||
* ntfs_translate_symlink_path
|
||||
*
|
||||
@@ -410,8 +417,7 @@ int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
|
||||
path += 4;
|
||||
|
||||
/* target must start with a drive character or '/'. */
|
||||
if (((path[0] >= 'A' && path[0] <= 'Z') ||
|
||||
(path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':') {
|
||||
if (ntfs_is_drive_letter(path)) {
|
||||
if (path[2] && path[2] != '/')
|
||||
return -EOPNOTSUPP;
|
||||
tail = path + 2;
|
||||
@@ -792,6 +798,127 @@ 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, prt_sub_shared = true;
|
||||
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:/ or C:\) */
|
||||
is_absolute = target_len > 2 &&
|
||||
ntfs_is_drive_letter(target) &&
|
||||
(target[2] == '/' || target[2] == '\\');
|
||||
|
||||
|
||||
/* Normalize and prepare NLS paths */
|
||||
prt_name = kstrdup(target, GFP_NOFS);
|
||||
if (!prt_name)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Replace '/' with '\' */
|
||||
for (i = 0; i < target_len; i++) {
|
||||
if (prt_name[i] == '/')
|
||||
prt_name[i] = '\\';
|
||||
}
|
||||
|
||||
if (is_absolute) {
|
||||
/* Prepend '\??\' to Substitutename */
|
||||
sub_name = kmalloc(target_len + 5, GFP_NOFS);
|
||||
if (!sub_name) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
snprintf(sub_name, target_len + 5, "\\??\\%s", prt_name);
|
||||
prt_sub_shared = false;
|
||||
} else {
|
||||
/* For relative symlinks (including absolute paths without drive letters),
|
||||
* SubstituteName and PrintName are identical.
|
||||
*/
|
||||
sub_name = prt_name;
|
||||
}
|
||||
|
||||
/* 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 = is_absolute ? 0 : cpu_to_le32(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) {
|
||||
int len = strlen(sub_name);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (sub_name[i] == '\\')
|
||||
sub_name[i] = '/';
|
||||
}
|
||||
ni->target = sub_name;
|
||||
sub_name = NULL;
|
||||
if (prt_sub_shared)
|
||||
prt_name = NULL;
|
||||
ni->reparse_tag = IO_REPARSE_TAG_SYMLINK;
|
||||
ni->reparse_flags = is_absolute ? 0 :
|
||||
cpu_to_le32(SYMLINK_FLAG_RELATIVE);
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(prt_name);
|
||||
if (!prt_sub_shared)
|
||||
kfree(sub_name);
|
||||
kvfree(sub_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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user