Supported use of WSL special file

The Windows Subsystem for Linux (WSL) of Windows 10 uses reparse points
to record special files (symlinks, fifos, sockets, char or block devices).
Honor such reparse points with the same meaning as WSL.
This commit is contained in:
Jean-Pierre André 2021-01-26 10:06:17 +01:00
parent a67746c8a8
commit 8073ab6764
8 changed files with 300 additions and 9 deletions

View File

@ -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);

View File

@ -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),

View File

@ -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);

View File

@ -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 <errno.h>
#endif
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#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);
}

View File

@ -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 <sys/stat.h>
#endif
#ifdef HAVE_SYS_SYSMACROS_H
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#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;

View File

@ -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. */

View File

@ -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 */

View File

@ -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 */