diff --git a/include/ntfs-3g/ea.h b/include/ntfs-3g/ea.h index b96e40b4..cd61fa2f 100644 --- a/include/ntfs-3g/ea.h +++ b/include/ntfs-3g/ea.h @@ -26,6 +26,8 @@ int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp); +int ntfs_ea_set_wsl_not_symlink(ntfs_inode *ni, mode_t mode, dev_t dev); + int ntfs_get_ntfs_ea(ntfs_inode *ni, char *value, size_t size); int ntfs_set_ntfs_ea(ntfs_inode *ni, const char *value, size_t size, int flags); diff --git a/include/ntfs-3g/reparse.h b/include/ntfs-3g/reparse.h index 0813ad84..c617951e 100644 --- a/include/ntfs-3g/reparse.h +++ b/include/ntfs-3g/reparse.h @@ -37,6 +37,11 @@ REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni); int ntfs_reparse_check_wsl(ntfs_inode *ni, const REPARSE_POINT *reparse); +int ntfs_reparse_set_wsl_symlink(ntfs_inode *ni, + const ntfschar *target, int target_len); + +int ntfs_reparse_set_wsl_not_symlink(ntfs_inode *ni, mode_t mode); + int ntfs_set_ntfs_reparse_data(ntfs_inode *ni, const char *value, size_t size, int flags); int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni); diff --git a/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h index 67fc49a8..30e07906 100644 --- a/include/ntfs-3g/volume.h +++ b/include/ntfs-3g/volume.h @@ -98,6 +98,11 @@ typedef enum { NTFS_VOLUME_INSECURE = 22 } ntfs_volume_status; +typedef enum { + NTFS_FILES_INTERIX, + NTFS_FILES_WSL, +} ntfs_volume_special_files; + /** * enum ntfs_volume_state_bits - * @@ -256,6 +261,7 @@ struct _ntfs_volume { s64 free_mft_records; /* Same for free mft records (see above) */ BOOL efs_raw; /* volume is mounted for raw access to efs-encrypted files */ + ntfs_volume_special_files special_files; /* Implementation of special files */ const char *abs_mnt_point; /* Mount point */ #ifdef XATTR_MAPPINGS struct XATTRMAPPING *xattr_mapping; diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index a517ece9..e85c3c52 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -5,7 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2004-2008 Szabolcs Szakacsits * Copyright (c) 2005-2007 Yura Pakhuchiy - * Copyright (c) 2008-2020 Jean-Pierre Andre + * Copyright (c) 2008-2021 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -68,6 +68,7 @@ #include "reparse.h" #include "object_id.h" #include "xattrs.h" +#include "ea.h" /* * The little endian Unicode strings "$I30", "$SII", "$SDH", "$O" @@ -1496,9 +1497,11 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, { ntfs_inode *ni; int rollback_data = 0, rollback_sd = 0; + int rollback_dir = 0; FILE_NAME_ATTR *fn = NULL; STANDARD_INFORMATION *si = NULL; int err, fn_len, si_len; + ntfs_volume_special_files special_files; ntfs_log_trace("Entering.\n"); @@ -1515,6 +1518,7 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, #if CACHE_NIDATA_SIZE ntfs_inode_invalidate(dir_ni->vol, ni->mft_no); #endif + special_files = dir_ni->vol->special_files; /* * Create STANDARD_INFORMATION attribute. * JPA Depending on available inherited security descriptor, @@ -1542,8 +1546,19 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, } else clear_nino_flag(ni, v3_Extensions); if (!S_ISREG(type) && !S_ISDIR(type)) { - si->file_attributes = FILE_ATTR_SYSTEM; - ni->flags = FILE_ATTR_SYSTEM; + switch (special_files) { + case NTFS_FILES_WSL : + if (!S_ISLNK(type)) { + si->file_attributes + = FILE_ATTRIBUTE_RECALL_ON_OPEN; + ni->flags = FILE_ATTRIBUTE_RECALL_ON_OPEN; + } + break; + default : + si->file_attributes = FILE_ATTR_SYSTEM; + ni->flags = FILE_ATTR_SYSTEM; + break; + } } ni->flags |= FILE_ATTR_ARCHIVE; if (NVolHideDotFiles(dir_ni->vol) @@ -1576,8 +1591,8 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, err = errno; goto err_out; } + rollback_sd = 1; } - rollback_sd = 1; if (S_ISDIR(type)) { INDEX_ROOT *ir = NULL; @@ -1626,34 +1641,58 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, switch (type) { case S_IFBLK: case S_IFCHR: - data_len = offsetof(INTX_FILE, device_end); - data = ntfs_malloc(data_len); - if (!data) { - err = errno; - goto err_out; + switch (special_files) { + case NTFS_FILES_WSL : + data_len = 0; + data = (INTX_FILE*)NULL; + break; + default : + data_len = offsetof(INTX_FILE, + device_end); + data = (INTX_FILE*)ntfs_malloc( + data_len); + if (!data) { + err = errno; + goto err_out; + } + data->major = cpu_to_le64(major(dev)); + data->minor = cpu_to_le64(minor(dev)); + if (type == S_IFBLK) + data->magic + = INTX_BLOCK_DEVICE; + if (type == S_IFCHR) + data->magic + = INTX_CHARACTER_DEVICE; + break; } - data->major = cpu_to_le64(major(dev)); - data->minor = cpu_to_le64(minor(dev)); - if (type == S_IFBLK) - data->magic = INTX_BLOCK_DEVICE; - if (type == S_IFCHR) - data->magic = INTX_CHARACTER_DEVICE; break; case S_IFLNK: - data_len = sizeof(INTX_FILE_TYPES) + + switch (special_files) { + case NTFS_FILES_WSL : + data_len = 0; + data = (INTX_FILE*)NULL; + break; + default : + data_len = sizeof(INTX_FILE_TYPES) + target_len * sizeof(ntfschar); - data = ntfs_malloc(data_len); - if (!data) { - err = errno; - goto err_out; - } - data->magic = INTX_SYMBOLIC_LINK; - memcpy(data->target, target, + data = (INTX_FILE*)ntfs_malloc( + data_len); + if (!data) { + err = errno; + goto err_out; + } + data->magic = INTX_SYMBOLIC_LINK; + memcpy(data->target, target, target_len * sizeof(ntfschar)); + break; + } break; case S_IFSOCK: data = NULL; - data_len = 1; + if (special_files == NTFS_FILES_WSL) + data_len = 0; + else + data_len = 1; break; default: /* FIFO or regular file. */ data = NULL; @@ -1684,9 +1723,10 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, fn->file_name_type = FILE_NAME_POSIX; if (S_ISDIR(type)) fn->file_attributes = FILE_ATTR_I30_INDEX_PRESENT; - if (!S_ISREG(type) && !S_ISDIR(type)) - fn->file_attributes = FILE_ATTR_SYSTEM; - else + if (!S_ISREG(type) && !S_ISDIR(type)) { + if (special_files == NTFS_FILES_INTERIX) + fn->file_attributes = FILE_ATTR_SYSTEM; + } else fn->file_attributes |= ni->flags & FILE_ATTR_COMPRESSED; fn->file_attributes |= FILE_ATTR_ARCHIVE; fn->file_attributes |= ni->flags & FILE_ATTR_HIDDEN; @@ -1714,10 +1754,40 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, ntfs_log_perror("Failed to add entry to the index"); goto err_out; } + rollback_dir = 1; /* Set hard links count and directory flag. */ ni->mrec->link_count = const_cpu_to_le16(1); if (S_ISDIR(type)) ni->mrec->flags |= MFT_RECORD_IS_DIRECTORY; + /* Add reparse data */ + if (special_files == NTFS_FILES_WSL) { + switch (type) { + case S_IFLNK : + err = ntfs_reparse_set_wsl_symlink(ni, target, + target_len); + break; + case S_IFIFO : + case S_IFSOCK : + case S_IFCHR : + case S_IFBLK : + err = ntfs_reparse_set_wsl_not_symlink(ni, + type); + if (!err) { + err = ntfs_ea_set_wsl_not_symlink(ni, + type, dev); + if (err) + ntfs_remove_ntfs_reparse_data(ni); + } + break; + default : + err = 0; + break; + } + if (err) { + err = errno; + goto err_out; + } + } ntfs_inode_mark_dirty(ni); /* Done! */ free(fn); @@ -1727,6 +1797,9 @@ static ntfs_inode *__ntfs_create(ntfs_inode *dir_ni, le32 securid, err_out: ntfs_log_trace("Failed.\n"); + if (rollback_dir) + ntfs_index_remove(dir_ni, ni, fn, fn_len); + if (rollback_sd) ntfs_attr_remove(ni, AT_SECURITY_DESCRIPTOR, AT_UNNAMED, 0); diff --git a/libntfs-3g/ea.c b/libntfs-3g/ea.c index f03378bc..b86930ab 100644 --- a/libntfs-3g/ea.c +++ b/libntfs-3g/ea.c @@ -62,6 +62,7 @@ #include "xattrs.h" static const char lxdev[] = "$LXDEV"; +static const char lxmod[] = "$LXMOD"; /* @@ -466,3 +467,53 @@ int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp) return (res); } +int ntfs_ea_set_wsl_not_symlink(ntfs_inode *ni, mode_t type, dev_t dev) +{ + le32 mode; + struct { + le32 major; + le32 minor; + } device; + struct EA_WSL { + struct EA_LXMOD { /* always inserted */ + EA_ATTR base; + char name[sizeof(lxmod)]; + char value[sizeof(mode)]; + char stuff[3 & -(sizeof(lxmod) + sizeof(mode))]; + } mod; + struct EA_LXDEV { /* char or block devices only */ + EA_ATTR base; + char name[sizeof(lxdev)]; + char value[sizeof(device)]; + char stuff[3 & -(sizeof(lxdev) + sizeof(device))]; + } dev; + } attr; + int len; + int res; + + memset(&attr, 0, sizeof(attr)); + mode = cpu_to_le32((u32)(type | 0644)); + attr.mod.base.next_entry_offset + = const_cpu_to_le32(sizeof(attr.mod)); + attr.mod.base.flags = 0; + attr.mod.base.name_length = sizeof(lxmod) - 1; + attr.mod.base.value_length = const_cpu_to_le16(sizeof(mode)); + memcpy(attr.mod.name, lxmod, sizeof(lxmod)); + memcpy(attr.mod.value, &mode, sizeof(mode)); + len = sizeof(attr.mod); + + if (S_ISCHR(type) || S_ISBLK(type)) { + device.major = cpu_to_le32(major(dev)); + device.minor = cpu_to_le32(minor(dev)); + attr.dev.base.next_entry_offset + = const_cpu_to_le32(sizeof(attr.dev)); + attr.dev.base.flags = 0; + attr.dev.base.name_length = sizeof(lxdev) - 1; + attr.dev.base.value_length = const_cpu_to_le16(sizeof(device)); + memcpy(attr.dev.name, lxdev, sizeof(lxdev)); + memcpy(attr.dev.value, &device, sizeof(device)); + len += sizeof(attr.dev); + } + res = ntfs_set_ntfs_ea(ni, (char*)&attr, len, 0); + return (res); +} diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index fbdc7ca1..76dbe2f1 100644 --- a/libntfs-3g/reparse.c +++ b/libntfs-3g/reparse.c @@ -1331,6 +1331,92 @@ int ntfs_remove_ntfs_reparse_data(ntfs_inode *ni) return (res ? -1 : 0); } +/* + * Set reparse data for a WSL type symlink + */ + +int ntfs_reparse_set_wsl_symlink(ntfs_inode *ni, + const ntfschar *target, int target_len) +{ + int res; + int len; + int reparse_len; + char *utarget; + REPARSE_POINT *reparse; + struct WSL_LINK_REPARSE_DATA *data; + + res = -1; + utarget = (char*)NULL; + len = ntfs_ucstombs(target, target_len, &utarget, 0); + if (len > 0) { + reparse_len = sizeof(REPARSE_POINT) + sizeof(data->type) + len; + reparse = (REPARSE_POINT*)malloc(reparse_len); + if (reparse) { + data = (struct WSL_LINK_REPARSE_DATA*) + reparse->reparse_data; + reparse->reparse_tag = IO_REPARSE_TAG_LX_SYMLINK; + reparse->reparse_data_length + = cpu_to_le16(sizeof(data->type) + len); + reparse->reserved = const_cpu_to_le16(0); + data->type = const_cpu_to_le32(2); + memcpy(data->link, utarget, len); + res = ntfs_set_ntfs_reparse_data(ni, + (char*)reparse, reparse_len, 0); + free(reparse); + } + } + free(utarget); + return (res); +} + +/* + * Set reparse data for a WSL special file other than a symlink + * (socket, fifo, character or block device) + */ + +int ntfs_reparse_set_wsl_not_symlink(ntfs_inode *ni, mode_t mode) +{ + int res; + int len; + int reparse_len; + le32 reparse_tag; + REPARSE_POINT *reparse; + + res = -1; + len = 0; + switch (mode) { + case S_IFSOCK : + reparse_tag = IO_REPARSE_TAG_AF_UNIX; + break; + case S_IFIFO : + reparse_tag = IO_REPARSE_TAG_LX_FIFO; + break; + case S_IFCHR : + reparse_tag = IO_REPARSE_TAG_LX_CHR; + break; + case S_IFBLK : + reparse_tag = IO_REPARSE_TAG_LX_BLK; + break; + default : + len = -1; + errno = EOPNOTSUPP; + break; + } + if (len >= 0) { + reparse_len = sizeof(REPARSE_POINT) + len; + reparse = (REPARSE_POINT*)malloc(reparse_len); + if (reparse) { + reparse->reparse_tag = reparse_tag; + reparse->reparse_data_length = cpu_to_le16(len); + reparse->reserved = const_cpu_to_le16(0); + res = ntfs_set_ntfs_reparse_data(ni, + (char*)reparse, reparse_len, 0); + free(reparse); + } + } + return (res); +} + /* * Get the reparse data into a buffer diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 5cbf7f94..eea4e6da 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -2385,7 +2385,11 @@ static int ntfs_fuse_create(fuse_req_t req, fuse_ino_t parent, const char *name, perm = (typemode & ~ctx->dmask & 0777) | (dsetgid & S_ISGID); else - perm = typemode & ~ctx->fmask & 0777; + if ((ctx->special_files == NTFS_FILES_WSL) + && S_ISLNK(type)) + perm = typemode | 0777; + else + perm = typemode & ~ctx->fmask & 0777; /* * Try to get a security id available for * file creation (from inheritance or argument). @@ -4737,6 +4741,7 @@ int main(int argc, char *argv[]) goto err_out; ctx->vol->abs_mnt_point = ctx->abs_mnt_point; + ctx->vol->special_files = ctx->special_files; ctx->security.vol = ctx->vol; ctx->vol->secure_flags = ctx->secure_flags; #ifdef HAVE_SETXATTR /* extended attributes interface required */ diff --git a/src/ntfs-3g.8.in b/src/ntfs-3g.8.in index 2d18fb4b..d4338a9c 100644 --- a/src/ntfs-3g.8.in +++ b/src/ntfs-3g.8.in @@ -316,6 +316,14 @@ data streams are mapped to xattrs and user can manipulate them using .B user_xattr Same as \fBstreams_interface=\fP\fIxattr\fP. .TP +.BI special_files= value +This option selects a mode for representing a special file to be created +(symbolic link, socket, fifo, character or block device). The mode can +be \fBinterix\fR or \fBwsl\fR, and existing files in either mode are +recognized irrespective of the selected mode. Interix is the traditional +mode, used by default, and wsl is interoperable with Windows WSL, but +it is not compatible with Windows versions earlier than Windows 10. +.TP .B efs_raw This option should only be used in backup or restore situation. It changes the apparent size of files and the behavior of read and diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index a4c77eff..6ec2020a 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -2117,7 +2117,11 @@ static int ntfs_fuse_create(const char *org_path, mode_t typemode, dev_t dev, perm = (typemode & ~ctx->dmask & 0777) | (dsetgid & S_ISGID); else - perm = typemode & ~ctx->fmask & 0777; + if ((ctx->special_files == NTFS_FILES_WSL) + && S_ISLNK(type)) + perm = typemode | 0777; + else + perm = typemode & ~ctx->fmask & 0777; /* * Try to get a security id available for * file creation (from inheritance or argument). @@ -4464,6 +4468,7 @@ int main(int argc, char *argv[]) ctx->vol->abs_mnt_point = ctx->abs_mnt_point; ctx->security.vol = ctx->vol; ctx->vol->secure_flags = ctx->secure_flags; + ctx->vol->special_files = ctx->special_files; #ifdef HAVE_SETXATTR /* extended attributes interface required */ ctx->vol->efs_raw = ctx->efs_raw; #endif /* HAVE_SETXATTR */ diff --git a/src/ntfs-3g_common.c b/src/ntfs-3g_common.c index 6b24528b..7e3e93d2 100644 --- a/src/ntfs-3g_common.c +++ b/src/ntfs-3g_common.c @@ -1,7 +1,7 @@ /** * ntfs-3g_common.c - Common definitions for ntfs-3g and lowntfs-3g. * - * Copyright (c) 2010-2020 Jean-Pierre Andre + * Copyright (c) 2010-2021 Jean-Pierre Andre * Copyright (c) 2010 Erik Larsson * * This program/include file is free software; you can redistribute it and/or @@ -127,6 +127,7 @@ const struct DEFOPTION optionlist[] = { { "xattrmapping", OPT_XATTRMAPPING, FLGOPT_STRING }, { "efs_raw", OPT_EFS_RAW, FLGOPT_BOGUS }, { "posix_nlink", OPT_POSIX_NLINK, FLGOPT_BOGUS }, + { "special_files", OPT_SPECIAL_FILES, FLGOPT_STRING }, { (const char*)NULL, 0, 0 } /* end marker */ } ; @@ -503,6 +504,17 @@ char *parse_mount_options(ntfs_fuse_context_t *ctx, case OPT_POSIX_NLINK : ctx->posix_nlink = TRUE; break; + case OPT_SPECIAL_FILES : + if (!strcmp(val, "interix")) + ctx->special_files = NTFS_FILES_INTERIX; + else if (!strcmp(val, "wsl")) + ctx->special_files = NTFS_FILES_WSL; + else { + ntfs_log_error("Invalid special_files" + " mode.\n"); + goto err_exit; + } + break; case OPT_FSNAME : /* Filesystem name. */ /* * We need this to be able to check whether filesystem diff --git a/src/ntfs-3g_common.h b/src/ntfs-3g_common.h index 8c2ecf39..4ed256a3 100644 --- a/src/ntfs-3g_common.h +++ b/src/ntfs-3g_common.h @@ -93,6 +93,7 @@ enum { OPT_XATTRMAPPING, OPT_EFS_RAW, OPT_POSIX_NLINK, + OPT_SPECIAL_FILES, } ; /* Option flags */ @@ -155,6 +156,7 @@ typedef struct { BOOL blkdev; BOOL mounted; BOOL posix_nlink; + ntfs_volume_special_files special_files; #ifdef HAVE_SETXATTR /* extended attributes interface required */ BOOL efs_raw; #ifdef XATTR_MAPPINGS