diff --git a/include/ntfs-3g/ea.h b/include/ntfs-3g/ea.h index 1936aab4..b96e40b4 100644 --- a/include/ntfs-3g/ea.h +++ b/include/ntfs-3g/ea.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-2021 Jean-Pierre Andre * */ @@ -24,6 +24,8 @@ #ifndef EA_H #define EA_H +int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp); + 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/layout.h b/include/ntfs-3g/layout.h index 6d4d589a..d2022628 100644 --- a/include/ntfs-3g/layout.h +++ b/include/ntfs-3g/layout.h @@ -850,8 +850,10 @@ typedef enum { FILE_ATTR_OFFLINE = const_cpu_to_le32(0x00001000), FILE_ATTR_NOT_CONTENT_INDEXED = const_cpu_to_le32(0x00002000), FILE_ATTR_ENCRYPTED = const_cpu_to_le32(0x00004000), + /* Supposed to mean no data locally, possibly repurposed */ + FILE_ATTRIBUTE_RECALL_ON_OPEN = const_cpu_to_le32(0x00040000), - FILE_ATTR_VALID_FLAGS = const_cpu_to_le32(0x00007fb7), + FILE_ATTR_VALID_FLAGS = const_cpu_to_le32(0x00047fb7), /* FILE_ATTR_VALID_FLAGS masks out the old DOS VolId and the FILE_ATTR_DEVICE and preserves everything else. This mask is used to obtain all flags that are valid for reading. */ @@ -2444,8 +2446,12 @@ typedef enum { IO_REPARSE_TAG_WCI = const_cpu_to_le32(0x80000018), IO_REPARSE_TAG_CLOUD = const_cpu_to_le32(0x9000001A), IO_REPARSE_TAG_APPEXECLINK = const_cpu_to_le32(0x8000001B), - IO_REPARSE_TAG_GVFS = const_cpu_to_le32(0x9000001C), - IO_REPARSE_TAG_LX_SYMLINK = const_cpu_to_le32(0xA000001D), + IO_REPARSE_TAG_GVFS = const_cpu_to_le32(0x9000001C), + IO_REPARSE_TAG_LX_SYMLINK = const_cpu_to_le32(0xA000001D), + IO_REPARSE_TAG_AF_UNIX = const_cpu_to_le32(0x80000023), + IO_REPARSE_TAG_LX_FIFO = const_cpu_to_le32(0x80000024), + IO_REPARSE_TAG_LX_CHR = const_cpu_to_le32(0x80000025), + IO_REPARSE_TAG_LX_BLK = const_cpu_to_le32(0x80000026), IO_REPARSE_TAG_VALID_VALUES = const_cpu_to_le32(0xf000ffff), IO_REPARSE_PLUGIN_SELECT = const_cpu_to_le32(0xffff0fff), diff --git a/include/ntfs-3g/reparse.h b/include/ntfs-3g/reparse.h index 9109762b..0813ad84 100644 --- a/include/ntfs-3g/reparse.h +++ b/include/ntfs-3g/reparse.h @@ -35,6 +35,8 @@ char *ntfs_get_abslink(ntfs_volume *vol, ntfschar *junction, REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni); +int ntfs_reparse_check_wsl(ntfs_inode *ni, const REPARSE_POINT *reparse); + 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/libntfs-3g/ea.c b/libntfs-3g/ea.c index 0a7b20e9..32cccdad 100644 --- a/libntfs-3g/ea.c +++ b/libntfs-3g/ea.c @@ -3,7 +3,7 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2014 Jean-Pierre Andre + * Copyright (c) 2014-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 @@ -43,6 +43,12 @@ #ifdef HAVE_ERRNO_H #include #endif +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS +#include +#endif #include "types.h" #include "param.h" @@ -55,6 +61,9 @@ #include "logging.h" #include "xattrs.h" +static const char lxdev[] = "$LXDEV"; + + /* * Create a needed attribute (EA or EA_INFORMATION) * @@ -393,3 +402,68 @@ int ntfs_remove_ntfs_ea(ntfs_inode *ni) } return (res ? -1 : 0); } + +/* + * Check for the presence of an EA "$LXDEV" (used by WSL) + * and return its value as a device address + * + * Returns zero if successful + * -1 if failed, with errno set + */ + +int ntfs_ea_check_wsldev(ntfs_inode *ni, dev_t *rdevp) +{ + const EA_ATTR *p_ea; + int bufsize; + char *buf; + int lth; + int res; + int offset; + int next; + BOOL found; + struct { + le32 major; + le32 minor; + } device; + + res = -EOPNOTSUPP; + bufsize = 256; /* expected to be enough */ + buf = (char*)malloc(bufsize); + if (buf) { + lth = ntfs_get_ntfs_ea(ni, buf, bufsize); + /* retry if short buf */ + if (lth > bufsize) { + free(buf); + bufsize = lth; + buf = (char*)malloc(bufsize); + if (buf) + lth = ntfs_get_ntfs_ea(ni, buf, bufsize); + } + } + if (buf && (lth > 0) && (lth <= bufsize)) { + offset = 0; + found = FALSE; + do { + p_ea = (const EA_ATTR*)&buf[offset]; + next = le32_to_cpu(p_ea->next_entry_offset); + found = ((next > (int)(sizeof(lxdev) + sizeof(device))) + && (p_ea->name_length == (sizeof(lxdev) - 1)) + && (p_ea->value_length + == const_cpu_to_le16(sizeof(device))) + && !memcmp(p_ea->name, lxdev, sizeof(lxdev))); + if (!found) + offset += next; + } while (!found && (next > 0) && (offset < lth)); + if (found) { + /* beware of alignment */ + memcpy(&device, &p_ea->name[p_ea->name_length + 1], + sizeof(device)); + *rdevp = makedev(le32_to_cpu(device.major), + le32_to_cpu(device.minor)); + res = 0; + } + } + free(buf); + return (res); +} + diff --git a/libntfs-3g/reparse.c b/libntfs-3g/reparse.c index 362d539a..d8415aa5 100644 --- a/libntfs-3g/reparse.c +++ b/libntfs-3g/reparse.c @@ -3,7 +3,7 @@ * * This module is part of ntfs-3g library * - * Copyright (c) 2008-2016 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 @@ -37,7 +37,10 @@ #ifdef HAVE_SYS_STAT_H #include #endif -#ifdef HAVE_SYS_SYSMACROS_H +#ifdef MAJOR_IN_MKDEV +#include +#endif +#ifdef MAJOR_IN_SYSMACROS #include #endif @@ -56,6 +59,7 @@ #include "misc.h" #include "reparse.h" #include "xattrs.h" +#include "ea.h" struct MOUNT_POINT_REPARSE_DATA { /* reparse data for junctions */ le16 subst_name_offset; @@ -74,6 +78,11 @@ struct SYMLINK_REPARSE_DATA { /* reparse data for symlinks */ char path_buffer[0]; /* above data assume this is char array */ } ; +struct WSL_LINK_REPARSE_DATA { + le32 type; + char link[0]; +} ; + struct REPARSE_INDEX { /* index entry in $Extend/$Reparse */ INDEX_ENTRY_HEADER header; REPARSE_INDEX_KEY key; @@ -415,6 +424,35 @@ static int ntfs_drive_letter(ntfs_volume *vol, ntfschar letter) return (ret); } +/* + * Check whether reparse data describes a valid wsl special file + * which is either a socket, a fifo, or a character or block device + * + * Return zero if valid, otherwise returns a negative error code + */ + +int ntfs_reparse_check_wsl(ntfs_inode *ni, const REPARSE_POINT *reparse) +{ + int res; + + res = -EOPNOTSUPP; + switch (reparse->reparse_tag) { + case IO_REPARSE_TAG_AF_UNIX : + case IO_REPARSE_TAG_LX_FIFO : + case IO_REPARSE_TAG_LX_CHR : + case IO_REPARSE_TAG_LX_BLK : + if (!reparse->reparse_data_length + && (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) + res = 0; + break; + default : + break; + } + if (res) + errno = EOPNOTSUPP; + return (res); +} + /* * Do some sanity checks on reparse data * @@ -435,6 +473,7 @@ static BOOL valid_reparse_data(ntfs_inode *ni, unsigned int lth; const struct MOUNT_POINT_REPARSE_DATA *mount_point_data; const struct SYMLINK_REPARSE_DATA *symlink_data; + const struct WSL_LINK_REPARSE_DATA *wsl_reparse_data; ok = ni && reparse_attr && (size >= sizeof(REPARSE_POINT)) @@ -477,6 +516,22 @@ static BOOL valid_reparse_data(ntfs_inode *ni, + offs + lth)) > size) ok = FALSE; break; + case IO_REPARSE_TAG_LX_SYMLINK : + wsl_reparse_data = (const struct WSL_LINK_REPARSE_DATA*) + reparse_attr->reparse_data; + if ((le16_to_cpu(reparse_attr->reparse_data_length) + <= sizeof(wsl_reparse_data->type)) + || (wsl_reparse_data->type != const_cpu_to_le32(2))) + ok = FALSE; + break; + case IO_REPARSE_TAG_AF_UNIX : + case IO_REPARSE_TAG_LX_FIFO : + case IO_REPARSE_TAG_LX_CHR : + case IO_REPARSE_TAG_LX_BLK : + if (reparse_attr->reparse_data_length + || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) + ok = FALSE; + break; default : break; } @@ -737,6 +792,7 @@ char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point) REPARSE_POINT *reparse_attr; struct MOUNT_POINT_REPARSE_DATA *mount_point_data; struct SYMLINK_REPARSE_DATA *symlink_data; + struct WSL_LINK_REPARSE_DATA *wsl_link_data; enum { FULL_TARGET, ABS_TARGET, REL_TARGET } kind; ntfschar *p; BOOL bad; @@ -819,6 +875,22 @@ char *ntfs_make_symlink(ntfs_inode *ni, const char *mnt_point) break; } break; + case IO_REPARSE_TAG_LX_SYMLINK : + wsl_link_data = (struct WSL_LINK_REPARSE_DATA*) + reparse_attr->reparse_data; + if (wsl_link_data->type == const_cpu_to_le32(2)) { + lth = le16_to_cpu( + reparse_attr->reparse_data_length) + - sizeof(wsl_link_data->type); + target = (char*)ntfs_malloc(lth + 1); + if (target) { + memcpy(target, wsl_link_data->link, + lth); + target[lth] = 0; + bad = FALSE; + } + } + break; } free(reparse_attr); } @@ -848,6 +920,7 @@ BOOL ntfs_possible_symlink(ntfs_inode *ni) switch (reparse_attr->reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT : case IO_REPARSE_TAG_SYMLINK : + case IO_REPARSE_TAG_LX_SYMLINK : possible = TRUE; default : ; } @@ -1279,7 +1352,7 @@ REPARSE_POINT *ntfs_get_reparse_point(ntfs_inode *ni) && !valid_reparse_data(ni, reparse_attr, attr_size)) { free(reparse_attr); reparse_attr = (REPARSE_POINT*)NULL; - errno = ENOENT; + errno = EINVAL; } } else errno = EINVAL; diff --git a/ntfsprogs/ntfsinfo.c b/ntfsprogs/ntfsinfo.c index 0a6364c7..5a22f9c3 100644 --- a/ntfsprogs/ntfsinfo.c +++ b/ntfsprogs/ntfsinfo.c @@ -439,6 +439,18 @@ static const char *reparse_type_name(le32 tag) case IO_REPARSE_TAG_LX_SYMLINK : name = " (Linux symlink)"; break; + case IO_REPARSE_TAG_LX_FIFO : + name = " (Linux fifo)"; + break; + case IO_REPARSE_TAG_LX_CHR : + name = " (Linux character device)"; + break; + case IO_REPARSE_TAG_LX_BLK : + name = " (Linux block device)"; + break; + case IO_REPARSE_TAG_AF_UNIX : + name = " (Unix socket)"; + break; case IO_REPARSE_TAG_APPEXECLINK : name = " (Exec link)"; break; @@ -622,6 +634,10 @@ static void ntfs_dump_flags(const char *indent, ATTR_TYPES type, le32 flags) printf(" VIEW_INDEX"); flags &= ~FILE_ATTR_VIEW_INDEX_PRESENT; } + if (flags & FILE_ATTRIBUTE_RECALL_ON_OPEN) { + printf(" RECALL_ON_OPEN"); + flags &= ~FILE_ATTRIBUTE_RECALL_ON_OPEN; + } if (flags) printf(" UNKNOWN: 0x%08x", (unsigned int)le32_to_cpu(flags)); /* Print all the flags in hex. */ diff --git a/src/lowntfs-3g.c b/src/lowntfs-3g.c index 41049162..5cbf7f94 100644 --- a/src/lowntfs-3g.c +++ b/src/lowntfs-3g.c @@ -102,6 +102,7 @@ #include "ntfstime.h" #include "security.h" #include "reparse.h" +#include "ea.h" #include "object_id.h" #include "efs.h" #include "logging.h" @@ -677,6 +678,49 @@ static int junction_getstat(ntfs_inode *ni, return (res); } +static int wsl_getstat(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct stat *stbuf) +{ + dev_t rdev; + int res; + + res = ntfs_reparse_check_wsl(ni, reparse); + if (!res) { + switch (reparse->reparse_tag) { + case IO_REPARSE_TAG_AF_UNIX : + stbuf->st_mode = S_IFSOCK; + break; + case IO_REPARSE_TAG_LX_FIFO : + stbuf->st_mode = S_IFIFO; + break; + case IO_REPARSE_TAG_LX_CHR : + stbuf->st_mode = S_IFCHR; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + break; + case IO_REPARSE_TAG_LX_BLK : + stbuf->st_mode = S_IFBLK; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + break; + default : + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + break; + } + } + /* + * If the reparse point is not a valid wsl special file + * we display as a symlink + */ + if (res) { + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + res = 0; + } + return (res); +} + /* * Apply permission masks to st_mode returned by reparse handler */ @@ -1095,7 +1139,8 @@ static void ntfs_fuse_readlink(fuse_req_t req, fuse_ino_t ino) const plugin_operations_t *ops; res = CALL_REPARSE_PLUGIN(ni, readlink, &buf); - if (res && (errno == ELIBACC)) + /* plugin missing or reparse tag failing the check */ + if (res && ((errno == ELIBACC) || (errno == EINVAL))) errno = EOPNOTSUPP; #else /* DISABLE_PLUGINS */ errno = 0; @@ -4131,10 +4176,23 @@ static void register_internal_reparse_plugins(void) .getattr = junction_getstat, .readlink = junction_readlink, } ; + static const plugin_operations_t wsl_ops = { + .getattr = wsl_getstat, + } ; register_reparse_plugin(ctx, IO_REPARSE_TAG_MOUNT_POINT, &ops, (void*)NULL); register_reparse_plugin(ctx, IO_REPARSE_TAG_SYMLINK, &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_SYMLINK, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_AF_UNIX, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_FIFO, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_CHR, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_BLK, + &wsl_ops, (void*)NULL); } #endif /* DISABLE_PLUGINS */ diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index a1788512..a4c77eff 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -100,6 +100,7 @@ #include "ntfstime.h" #include "security.h" #include "reparse.h" +#include "ea.h" #include "object_id.h" #include "efs.h" #include "logging.h" @@ -735,6 +736,49 @@ static int junction_getattr(ntfs_inode *ni, return (res); } +static int wsl_getattr(ntfs_inode *ni, const REPARSE_POINT *reparse, + struct stat *stbuf) +{ + dev_t rdev; + int res; + + res = ntfs_reparse_check_wsl(ni, reparse); + if (!res) { + switch (reparse->reparse_tag) { + case IO_REPARSE_TAG_AF_UNIX : + stbuf->st_mode = S_IFSOCK; + break; + case IO_REPARSE_TAG_LX_FIFO : + stbuf->st_mode = S_IFIFO; + break; + case IO_REPARSE_TAG_LX_CHR : + stbuf->st_mode = S_IFCHR; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + break; + case IO_REPARSE_TAG_LX_BLK : + stbuf->st_mode = S_IFBLK; + res = ntfs_ea_check_wsldev(ni, &rdev); + stbuf->st_rdev = rdev; + break; + default : + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + break; + } + } + /* + * If the reparse point is not a valid wsl special file + * we display as a symlink + */ + if (res) { + stbuf->st_size = ntfs_bad_reparse_lth; + stbuf->st_mode = S_IFLNK; + res = 0; + } + return (res); +} + /* * Apply permission masks to st_mode returned by a reparse handler */ @@ -3832,10 +3876,26 @@ static void register_internal_reparse_plugins(void) .getattr = junction_getattr, .readlink = junction_readlink, } ; + static const plugin_operations_t wsl_ops = { + .getattr = wsl_getattr, + } ; + register_reparse_plugin(ctx, IO_REPARSE_TAG_MOUNT_POINT, &ops, (void*)NULL); register_reparse_plugin(ctx, IO_REPARSE_TAG_SYMLINK, &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_SYMLINK, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_SYMLINK, + &ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_AF_UNIX, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_FIFO, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_CHR, + &wsl_ops, (void*)NULL); + register_reparse_plugin(ctx, IO_REPARSE_TAG_LX_BLK, + &wsl_ops, (void*)NULL); } #endif /* DISABLE_PLUGINS */