diff mbox series

[07/16] hw/9pfs: Implement Windows specific utilities functions for 9pfs

Message ID 20221024045759.448014-8-bin.meng@windriver.com
State New
Headers show
Series hw/9pfs: Add 9pfs support for Windows | expand

Commit Message

Bin Meng Oct. 24, 2022, 4:57 a.m. UTC
From: Guohuai Shi <guohuai.shi@windriver.com>

Windows POSIX API and MinGW library do not provide the NO_FOLLOW
flag, and do not allow opening a directory by POSIX open(). This
causes all xxx_at() functions cannot work directly. However, we
can provide Windows handle based functions to emulate xxx_at()
functions (e.g.: openat_win32, utimensat_win32, etc.).

Windows does not support extended attributes. 9pfs for Windows uses
NTFS ADS (Alternate Data Streams) to emulate extended attributes.

Windows does not provide POSIX compatible readlink(), and symbolic
link feature in 9pfs will be disabled on Windows.

Signed-off-by: Guohuai Shi <guohuai.shi@windriver.com>
Signed-off-by: Bin Meng <bin.meng@windriver.com>
---

 hw/9pfs/9p-local.h      |   7 +
 hw/9pfs/9p-util.h       |  40 +-
 hw/9pfs/9p-local.c      |   4 -
 hw/9pfs/9p-util-win32.c | 885 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 931 insertions(+), 5 deletions(-)
 create mode 100644 hw/9pfs/9p-util-win32.c

Comments

Christian Schoenebeck Nov. 1, 2022, 2:27 p.m. UTC | #1
On Monday, October 24, 2022 6:57:50 AM CET Bin Meng wrote:
> From: Guohuai Shi <guohuai.shi@windriver.com>
> 
> Windows POSIX API and MinGW library do not provide the NO_FOLLOW
> flag, and do not allow opening a directory by POSIX open(). This
> causes all xxx_at() functions cannot work directly. However, we
> can provide Windows handle based functions to emulate xxx_at()
> functions (e.g.: openat_win32, utimensat_win32, etc.).
> 
> Windows does not support extended attributes. 9pfs for Windows uses
> NTFS ADS (Alternate Data Streams) to emulate extended attributes.
> 
> Windows does not provide POSIX compatible readlink(), and symbolic
> link feature in 9pfs will be disabled on Windows.

Wouldn't it be more user friendly if the relevant error locations would use
something like error_report_once() and suggesting to enable mapped(-xattr) to
make 9p symlinks on guest working if desired by the user?

Probably this error case would need to wrapped into a dedicated function,
otherwise I guess error_report_once() would fire several times by different
callers.

> Signed-off-by: Guohuai Shi <guohuai.shi@windriver.com>
> Signed-off-by: Bin Meng <bin.meng@windriver.com>
> ---
> 
>  hw/9pfs/9p-local.h      |   7 +
>  hw/9pfs/9p-util.h       |  40 +-
>  hw/9pfs/9p-local.c      |   4 -
>  hw/9pfs/9p-util-win32.c | 885 ++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 931 insertions(+), 5 deletions(-)
>  create mode 100644 hw/9pfs/9p-util-win32.c
> 
> diff --git a/hw/9pfs/9p-local.h b/hw/9pfs/9p-local.h
> index c8404063e5..02fd894ba3 100644
> --- a/hw/9pfs/9p-local.h
> +++ b/hw/9pfs/9p-local.h
> @@ -15,6 +15,13 @@
>  
>  #include "9p-file-id.h"
>  
> +typedef struct {
> +    P9_FILE_ID mountfd;
> +#ifdef CONFIG_WIN32
> +    char *root_path;
> +#endif
> +} LocalData;
> +
>  P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
>                                 mode_t mode);
>  P9_FILE_ID local_opendir_nofollow(FsContext *fs_ctx, const char *path);
> diff --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h
> index 1e7dc76345..82b2d0c3e4 100644
> --- a/hw/9pfs/9p-util.h
> +++ b/hw/9pfs/9p-util.h
> @@ -90,26 +90,61 @@ static inline int errno_to_dotl(int err) {
>      return err;
>  }
>  
> -#ifdef CONFIG_DARWIN
> +#if defined(CONFIG_DARWIN)
>  #define qemu_fgetxattr(...) fgetxattr(__VA_ARGS__, 0, 0)
> +#elif defined(CONFIG_WIN32)
> +#define qemu_fgetxattr fgetxattr_win32
>  #else
>  #define qemu_fgetxattr fgetxattr
>  #endif
>  
> +#ifdef CONFIG_WIN32
> +#define qemu_openat     openat_win32
> +#define qemu_fstatat    fstatat_win32
> +#define qemu_mkdirat    mkdirat_win32
> +#define qemu_renameat   renameat_win32
> +#define qemu_utimensat  utimensat_win32
> +#define qemu_unlinkat   unlinkat_win32
> +#else
>  #define qemu_openat     openat
>  #define qemu_fstatat    fstatat
>  #define qemu_mkdirat    mkdirat
>  #define qemu_renameat   renameat
>  #define qemu_utimensat  utimensat
>  #define qemu_unlinkat   unlinkat
> +#endif
> +
> +#ifdef CONFIG_WIN32
> +char *get_full_path_win32(P9_FILE_ID fd, const char *name);
> +ssize_t fgetxattr_win32(int fd, const char *name, void *value, size_t size);
> +P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int flags,
> +                        mode_t mode);
> +int fstatat_win32(P9_FILE_ID dirfd, const char *pathname,
> +                  struct stat *statbuf, int flags);
> +int mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t mode);
> +int renameat_win32(P9_FILE_ID olddirfd, const char *oldpath,
> +                   P9_FILE_ID newdirfd, const char *newpath);
> +int utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
> +                    const struct timespec times[2], int flags);
> +int unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags);
> +int statfs_win32(const char *root_path, struct statfs *stbuf);
> +P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name);
> +P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
> +                       mode_t mode);
> +#endif
>  
>  static inline void close_preserve_errno(P9_FILE_ID fd)
>  {
>      int serrno = errno;
> +#ifndef CONFIG_WIN32
>      close(fd);
> +#else
> +    CloseHandle(fd);
> +#endif
>      errno = serrno;
>  }
>  
> +#ifndef CONFIG_WIN32
>  static inline P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name)
>  {
>      return qemu_openat(dirfd, name,
> @@ -157,6 +192,7 @@ again:
>      errno = serrno;
>      return fd;
>  }
> +#endif
>  
>  ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
>                               const char *name, void *value, size_t size);
> @@ -167,6 +203,7 @@ ssize_t flistxattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
>  ssize_t fremovexattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
>                                  const char *name);
>  
> +#ifndef CONFIG_WIN32
>  /*
>   * Darwin has d_seekoff, which appears to function similarly to d_off.
>   * However, it does not appear to be supported on all file systems,
> @@ -181,6 +218,7 @@ static inline off_t qemu_dirent_off(struct dirent *dent)
>      return dent->d_off;
>  #endif
>  }
> +#endif /* !CONFIG_WIN32 */
>  
>  /**
>   * qemu_dirent_dup() - Duplicate directory entry @dent.
> diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
> index 7e8d8492ea..d0d6d93549 100644
> --- a/hw/9pfs/9p-local.c
> +++ b/hw/9pfs/9p-local.c
> @@ -53,10 +53,6 @@
>  #define BTRFS_SUPER_MAGIC 0x9123683E
>  #endif
>  
> -typedef struct {
> -    P9_FILE_ID mountfd;
> -} LocalData;
> -
>  P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
>                                 mode_t mode)
>  {
> diff --git a/hw/9pfs/9p-util-win32.c b/hw/9pfs/9p-util-win32.c
> new file mode 100644
> index 0000000000..953e7da6fa
> --- /dev/null
> +++ b/hw/9pfs/9p-util-win32.c
> @@ -0,0 +1,885 @@
> +/*
> + * 9p utilities (Windows Implementation)
> + *
> + * Copyright (c) 2022 Wind River Systems, Inc.
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +
> +/*
> + * This file contains Windows only functions for 9pfs.
> + *
> + * For 9pfs Windows host, the following features are different from Linux host:
> + *
> + * 1. Windows POSIX API does not provide the NO_FOLLOW flag, that means MinGW
> + *    cannot detect if a path is a symbolic link or not. Also Windows do not
> + *    provide POSIX compatible readlink(). Supporting symbolic link in 9pfs on
> + *    Windows may cause security issues, so symbolic link support is disabled
> + *    completely.
> + *
> + * 2. Windows file system does not support extended attributes directly. 9pfs
> + *    for Windows uses NTFS ADS (Alternate Data Streams) to emulate extended
> + *    attributes.
> + *
> + * 3. statfs() is not available on Windows. qemu_statfs() is used to emulate it.
> + *
> + * 4. On Windows trying to open a directory with the open() API will fail.
> + *    This is because Windows does not allow opening directory in normal usage.
> + *
> + *    As a result of this, all xxx_at() functions won't work directly on
> + *    Windows, e.g.: openat(), unlinkat(), etc.
> + *
> + *    As xxx_at() can prevent parent directory to be modified on Linux host,
> + *    to support this and prevent security issue, all xxx_at() APIs are replaced
> + *    by xxx_at_win32() and Windows handle is used to replace the directory fd.
> + *
> + *    Windows file system does not allow replacing a file or directory if it is
> + *    referenced by a handle. Keep the handle open will lock and protect the
> + *    parent directory and make the access to files atomically.
> + *
> + *    If we don't protect (lock) the parent directory, the parent directory may
> + *    be replaced by others (e.g.: a symbolic link) and cause security issues.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/error-report.h"
> +#include "9p.h"
> +#include "9p-util.h"
> +#include "9p-local.h"
> +
> +#include <windows.h>
> +#include <dirent.h>
> +
> +#define V9FS_MAGIC  0x53465039  /* string "9PFS" */
> +
> +/*
> + * build_ads_name - construct Windows ADS name
> + *
> + * This function constructs Windows NTFS ADS (Alternate Data Streams) name
> + * to <namebuf>.
> + */
> +static int build_ads_name(char *namebuf, size_t namebuf_len,
> +                          const char *filename, const char *ads_name)
> +{
> +    size_t total_size;
> +
> +    total_size = strlen(filename) + strlen(ads_name) + 2;
> +    if (total_size  > namebuf_len) {
> +        return -1;
> +    }
> +
> +    /*
> +     * NTFS ADS (Alternate Data Streams) name format: filename:ads_name
> +     * e.g.: D:\1.txt:my_ads_name
> +     */
> +
> +    strcpy(namebuf, filename);
> +    strcat(namebuf, ":");
> +    strcat(namebuf, ads_name);
> +
> +    return 0;
> +}
> +
> +/*
> + * copy_ads_name - copy ADS name from buffer returned by FindNextStreamW()
> + *
> + * This function removes string "$DATA" in ADS name string returned by
> + * FindNextStreamW(), and copies the real ADS name to <namebuf>.
> + */
> +static ssize_t copy_ads_name(char *namebuf, size_t namebuf_len,
> +                             char *full_ads_name)
> +{
> +    char *p1, *p2;
> +
> +    /*
> +     * NTFS ADS (Alternate Data Streams) name from enumerate data format:
> +     * :ads_name:$DATA, e.g.: :my_ads_name:$DATA
> +     *
> +     * ADS name from FindNextStreamW() always has ":$DATA" string at the end.
> +     *
> +     * This function copies ADS name to namebuf.
> +     */
> +
> +    p1 = strchr(full_ads_name, ':');
> +    if (p1 == NULL) {
> +        return -1;
> +    }
> +
> +    p2 = strchr(p1 + 1, ':');
> +    if (p2 == NULL) {
> +        return -1;
> +    }
> +
> +    /* skip empty ads name */
> +    if (p2 - p1 == 1) {
> +        return 0;
> +    }
> +
> +    if (p2 - p1 + 1 > namebuf_len) {
> +        return -1;
> +    }
> +
> +    memcpy(namebuf, p1 + 1, p2 - p1 - 1);
> +    namebuf[p2 - p1 - 1] = '\0';
> +
> +    return p2 - p1;
> +}
> +
> +/*
> + * get_full_path_win32 - get full file name base on a handle
> + *
> + * This function gets full file name based on a handle specified by <fd> to
> + * a file or directory.
> + *
> + * Caller function needs to free the file name string after use.
> + */
> +char *get_full_path_win32(P9_FILE_ID fd, const char *name)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    DWORD total_size;
> +    DWORD name_size;
> +
> +    full_file_name = g_malloc0(NAME_MAX);
> +
> +    /* get parent directory full file name */
> +    name_size = GetFinalPathNameByHandle(fd, full_file_name,
> +                                         NAME_MAX - 1, FILE_NAME_NORMALIZED);
> +    if (name_size == 0 || name_size > NAME_MAX - 1) {
> +        return NULL;
> +    }
> +
> +    /* full path returned is the "\\?\" syntax, remove the lead string */
> +    memmove(full_file_name, full_file_name + 4, NAME_MAX - 4);
> +
> +    if (name != NULL) {
> +        total_size = strlen(full_file_name) + strlen(name) + 2;
> +
> +        if (total_size > NAME_MAX) {
> +            return NULL;
> +        }
> +
> +        /* build sub-directory file name */
> +        strcat(full_file_name, "\\");
> +        strcat(full_file_name, name);
> +    }
> +
> +    return g_steal_pointer(&full_file_name);
> +}
> +
> +/*
> + * fgetxattr_win32 - get extended attribute by fd
> + *
> + * This function gets extened attribute by <fd>. <fd> will be translated to
> + * Windows handle.
> + *
> + * This function emulates extended attribute by NTFS ADS.
> + */
> +ssize_t fgetxattr_win32(int fd, const char *name, void *value, size_t size)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    char ads_file_name[NAME_MAX + 1] = {0};
> +    DWORD dwBytesRead;
> +    HANDLE hStream;
> +    HANDLE hFile;
> +
> +    hFile = (HANDLE)_get_osfhandle(fd);
> +
> +    full_file_name = get_full_path_win32(hFile, NULL);
> +    if (full_file_name == NULL) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ, NULL,
> +                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
> +    if (hStream == INVALID_HANDLE_VALUE &&
> +        GetLastError() == ERROR_FILE_NOT_FOUND) {
> +        errno = ENODATA;
> +        return -1;
> +    }
> +
> +    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
> +        errno = EIO;
> +        CloseHandle(hStream);
> +        return -1;
> +    }
> +
> +    CloseHandle(hStream);
> +
> +    return dwBytesRead;
> +}
> +
> +/*
> + * openat_win32 - emulate openat()
> + *
> + * This function emulates openat().
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So openat_win32() has to use a directory handle instead of a directory fd.
> + *
> + * For symbolic access:
> + * 1. Parent directory handle <dirfd> should not be a symbolic link because
> + *    it is opened by openat_dir() which can prevent from opening a link to
> + *    a dirctory.
> + * 2. Link flag in <mode> is not set because Windows does not have this flag.
> + *    Create a new symbolic link will be denied.
> + * 3. This function checks file symbolic link attribute after open.
> + *
> + * So symbolic link will not be accessed by 9p client.
> + */
> +P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int flags,
> +                        mode_t mode)
> +{
> +    g_autofree char *full_file_name1 = NULL;
> +    g_autofree char *full_file_name2 = NULL;
> +    HANDLE hFile = INVALID_HANDLE_VALUE;
> +    int fd;
> +
> +    full_file_name1 = get_full_path_win32(dirfd, pathname);
> +    if (full_file_name1 == NULL) {
> +        return hFile;
> +    }
> +
> +    fd = open(full_file_name1, flags, mode);
> +    if (fd > 0) {
> +        DWORD attribute;
> +        hFile = (HANDLE)_get_osfhandle(fd);
> +
> +        full_file_name2 = get_full_path_win32(hFile, NULL);
> +        attribute = GetFileAttributes(full_file_name2);
> +
> +        /* check if it is a symbolic link */
> +        if ((attribute == INVALID_FILE_ATTRIBUTES)
> +            || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> +            errno = EACCES;
> +            hFile = INVALID_HANDLE_VALUE;
> +            close(fd);
> +        }
> +    }
> +
> +    return hFile;
> +}
> +
> +/*
> + * fstatat_win32 - emulate fstatat()
> + *
> + * This function emulates fstatat().
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So fstatat_win32() has to use a directory handle instead of a directory fd.
> + *
> + * Access to a symbolic link will be denied to prevent security issues.
> + */
> +int fstatat_win32(P9_FILE_ID dirfd, const char *pathname,
> +                  struct stat *statbuf, int flags)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    HANDLE hFile = INVALID_HANDLE_VALUE;
> +    DWORD attribute;
> +    int err = 0;
> +    int ret = -1;
> +
> +    full_file_name = get_full_path_win32(dirfd, pathname);
> +    if (full_file_name == NULL) {
> +        return ret;
> +    }
> +
> +    /* open file to lock it */
> +    hFile = CreateFile(full_file_name, GENERIC_READ,
> +                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +                       NULL,
> +                       OPEN_EXISTING,
> +                       FILE_FLAG_BACKUP_SEMANTICS
> +                       | FILE_FLAG_OPEN_REPARSE_POINT,
> +                       NULL);
> +
> +    if (hFile == INVALID_HANDLE_VALUE) {
> +        err = EACCES;
> +        goto out;
> +    }
> +
> +    attribute = GetFileAttributes(full_file_name);
> +
> +    /* check if it is a symbolic link */
> +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> +        errno = EACCES;
> +        goto out;
> +    }
> +
> +    ret = stat(full_file_name, statbuf);
> +
> +out:
> +    if (hFile != INVALID_HANDLE_VALUE) {
> +        CloseHandle(hFile);
> +    }
> +
> +    if (err != 0) {
> +        errno = err;
> +    }
> +    return ret;
> +}
> +
> +/*
> + * mkdirat_win32 - emulate mkdirat()
> + *
> + * This function emulates mkdirat().
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So mkdirat_win32() has to use a directory handle instead of a directory fd.
> + */
> +int mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t mode)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    int ret = -1;
> +
> +    full_file_name = get_full_path_win32(dirfd, pathname);
> +    if (full_file_name == NULL) {
> +        return ret;
> +    }
> +
> +    ret = mkdir(full_file_name);
> +
> +    return ret;
> +}
> +
> +/*
> + * renameat_win32 - emulate renameat()
> + *
> + * This function emulates renameat().
> + *
> + * Windows POSIX API does not support openning a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So renameat_win32() has to use a directory handle instead of a directory fd.
> + *
> + * Access to a symbolic link will be denied to prevent security issues.
> + */
> +int renameat_win32(HANDLE olddirfd, const char *oldpath,
> +                   HANDLE newdirfd, const char *newpath)
> +{
> +    g_autofree char *full_old_name = NULL;
> +    g_autofree char *full_new_name = NULL;
> +    HANDLE hFile;
> +    DWORD attribute;
> +    int err = 0;
> +    int ret = -1;
> +
> +    full_old_name = get_full_path_win32(olddirfd, oldpath);
> +    full_new_name = get_full_path_win32(newdirfd, newpath);
> +    if (full_old_name == NULL || full_new_name == NULL) {
> +        return ret;
> +    }
> +
> +    /* open file to lock it */
> +    hFile = CreateFile(full_old_name, GENERIC_READ,
> +                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +                       NULL,
> +                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
> +
> +    attribute = GetFileAttributes(full_old_name);
> +
> +    /* check if it is a symbolic link */
> +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> +        err = EACCES;
> +        goto out;
> +    }
> +
> +    CloseHandle(hFile);
> +
> +    ret = rename(full_old_name, full_new_name);
> +out:
> +    if (err != 0) {
> +        errno = err;
> +    }
> +    return ret;
> +}
> +
> +/*
> + * utimensat_win32 - emulate utimensat()
> + *
> + * This function emulates utimensat().
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So utimensat_win32() has to use a directory handle instead of a directory fd.
> + *
> + * Access to a symbolic link will be denied to prevent security issues.
> + */
> +int utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
> +                    const struct timespec times[2], int flags)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    HANDLE hFile = INVALID_HANDLE_VALUE;
> +    DWORD attribute;
> +    struct utimbuf tm;
> +    int err = 0;
> +    int ret = -1;
> +
> +    full_file_name = get_full_path_win32(dirfd, pathname);
> +    if (full_file_name == NULL) {
> +        return ret;
> +    }
> +
> +    /* open file to lock it */
> +    hFile = CreateFile(full_file_name, GENERIC_READ,
> +                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +                       NULL,
> +                       OPEN_EXISTING,
> +                       FILE_FLAG_BACKUP_SEMANTICS
> +                       | FILE_FLAG_OPEN_REPARSE_POINT,
> +                       NULL);
> +
> +    if (hFile == INVALID_HANDLE_VALUE) {
> +        err = EACCES;
> +        goto out;
> +    }
> +
> +    attribute = GetFileAttributes(full_file_name);
> +
> +    /* check if it is a symbolic link */
> +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> +        errno = EACCES;
> +        goto out;
> +    }
> +
> +    tm.actime = times[0].tv_sec;
> +    tm.modtime = times[1].tv_sec;
> +
> +    ret = utime(full_file_name, &tm);
> +
> +out:
> +    if (hFile != INVALID_HANDLE_VALUE) {
> +        CloseHandle(hFile);
> +    }
> +
> +    if (err != 0) {
> +        errno = err;
> +    }
> +    return ret;
> +}
> +
> +/*
> + * unlinkat_win32 - emulate unlinkat()
> + *
> + * This function emulates unlinkat().
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So unlinkat_win32() has to use a directory handle instead of a directory fd.
> + *
> + * Access to a symbolic link will be denied to prevent security issues.
> + */
> +
> +int unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    HANDLE hFile;
> +    DWORD attribute;
> +    int err = 0;
> +    int ret = -1;
> +
> +    full_file_name = get_full_path_win32(dirfd, pathname);
> +    if (full_file_name == NULL) {
> +        return ret;
> +    }
> +
> +    /* open file to prevent other one modify it */
> +    hFile = CreateFile(full_file_name, GENERIC_READ,
> +                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +                       NULL,
> +                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
> +
> +    attribute = GetFileAttributes(full_file_name);
> +
> +    /* check if it is a symbolic link */
> +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> +        err = EACCES;
> +        goto out;
> +    }
> +
> +    if (flags == AT_REMOVEDIR) { /* remove directory */
> +        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
> +            err = ENOTDIR;
> +            goto out;
> +        }
> +        ret = rmdir(full_file_name);
> +    } else { /* remove regular file */
> +        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) != 0) {
> +            err = EISDIR;
> +            goto out;
> +        }
> +        ret = remove(full_file_name);
> +    }
> +
> +    /* after last handle closed, file will be removed */
> +    CloseHandle(hFile);
> +
> +out:
> +    if (err != 0) {
> +        errno = err;
> +    }
> +    return ret;
> +}
> +
> +/*
> + * statfs_win32 - statfs() on Windows
> + *
> + * This function emulates statfs() on Windows host.
> + */
> +int statfs_win32(const char *path, struct statfs *stbuf)
> +{
> +    char RealPath[4] = { 0 };
> +    unsigned long SectorsPerCluster;
> +    unsigned long BytesPerSector;
> +    unsigned long NumberOfFreeClusters;
> +    unsigned long TotalNumberOfClusters;
> +
> +    /* only need first 3 bytes, e.g. "C:\ABC", only need "C:\" */
> +    memcpy(RealPath, path, 3);
> +
> +    if (GetDiskFreeSpace(RealPath, &SectorsPerCluster, &BytesPerSector,
> +                         &NumberOfFreeClusters, &TotalNumberOfClusters) == 0) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    stbuf->f_type = V9FS_MAGIC;
> +    stbuf->f_bsize =
> +        (__fsword_t)SectorsPerCluster * (__fsword_t)BytesPerSector;
> +    stbuf->f_blocks = (fsblkcnt_t)TotalNumberOfClusters;
> +    stbuf->f_bfree = (fsblkcnt_t)NumberOfFreeClusters;
> +    stbuf->f_bavail = (fsblkcnt_t)NumberOfFreeClusters;
> +    stbuf->f_files = -1;
> +    stbuf->f_ffree = -1;
> +    stbuf->f_namelen = NAME_MAX;
> +    stbuf->f_frsize = 0;
> +    stbuf->f_flags = 0;
> +
> +    return 0;
> +}
> +
> +/*
> + * openat_dir - emulate openat_dir()
> + *
> + * This function emulates openat_dir().
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * So openat_dir() has to use a directory handle instead of a directory fd.
> + *
> + * Access to a symbolic link will be denied to prevent security issues.
> + */
> +P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    HANDLE hSubDir;
> +    DWORD attribute;
> +
> +    full_file_name = get_full_path_win32(dirfd, name);
> +    if (full_file_name == NULL) {
> +        return INVALID_HANDLE_VALUE;
> +    }
> +
> +    attribute = GetFileAttributes(full_file_name);
> +    if (attribute == INVALID_FILE_ATTRIBUTES) {
> +        return INVALID_HANDLE_VALUE;
> +    }
> +
> +    /* check if it is a directory */
> +    if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
> +        return INVALID_HANDLE_VALUE;
> +    }
> +
> +    /* do not allow opening a symbolic link */
> +    if ((attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> +        return INVALID_HANDLE_VALUE;
> +    }
> +
> +    /* open it */
> +    hSubDir = CreateFile(full_file_name, GENERIC_READ,
> +                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
> +                         NULL,
> +                         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
> +    return hSubDir;
> +}
> +
> +P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
> +                       mode_t mode)
> +{
> +    return openat_win32(dirfd, name, flags | _O_BINARY, mode);
> +}
> +
> +/*
> + * fgetxattrat_nofollow - get extended attribute
> + *
> + * This function gets extended attribute from file <path> in the directory
> + * specified by <dirfd>. The extended atrribute name is specified by <name>
> + * and return value will be put in <value>.
> + *
> + * This function emulates extended attribute by NTFS ADS.
> + */
> +ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
> +                             const char *name, void *value, size_t size)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    char ads_file_name[NAME_MAX + 1] = { 0 };
> +    DWORD dwBytesRead;
> +    HANDLE hStream;
> +
> +    full_file_name = get_full_path_win32(dirfd, path);
> +    if (full_file_name == NULL) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ, NULL,
> +                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
> +    if (hStream == INVALID_HANDLE_VALUE &&
> +        GetLastError() == ERROR_FILE_NOT_FOUND) {
> +        errno = ENODATA;
> +        return -1;
> +    }
> +
> +    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
> +        errno = EIO;
> +        CloseHandle(hStream);
> +        return -1;
> +    }
> +
> +    CloseHandle(hStream);
> +
> +    return dwBytesRead;
> +}
> +
> +/*
> + * fsetxattrat_nofollow - set extended attribute
> + *
> + * This function set extended attribute to file <path> in the directory
> + * specified by <dirfd>.
> + *
> + * This function emulates extended attribute by NTFS ADS.
> + */
> +
> +int fsetxattrat_nofollow(P9_FILE_ID dirfd, const char *path, const char *name,
> +                         void *value, size_t size, int flags)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    char ads_file_name[NAME_MAX + 1] = { 0 };
> +    DWORD dwBytesWrite;
> +    HANDLE hStream;
> +
> +    full_file_name = get_full_path_win32(dirfd, path);
> +    if (full_file_name == NULL) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    hStream = CreateFile(ads_file_name, GENERIC_WRITE, FILE_SHARE_READ, NULL,
> +                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
> +    if (hStream == INVALID_HANDLE_VALUE) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    if (WriteFile(hStream, value, size, &dwBytesWrite, NULL) == FALSE) {
> +        errno = EIO;
> +        CloseHandle(hStream);
> +        return -1;
> +    }
> +
> +    CloseHandle(hStream);
> +
> +    return 0;
> +}
> +
> +/*
> + * flistxattrat_nofollow - list extended attribute
> + *
> + * This function gets extended attribute lists from file <filename> in the
> + * directory specified by <dirfd>. Lists returned will be put in <list>.
> + *
> + * This function emulates extended attribute by NTFS ADS.
> + */
> +ssize_t flistxattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
> +                              char *list, size_t size)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    WCHAR WideCharStr[NAME_MAX + 1] = { 0 };
> +    char full_ads_name[NAME_MAX + 1];
> +    WIN32_FIND_STREAM_DATA fsd;
> +    BOOL bFindNext;
> +    char *list_ptr = list;
> +    size_t list_left_size = size;
> +    HANDLE hFind;
> +    int ret;
> +
> +    full_file_name = get_full_path_win32(dirfd, filename);
> +    if (full_file_name == NULL) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    /*
> +     * ADS enumerate function only has WCHAR version, so we need to
> +     * covert filename to utf-8 string.
> +     */
> +    ret = MultiByteToWideChar(CP_UTF8, 0, full_file_name,
> +                              strlen(full_file_name), WideCharStr, NAME_MAX);
> +    if (ret == 0) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    hFind = FindFirstStreamW(WideCharStr, FindStreamInfoStandard, &fsd, 0);
> +    if (hFind == INVALID_HANDLE_VALUE) {
> +        errno = ENODATA;
> +        return -1;
> +    }
> +
> +    do {
> +        memset(full_ads_name, 0, sizeof(full_ads_name));
> +
> +        /*
> +         * ADS enumerate function only has WCHAR version, so we need to
> +         * covert cStreamName to utf-8 string.
> +         */
> +        ret = WideCharToMultiByte(CP_UTF8, 0,
> +                                  fsd.cStreamName, wcslen(fsd.cStreamName) + 1,
> +                                  full_ads_name, sizeof(full_ads_name) - 1,
> +                                  NULL, NULL);
> +        if (ret == 0) {
> +            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
> +                errno = ERANGE;
> +            }
> +            CloseHandle(hFind);
> +            return -1;
> +        }
> +
> +        ret = copy_ads_name(list_ptr, list_left_size, full_ads_name);
> +        if (ret < 0) {
> +            errno = ERANGE;
> +            CloseHandle(hFind);
> +            return -1;
> +        }
> +
> +        list_ptr = list_ptr + ret;
> +        list_left_size = list_left_size - ret;
> +
> +        bFindNext = FindNextStreamW(hFind, &fsd);
> +    } while (bFindNext);
> +
> +    CloseHandle(hFind);
> +
> +    return size - list_left_size;
> +}
> +
> +/*
> + * fremovexattrat_nofollow - remove extended attribute
> + *
> + * This function removes an extended attribute from file <filename> in the
> + * directory specified by <dirfd>.
> + *
> + * This function emulates extended attribute by NTFS ADS.
> + */
> +ssize_t fremovexattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
> +                                const char *name)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    char ads_file_name[NAME_MAX + 1] = { 0 };
> +
> +    full_file_name = get_full_path_win32(dirfd, filename);
> +    if (full_file_name == NULL) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    if (build_ads_name(ads_file_name, NAME_MAX, filename, name) < 0) {
> +        errno = EIO;
> +        return -1;
> +    }
> +
> +    if (DeleteFile(ads_file_name) != 0) {
> +        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
> +            errno = ENODATA;
> +            return -1;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +/*
> + * local_opendir_nofollow - open a Windows directory
> + *
> + * This function returns a Windows file handle of the directory specified by
> + * <dirpath> based on 9pfs mount point.
> + *
> + * Windows POSIX API does not support opening a directory by open(). Only
> + * handle of directory can be opened by CreateFile().
> + *
> + * This function checks the resolved path of <dirpath>. If the resolved
> + * path is not in the scope of root directory (e.g. by symbolic link), then
> + * this function will fail to prevent any security issues.
> + */
> +HANDLE local_opendir_nofollow(FsContext *fs_ctx, const char *dirpath)
> +{
> +    g_autofree char *full_file_name = NULL;
> +    LocalData *data = fs_ctx->private;
> +    HANDLE hDir;
> +
> +    hDir = openat_dir(data->mountfd, dirpath);
> +    if (hDir == INVALID_HANDLE_VALUE) {
> +        return INVALID_HANDLE_VALUE;
> +    }
> +
> +    full_file_name = get_full_path_win32(hDir, NULL);
> +    if (full_file_name == NULL) {
> +        CloseHandle(hDir);
> +        return INVALID_HANDLE_VALUE;
> +    }
> +
> +    /*
> +     * Check if the resolved path is in the root directory scope:
> +     * data->root_path and full_file_name are full path with symbolic
> +     * link resolved, so fs_ctx->root_path must be in the head of
> +     * full_file_name. If not, that means guest OS tries to open a file not
> +     * in the scope of mount point. This operation should be denied.
> +     */
> +    if (memcmp(full_file_name, data->root_path,
> +               strlen(data->root_path)) != 0) {
> +        CloseHandle(hDir);
> +        hDir = INVALID_HANDLE_VALUE;
> +    }
> +
> +    return hDir;
> +}
>
Shi, Guohuai Nov. 1, 2022, 3:13 p.m. UTC | #2
> -----Original Message-----
> From: Christian Schoenebeck <qemu_oss@crudebyte.com>
> Sent: Tuesday, November 1, 2022 22:28
> To: qemu-devel@nongnu.org
> Cc: Shi, Guohuai <Guohuai.Shi@windriver.com>; Greg Kurz <groug@kaod.org>;
> Meng, Bin <Bin.Meng@windriver.com>
> Subject: Re: [PATCH 07/16] hw/9pfs: Implement Windows specific utilities
> functions for 9pfs
> 
> [Please note: This e-mail is from an EXTERNAL e-mail address]
> 
> On Monday, October 24, 2022 6:57:50 AM CET Bin Meng wrote:
> > From: Guohuai Shi <guohuai.shi@windriver.com>
> >
> > Windows POSIX API and MinGW library do not provide the NO_FOLLOW flag,
> > and do not allow opening a directory by POSIX open(). This causes all
> > xxx_at() functions cannot work directly. However, we can provide
> > Windows handle based functions to emulate xxx_at() functions (e.g.:
> > openat_win32, utimensat_win32, etc.).
> >
> > Windows does not support extended attributes. 9pfs for Windows uses
> > NTFS ADS (Alternate Data Streams) to emulate extended attributes.
> >
> > Windows does not provide POSIX compatible readlink(), and symbolic
> > link feature in 9pfs will be disabled on Windows.
> 
> Wouldn't it be more user friendly if the relevant error locations would use
> something like error_report_once() and suggesting to enable mapped(-xattr) to
> make 9p symlinks on guest working if desired by the user?
> 
> Probably this error case would need to wrapped into a dedicated function,
> otherwise I guess error_report_once() would fire several times by different
> callers.
> 

Windows (MinGW) does not only support symlink, but also does not have symlink definitions.
Windows does not support symlink flags S_IFLNK.

So even I add symlink support by mapped-xattr, the MinGW library does not have symlink flags and get a build error.
And this flags is defined by Windows header files.
The impact of adding a new flags to an pre-defined structure (struct stat) is unknown.

So I think it is not a good idea to do that.

> > Signed-off-by: Guohuai Shi <guohuai.shi@windriver.com>
> > Signed-off-by: Bin Meng <bin.meng@windriver.com>
> > ---
> >
> >  hw/9pfs/9p-local.h      |   7 +
> >  hw/9pfs/9p-util.h       |  40 +-
> >  hw/9pfs/9p-local.c      |   4 -
> >  hw/9pfs/9p-util-win32.c | 885
> > ++++++++++++++++++++++++++++++++++++++++
> >  4 files changed, 931 insertions(+), 5 deletions(-)  create mode
> > 100644 hw/9pfs/9p-util-win32.c
> >
> > diff --git a/hw/9pfs/9p-local.h b/hw/9pfs/9p-local.h index
> > c8404063e5..02fd894ba3 100644
> > --- a/hw/9pfs/9p-local.h
> > +++ b/hw/9pfs/9p-local.h
> > @@ -15,6 +15,13 @@
> >
> >  #include "9p-file-id.h"
> >
> > +typedef struct {
> > +    P9_FILE_ID mountfd;
> > +#ifdef CONFIG_WIN32
> > +    char *root_path;
> > +#endif
> > +} LocalData;
> > +
> >  P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int
> flags,
> >                                 mode_t mode);  P9_FILE_ID
> > local_opendir_nofollow(FsContext *fs_ctx, const char *path); diff
> > --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h index
> > 1e7dc76345..82b2d0c3e4 100644
> > --- a/hw/9pfs/9p-util.h
> > +++ b/hw/9pfs/9p-util.h
> > @@ -90,26 +90,61 @@ static inline int errno_to_dotl(int err) {
> >      return err;
> >  }
> >
> > -#ifdef CONFIG_DARWIN
> > +#if defined(CONFIG_DARWIN)
> >  #define qemu_fgetxattr(...) fgetxattr(__VA_ARGS__, 0, 0)
> > +#elif defined(CONFIG_WIN32)
> > +#define qemu_fgetxattr fgetxattr_win32
> >  #else
> >  #define qemu_fgetxattr fgetxattr
> >  #endif
> >
> > +#ifdef CONFIG_WIN32
> > +#define qemu_openat     openat_win32
> > +#define qemu_fstatat    fstatat_win32
> > +#define qemu_mkdirat    mkdirat_win32
> > +#define qemu_renameat   renameat_win32
> > +#define qemu_utimensat  utimensat_win32
> > +#define qemu_unlinkat   unlinkat_win32
> > +#else
> >  #define qemu_openat     openat
> >  #define qemu_fstatat    fstatat
> >  #define qemu_mkdirat    mkdirat
> >  #define qemu_renameat   renameat
> >  #define qemu_utimensat  utimensat
> >  #define qemu_unlinkat   unlinkat
> > +#endif
> > +
> > +#ifdef CONFIG_WIN32
> > +char *get_full_path_win32(P9_FILE_ID fd, const char *name); ssize_t
> > +fgetxattr_win32(int fd, const char *name, void *value, size_t size);
> > +P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int flags,
> > +                        mode_t mode); int fstatat_win32(P9_FILE_ID
> > +dirfd, const char *pathname,
> > +                  struct stat *statbuf, int flags); int
> > +mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t mode);
> > +int renameat_win32(P9_FILE_ID olddirfd, const char *oldpath,
> > +                   P9_FILE_ID newdirfd, const char *newpath); int
> > +utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
> > +                    const struct timespec times[2], int flags); int
> > +unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags);
> > +int statfs_win32(const char *root_path, struct statfs *stbuf);
> > +P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name); P9_FILE_ID
> > +openat_file(P9_FILE_ID dirfd, const char *name, int flags,
> > +                       mode_t mode);
> > +#endif
> >
> >  static inline void close_preserve_errno(P9_FILE_ID fd)  {
> >      int serrno = errno;
> > +#ifndef CONFIG_WIN32
> >      close(fd);
> > +#else
> > +    CloseHandle(fd);
> > +#endif
> >      errno = serrno;
> >  }
> >
> > +#ifndef CONFIG_WIN32
> >  static inline P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char
> > *name)  {
> >      return qemu_openat(dirfd, name,
> > @@ -157,6 +192,7 @@ again:
> >      errno = serrno;
> >      return fd;
> >  }
> > +#endif
> >
> >  ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
> >                               const char *name, void *value, size_t
> > size); @@ -167,6 +203,7 @@ ssize_t flistxattrat_nofollow(P9_FILE_ID
> > dirfd, const char *filename,  ssize_t fremovexattrat_nofollow(P9_FILE_ID
> dirfd, const char *filename,
> >                                  const char *name);
> >
> > +#ifndef CONFIG_WIN32
> >  /*
> >   * Darwin has d_seekoff, which appears to function similarly to d_off.
> >   * However, it does not appear to be supported on all file systems,
> > @@ -181,6 +218,7 @@ static inline off_t qemu_dirent_off(struct dirent
> *dent)
> >      return dent->d_off;
> >  #endif
> >  }
> > +#endif /* !CONFIG_WIN32 */
> >
> >  /**
> >   * qemu_dirent_dup() - Duplicate directory entry @dent.
> > diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c index
> > 7e8d8492ea..d0d6d93549 100644
> > --- a/hw/9pfs/9p-local.c
> > +++ b/hw/9pfs/9p-local.c
> > @@ -53,10 +53,6 @@
> >  #define BTRFS_SUPER_MAGIC 0x9123683E
> >  #endif
> >
> > -typedef struct {
> > -    P9_FILE_ID mountfd;
> > -} LocalData;
> > -
> >  P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int
> flags,
> >                                 mode_t mode)  { diff --git
> > a/hw/9pfs/9p-util-win32.c b/hw/9pfs/9p-util-win32.c new file mode
> > 100644 index 0000000000..953e7da6fa
> > --- /dev/null
> > +++ b/hw/9pfs/9p-util-win32.c
> > @@ -0,0 +1,885 @@
> > +/*
> > + * 9p utilities (Windows Implementation)
> > + *
> > + * Copyright (c) 2022 Wind River Systems, Inc.
> > + *
> > + * This work is licensed under the terms of the GNU GPL, version 2 or
> later.
> > + * See the COPYING file in the top-level directory.
> > + */
> > +
> > +/*
> > + * This file contains Windows only functions for 9pfs.
> > + *
> > + * For 9pfs Windows host, the following features are different from Linux
> host:
> > + *
> > + * 1. Windows POSIX API does not provide the NO_FOLLOW flag, that means
> MinGW
> > + *    cannot detect if a path is a symbolic link or not. Also Windows do
> not
> > + *    provide POSIX compatible readlink(). Supporting symbolic link in
> 9pfs on
> > + *    Windows may cause security issues, so symbolic link support is
> disabled
> > + *    completely.
> > + *
> > + * 2. Windows file system does not support extended attributes directly.
> 9pfs
> > + *    for Windows uses NTFS ADS (Alternate Data Streams) to emulate
> extended
> > + *    attributes.
> > + *
> > + * 3. statfs() is not available on Windows. qemu_statfs() is used to
> emulate it.
> > + *
> > + * 4. On Windows trying to open a directory with the open() API will fail.
> > + *    This is because Windows does not allow opening directory in normal
> usage.
> > + *
> > + *    As a result of this, all xxx_at() functions won't work directly on
> > + *    Windows, e.g.: openat(), unlinkat(), etc.
> > + *
> > + *    As xxx_at() can prevent parent directory to be modified on Linux
> host,
> > + *    to support this and prevent security issue, all xxx_at() APIs are
> replaced
> > + *    by xxx_at_win32() and Windows handle is used to replace the
> directory fd.
> > + *
> > + *    Windows file system does not allow replacing a file or directory if
> it is
> > + *    referenced by a handle. Keep the handle open will lock and protect
> the
> > + *    parent directory and make the access to files atomically.
> > + *
> > + *    If we don't protect (lock) the parent directory, the parent
> directory may
> > + *    be replaced by others (e.g.: a symbolic link) and cause security
> issues.
> > + */
> > +
> > +#include "qemu/osdep.h"
> > +#include "qapi/error.h"
> > +#include "qemu/error-report.h"
> > +#include "9p.h"
> > +#include "9p-util.h"
> > +#include "9p-local.h"
> > +
> > +#include <windows.h>
> > +#include <dirent.h>
> > +
> > +#define V9FS_MAGIC  0x53465039  /* string "9PFS" */
> > +
> > +/*
> > + * build_ads_name - construct Windows ADS name
> > + *
> > + * This function constructs Windows NTFS ADS (Alternate Data Streams)
> > +name
> > + * to <namebuf>.
> > + */
> > +static int build_ads_name(char *namebuf, size_t namebuf_len,
> > +                          const char *filename, const char *ads_name)
> > +{
> > +    size_t total_size;
> > +
> > +    total_size = strlen(filename) + strlen(ads_name) + 2;
> > +    if (total_size  > namebuf_len) {
> > +        return -1;
> > +    }
> > +
> > +    /*
> > +     * NTFS ADS (Alternate Data Streams) name format: filename:ads_name
> > +     * e.g.: D:\1.txt:my_ads_name
> > +     */
> > +
> > +    strcpy(namebuf, filename);
> > +    strcat(namebuf, ":");
> > +    strcat(namebuf, ads_name);
> > +
> > +    return 0;
> > +}
> > +
> > +/*
> > + * copy_ads_name - copy ADS name from buffer returned by
> > +FindNextStreamW()
> > + *
> > + * This function removes string "$DATA" in ADS name string returned
> > +by
> > + * FindNextStreamW(), and copies the real ADS name to <namebuf>.
> > + */
> > +static ssize_t copy_ads_name(char *namebuf, size_t namebuf_len,
> > +                             char *full_ads_name) {
> > +    char *p1, *p2;
> > +
> > +    /*
> > +     * NTFS ADS (Alternate Data Streams) name from enumerate data format:
> > +     * :ads_name:$DATA, e.g.: :my_ads_name:$DATA
> > +     *
> > +     * ADS name from FindNextStreamW() always has ":$DATA" string at the
> end.
> > +     *
> > +     * This function copies ADS name to namebuf.
> > +     */
> > +
> > +    p1 = strchr(full_ads_name, ':');
> > +    if (p1 == NULL) {
> > +        return -1;
> > +    }
> > +
> > +    p2 = strchr(p1 + 1, ':');
> > +    if (p2 == NULL) {
> > +        return -1;
> > +    }
> > +
> > +    /* skip empty ads name */
> > +    if (p2 - p1 == 1) {
> > +        return 0;
> > +    }
> > +
> > +    if (p2 - p1 + 1 > namebuf_len) {
> > +        return -1;
> > +    }
> > +
> > +    memcpy(namebuf, p1 + 1, p2 - p1 - 1);
> > +    namebuf[p2 - p1 - 1] = '\0';
> > +
> > +    return p2 - p1;
> > +}
> > +
> > +/*
> > + * get_full_path_win32 - get full file name base on a handle
> > + *
> > + * This function gets full file name based on a handle specified by
> > +<fd> to
> > + * a file or directory.
> > + *
> > + * Caller function needs to free the file name string after use.
> > + */
> > +char *get_full_path_win32(P9_FILE_ID fd, const char *name) {
> > +    g_autofree char *full_file_name = NULL;
> > +    DWORD total_size;
> > +    DWORD name_size;
> > +
> > +    full_file_name = g_malloc0(NAME_MAX);
> > +
> > +    /* get parent directory full file name */
> > +    name_size = GetFinalPathNameByHandle(fd, full_file_name,
> > +                                         NAME_MAX - 1,
> FILE_NAME_NORMALIZED);
> > +    if (name_size == 0 || name_size > NAME_MAX - 1) {
> > +        return NULL;
> > +    }
> > +
> > +    /* full path returned is the "\\?\" syntax, remove the lead string */
> > +    memmove(full_file_name, full_file_name + 4, NAME_MAX - 4);
> > +
> > +    if (name != NULL) {
> > +        total_size = strlen(full_file_name) + strlen(name) + 2;
> > +
> > +        if (total_size > NAME_MAX) {
> > +            return NULL;
> > +        }
> > +
> > +        /* build sub-directory file name */
> > +        strcat(full_file_name, "\\");
> > +        strcat(full_file_name, name);
> > +    }
> > +
> > +    return g_steal_pointer(&full_file_name); }
> > +
> > +/*
> > + * fgetxattr_win32 - get extended attribute by fd
> > + *
> > + * This function gets extened attribute by <fd>. <fd> will be
> > +translated to
> > + * Windows handle.
> > + *
> > + * This function emulates extended attribute by NTFS ADS.
> > + */
> > +ssize_t fgetxattr_win32(int fd, const char *name, void *value, size_t
> > +size) {
> > +    g_autofree char *full_file_name = NULL;
> > +    char ads_file_name[NAME_MAX + 1] = {0};
> > +    DWORD dwBytesRead;
> > +    HANDLE hStream;
> > +    HANDLE hFile;
> > +
> > +    hFile = (HANDLE)_get_osfhandle(fd);
> > +
> > +    full_file_name = get_full_path_win32(hFile, NULL);
> > +    if (full_file_name == NULL) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0)
> {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ,
> NULL,
> > +                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
> > +    if (hStream == INVALID_HANDLE_VALUE &&
> > +        GetLastError() == ERROR_FILE_NOT_FOUND) {
> > +        errno = ENODATA;
> > +        return -1;
> > +    }
> > +
> > +    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
> > +        errno = EIO;
> > +        CloseHandle(hStream);
> > +        return -1;
> > +    }
> > +
> > +    CloseHandle(hStream);
> > +
> > +    return dwBytesRead;
> > +}
> > +
> > +/*
> > + * openat_win32 - emulate openat()
> > + *
> > + * This function emulates openat().
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So openat_win32() has to use a directory handle instead of a directory
> fd.
> > + *
> > + * For symbolic access:
> > + * 1. Parent directory handle <dirfd> should not be a symbolic link
> because
> > + *    it is opened by openat_dir() which can prevent from opening a link
> to
> > + *    a dirctory.
> > + * 2. Link flag in <mode> is not set because Windows does not have this
> flag.
> > + *    Create a new symbolic link will be denied.
> > + * 3. This function checks file symbolic link attribute after open.
> > + *
> > + * So symbolic link will not be accessed by 9p client.
> > + */
> > +P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int flags,
> > +                        mode_t mode)
> > +{
> > +    g_autofree char *full_file_name1 = NULL;
> > +    g_autofree char *full_file_name2 = NULL;
> > +    HANDLE hFile = INVALID_HANDLE_VALUE;
> > +    int fd;
> > +
> > +    full_file_name1 = get_full_path_win32(dirfd, pathname);
> > +    if (full_file_name1 == NULL) {
> > +        return hFile;
> > +    }
> > +
> > +    fd = open(full_file_name1, flags, mode);
> > +    if (fd > 0) {
> > +        DWORD attribute;
> > +        hFile = (HANDLE)_get_osfhandle(fd);
> > +
> > +        full_file_name2 = get_full_path_win32(hFile, NULL);
> > +        attribute = GetFileAttributes(full_file_name2);
> > +
> > +        /* check if it is a symbolic link */
> > +        if ((attribute == INVALID_FILE_ATTRIBUTES)
> > +            || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > +            errno = EACCES;
> > +            hFile = INVALID_HANDLE_VALUE;
> > +            close(fd);
> > +        }
> > +    }
> > +
> > +    return hFile;
> > +}
> > +
> > +/*
> > + * fstatat_win32 - emulate fstatat()
> > + *
> > + * This function emulates fstatat().
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So fstatat_win32() has to use a directory handle instead of a directory
> fd.
> > + *
> > + * Access to a symbolic link will be denied to prevent security issues.
> > + */
> > +int fstatat_win32(P9_FILE_ID dirfd, const char *pathname,
> > +                  struct stat *statbuf, int flags) {
> > +    g_autofree char *full_file_name = NULL;
> > +    HANDLE hFile = INVALID_HANDLE_VALUE;
> > +    DWORD attribute;
> > +    int err = 0;
> > +    int ret = -1;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > +    if (full_file_name == NULL) {
> > +        return ret;
> > +    }
> > +
> > +    /* open file to lock it */
> > +    hFile = CreateFile(full_file_name, GENERIC_READ,
> > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> FILE_SHARE_DELETE,
> > +                       NULL,
> > +                       OPEN_EXISTING,
> > +                       FILE_FLAG_BACKUP_SEMANTICS
> > +                       | FILE_FLAG_OPEN_REPARSE_POINT,
> > +                       NULL);
> > +
> > +    if (hFile == INVALID_HANDLE_VALUE) {
> > +        err = EACCES;
> > +        goto out;
> > +    }
> > +
> > +    attribute = GetFileAttributes(full_file_name);
> > +
> > +    /* check if it is a symbolic link */
> > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > +        errno = EACCES;
> > +        goto out;
> > +    }
> > +
> > +    ret = stat(full_file_name, statbuf);
> > +
> > +out:
> > +    if (hFile != INVALID_HANDLE_VALUE) {
> > +        CloseHandle(hFile);
> > +    }
> > +
> > +    if (err != 0) {
> > +        errno = err;
> > +    }
> > +    return ret;
> > +}
> > +
> > +/*
> > + * mkdirat_win32 - emulate mkdirat()
> > + *
> > + * This function emulates mkdirat().
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So mkdirat_win32() has to use a directory handle instead of a directory
> fd.
> > + */
> > +int mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t
> > +mode) {
> > +    g_autofree char *full_file_name = NULL;
> > +    int ret = -1;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > +    if (full_file_name == NULL) {
> > +        return ret;
> > +    }
> > +
> > +    ret = mkdir(full_file_name);
> > +
> > +    return ret;
> > +}
> > +
> > +/*
> > + * renameat_win32 - emulate renameat()
> > + *
> > + * This function emulates renameat().
> > + *
> > + * Windows POSIX API does not support openning a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So renameat_win32() has to use a directory handle instead of a
> directory fd.
> > + *
> > + * Access to a symbolic link will be denied to prevent security issues.
> > + */
> > +int renameat_win32(HANDLE olddirfd, const char *oldpath,
> > +                   HANDLE newdirfd, const char *newpath) {
> > +    g_autofree char *full_old_name = NULL;
> > +    g_autofree char *full_new_name = NULL;
> > +    HANDLE hFile;
> > +    DWORD attribute;
> > +    int err = 0;
> > +    int ret = -1;
> > +
> > +    full_old_name = get_full_path_win32(olddirfd, oldpath);
> > +    full_new_name = get_full_path_win32(newdirfd, newpath);
> > +    if (full_old_name == NULL || full_new_name == NULL) {
> > +        return ret;
> > +    }
> > +
> > +    /* open file to lock it */
> > +    hFile = CreateFile(full_old_name, GENERIC_READ,
> > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> FILE_SHARE_DELETE,
> > +                       NULL,
> > +                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
> > + NULL);
> > +
> > +    attribute = GetFileAttributes(full_old_name);
> > +
> > +    /* check if it is a symbolic link */
> > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > +        err = EACCES;
> > +        goto out;
> > +    }
> > +
> > +    CloseHandle(hFile);
> > +
> > +    ret = rename(full_old_name, full_new_name);
> > +out:
> > +    if (err != 0) {
> > +        errno = err;
> > +    }
> > +    return ret;
> > +}
> > +
> > +/*
> > + * utimensat_win32 - emulate utimensat()
> > + *
> > + * This function emulates utimensat().
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So utimensat_win32() has to use a directory handle instead of a
> directory fd.
> > + *
> > + * Access to a symbolic link will be denied to prevent security issues.
> > + */
> > +int utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
> > +                    const struct timespec times[2], int flags) {
> > +    g_autofree char *full_file_name = NULL;
> > +    HANDLE hFile = INVALID_HANDLE_VALUE;
> > +    DWORD attribute;
> > +    struct utimbuf tm;
> > +    int err = 0;
> > +    int ret = -1;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > +    if (full_file_name == NULL) {
> > +        return ret;
> > +    }
> > +
> > +    /* open file to lock it */
> > +    hFile = CreateFile(full_file_name, GENERIC_READ,
> > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> FILE_SHARE_DELETE,
> > +                       NULL,
> > +                       OPEN_EXISTING,
> > +                       FILE_FLAG_BACKUP_SEMANTICS
> > +                       | FILE_FLAG_OPEN_REPARSE_POINT,
> > +                       NULL);
> > +
> > +    if (hFile == INVALID_HANDLE_VALUE) {
> > +        err = EACCES;
> > +        goto out;
> > +    }
> > +
> > +    attribute = GetFileAttributes(full_file_name);
> > +
> > +    /* check if it is a symbolic link */
> > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > +        errno = EACCES;
> > +        goto out;
> > +    }
> > +
> > +    tm.actime = times[0].tv_sec;
> > +    tm.modtime = times[1].tv_sec;
> > +
> > +    ret = utime(full_file_name, &tm);
> > +
> > +out:
> > +    if (hFile != INVALID_HANDLE_VALUE) {
> > +        CloseHandle(hFile);
> > +    }
> > +
> > +    if (err != 0) {
> > +        errno = err;
> > +    }
> > +    return ret;
> > +}
> > +
> > +/*
> > + * unlinkat_win32 - emulate unlinkat()
> > + *
> > + * This function emulates unlinkat().
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So unlinkat_win32() has to use a directory handle instead of a
> directory fd.
> > + *
> > + * Access to a symbolic link will be denied to prevent security issues.
> > + */
> > +
> > +int unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags)
> > +{
> > +    g_autofree char *full_file_name = NULL;
> > +    HANDLE hFile;
> > +    DWORD attribute;
> > +    int err = 0;
> > +    int ret = -1;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > +    if (full_file_name == NULL) {
> > +        return ret;
> > +    }
> > +
> > +    /* open file to prevent other one modify it */
> > +    hFile = CreateFile(full_file_name, GENERIC_READ,
> > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> FILE_SHARE_DELETE,
> > +                       NULL,
> > +                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
> > + NULL);
> > +
> > +    attribute = GetFileAttributes(full_file_name);
> > +
> > +    /* check if it is a symbolic link */
> > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > +        err = EACCES;
> > +        goto out;
> > +    }
> > +
> > +    if (flags == AT_REMOVEDIR) { /* remove directory */
> > +        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
> > +            err = ENOTDIR;
> > +            goto out;
> > +        }
> > +        ret = rmdir(full_file_name);
> > +    } else { /* remove regular file */
> > +        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) != 0) {
> > +            err = EISDIR;
> > +            goto out;
> > +        }
> > +        ret = remove(full_file_name);
> > +    }
> > +
> > +    /* after last handle closed, file will be removed */
> > +    CloseHandle(hFile);
> > +
> > +out:
> > +    if (err != 0) {
> > +        errno = err;
> > +    }
> > +    return ret;
> > +}
> > +
> > +/*
> > + * statfs_win32 - statfs() on Windows
> > + *
> > + * This function emulates statfs() on Windows host.
> > + */
> > +int statfs_win32(const char *path, struct statfs *stbuf) {
> > +    char RealPath[4] = { 0 };
> > +    unsigned long SectorsPerCluster;
> > +    unsigned long BytesPerSector;
> > +    unsigned long NumberOfFreeClusters;
> > +    unsigned long TotalNumberOfClusters;
> > +
> > +    /* only need first 3 bytes, e.g. "C:\ABC", only need "C:\" */
> > +    memcpy(RealPath, path, 3);
> > +
> > +    if (GetDiskFreeSpace(RealPath, &SectorsPerCluster, &BytesPerSector,
> > +                         &NumberOfFreeClusters, &TotalNumberOfClusters) ==
> 0) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    stbuf->f_type = V9FS_MAGIC;
> > +    stbuf->f_bsize =
> > +        (__fsword_t)SectorsPerCluster * (__fsword_t)BytesPerSector;
> > +    stbuf->f_blocks = (fsblkcnt_t)TotalNumberOfClusters;
> > +    stbuf->f_bfree = (fsblkcnt_t)NumberOfFreeClusters;
> > +    stbuf->f_bavail = (fsblkcnt_t)NumberOfFreeClusters;
> > +    stbuf->f_files = -1;
> > +    stbuf->f_ffree = -1;
> > +    stbuf->f_namelen = NAME_MAX;
> > +    stbuf->f_frsize = 0;
> > +    stbuf->f_flags = 0;
> > +
> > +    return 0;
> > +}
> > +
> > +/*
> > + * openat_dir - emulate openat_dir()
> > + *
> > + * This function emulates openat_dir().
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * So openat_dir() has to use a directory handle instead of a directory
> fd.
> > + *
> > + * Access to a symbolic link will be denied to prevent security issues.
> > + */
> > +P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name) {
> > +    g_autofree char *full_file_name = NULL;
> > +    HANDLE hSubDir;
> > +    DWORD attribute;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, name);
> > +    if (full_file_name == NULL) {
> > +        return INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    attribute = GetFileAttributes(full_file_name);
> > +    if (attribute == INVALID_FILE_ATTRIBUTES) {
> > +        return INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    /* check if it is a directory */
> > +    if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
> > +        return INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    /* do not allow opening a symbolic link */
> > +    if ((attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > +        return INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    /* open it */
> > +    hSubDir = CreateFile(full_file_name, GENERIC_READ,
> > +                         FILE_SHARE_READ | FILE_SHARE_WRITE |
> FILE_SHARE_DELETE,
> > +                         NULL,
> > +                         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
> > +    return hSubDir;
> > +}
> > +
> > +P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
> > +                       mode_t mode)
> > +{
> > +    return openat_win32(dirfd, name, flags | _O_BINARY, mode); }
> > +
> > +/*
> > + * fgetxattrat_nofollow - get extended attribute
> > + *
> > + * This function gets extended attribute from file <path> in the
> > +directory
> > + * specified by <dirfd>. The extended atrribute name is specified by
> > +<name>
> > + * and return value will be put in <value>.
> > + *
> > + * This function emulates extended attribute by NTFS ADS.
> > + */
> > +ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
> > +                             const char *name, void *value, size_t
> > +size) {
> > +    g_autofree char *full_file_name = NULL;
> > +    char ads_file_name[NAME_MAX + 1] = { 0 };
> > +    DWORD dwBytesRead;
> > +    HANDLE hStream;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, path);
> > +    if (full_file_name == NULL) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0)
> {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ,
> NULL,
> > +                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
> > +    if (hStream == INVALID_HANDLE_VALUE &&
> > +        GetLastError() == ERROR_FILE_NOT_FOUND) {
> > +        errno = ENODATA;
> > +        return -1;
> > +    }
> > +
> > +    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
> > +        errno = EIO;
> > +        CloseHandle(hStream);
> > +        return -1;
> > +    }
> > +
> > +    CloseHandle(hStream);
> > +
> > +    return dwBytesRead;
> > +}
> > +
> > +/*
> > + * fsetxattrat_nofollow - set extended attribute
> > + *
> > + * This function set extended attribute to file <path> in the
> > +directory
> > + * specified by <dirfd>.
> > + *
> > + * This function emulates extended attribute by NTFS ADS.
> > + */
> > +
> > +int fsetxattrat_nofollow(P9_FILE_ID dirfd, const char *path, const char
> *name,
> > +                         void *value, size_t size, int flags) {
> > +    g_autofree char *full_file_name = NULL;
> > +    char ads_file_name[NAME_MAX + 1] = { 0 };
> > +    DWORD dwBytesWrite;
> > +    HANDLE hStream;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, path);
> > +    if (full_file_name == NULL) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0)
> {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    hStream = CreateFile(ads_file_name, GENERIC_WRITE, FILE_SHARE_READ,
> NULL,
> > +                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
> > +    if (hStream == INVALID_HANDLE_VALUE) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    if (WriteFile(hStream, value, size, &dwBytesWrite, NULL) == FALSE) {
> > +        errno = EIO;
> > +        CloseHandle(hStream);
> > +        return -1;
> > +    }
> > +
> > +    CloseHandle(hStream);
> > +
> > +    return 0;
> > +}
> > +
> > +/*
> > + * flistxattrat_nofollow - list extended attribute
> > + *
> > + * This function gets extended attribute lists from file <filename>
> > +in the
> > + * directory specified by <dirfd>. Lists returned will be put in <list>.
> > + *
> > + * This function emulates extended attribute by NTFS ADS.
> > + */
> > +ssize_t flistxattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
> > +                              char *list, size_t size) {
> > +    g_autofree char *full_file_name = NULL;
> > +    WCHAR WideCharStr[NAME_MAX + 1] = { 0 };
> > +    char full_ads_name[NAME_MAX + 1];
> > +    WIN32_FIND_STREAM_DATA fsd;
> > +    BOOL bFindNext;
> > +    char *list_ptr = list;
> > +    size_t list_left_size = size;
> > +    HANDLE hFind;
> > +    int ret;
> > +
> > +    full_file_name = get_full_path_win32(dirfd, filename);
> > +    if (full_file_name == NULL) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    /*
> > +     * ADS enumerate function only has WCHAR version, so we need to
> > +     * covert filename to utf-8 string.
> > +     */
> > +    ret = MultiByteToWideChar(CP_UTF8, 0, full_file_name,
> > +                              strlen(full_file_name), WideCharStr,
> NAME_MAX);
> > +    if (ret == 0) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    hFind = FindFirstStreamW(WideCharStr, FindStreamInfoStandard, &fsd,
> 0);
> > +    if (hFind == INVALID_HANDLE_VALUE) {
> > +        errno = ENODATA;
> > +        return -1;
> > +    }
> > +
> > +    do {
> > +        memset(full_ads_name, 0, sizeof(full_ads_name));
> > +
> > +        /*
> > +         * ADS enumerate function only has WCHAR version, so we need to
> > +         * covert cStreamName to utf-8 string.
> > +         */
> > +        ret = WideCharToMultiByte(CP_UTF8, 0,
> > +                                  fsd.cStreamName, wcslen(fsd.cStreamName)
> + 1,
> > +                                  full_ads_name, sizeof(full_ads_name) -
> 1,
> > +                                  NULL, NULL);
> > +        if (ret == 0) {
> > +            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
> > +                errno = ERANGE;
> > +            }
> > +            CloseHandle(hFind);
> > +            return -1;
> > +        }
> > +
> > +        ret = copy_ads_name(list_ptr, list_left_size, full_ads_name);
> > +        if (ret < 0) {
> > +            errno = ERANGE;
> > +            CloseHandle(hFind);
> > +            return -1;
> > +        }
> > +
> > +        list_ptr = list_ptr + ret;
> > +        list_left_size = list_left_size - ret;
> > +
> > +        bFindNext = FindNextStreamW(hFind, &fsd);
> > +    } while (bFindNext);
> > +
> > +    CloseHandle(hFind);
> > +
> > +    return size - list_left_size;
> > +}
> > +
> > +/*
> > + * fremovexattrat_nofollow - remove extended attribute
> > + *
> > + * This function removes an extended attribute from file <filename>
> > +in the
> > + * directory specified by <dirfd>.
> > + *
> > + * This function emulates extended attribute by NTFS ADS.
> > + */
> > +ssize_t fremovexattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
> > +                                const char *name) {
> > +    g_autofree char *full_file_name = NULL;
> > +    char ads_file_name[NAME_MAX + 1] = { 0 };
> > +
> > +    full_file_name = get_full_path_win32(dirfd, filename);
> > +    if (full_file_name == NULL) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    if (build_ads_name(ads_file_name, NAME_MAX, filename, name) < 0) {
> > +        errno = EIO;
> > +        return -1;
> > +    }
> > +
> > +    if (DeleteFile(ads_file_name) != 0) {
> > +        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
> > +            errno = ENODATA;
> > +            return -1;
> > +        }
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +/*
> > + * local_opendir_nofollow - open a Windows directory
> > + *
> > + * This function returns a Windows file handle of the directory
> > +specified by
> > + * <dirpath> based on 9pfs mount point.
> > + *
> > + * Windows POSIX API does not support opening a directory by open().
> > +Only
> > + * handle of directory can be opened by CreateFile().
> > + *
> > + * This function checks the resolved path of <dirpath>. If the
> > +resolved
> > + * path is not in the scope of root directory (e.g. by symbolic
> > +link), then
> > + * this function will fail to prevent any security issues.
> > + */
> > +HANDLE local_opendir_nofollow(FsContext *fs_ctx, const char *dirpath)
> > +{
> > +    g_autofree char *full_file_name = NULL;
> > +    LocalData *data = fs_ctx->private;
> > +    HANDLE hDir;
> > +
> > +    hDir = openat_dir(data->mountfd, dirpath);
> > +    if (hDir == INVALID_HANDLE_VALUE) {
> > +        return INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    full_file_name = get_full_path_win32(hDir, NULL);
> > +    if (full_file_name == NULL) {
> > +        CloseHandle(hDir);
> > +        return INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    /*
> > +     * Check if the resolved path is in the root directory scope:
> > +     * data->root_path and full_file_name are full path with symbolic
> > +     * link resolved, so fs_ctx->root_path must be in the head of
> > +     * full_file_name. If not, that means guest OS tries to open a file
> not
> > +     * in the scope of mount point. This operation should be denied.
> > +     */
> > +    if (memcmp(full_file_name, data->root_path,
> > +               strlen(data->root_path)) != 0) {
> > +        CloseHandle(hDir);
> > +        hDir = INVALID_HANDLE_VALUE;
> > +    }
> > +
> > +    return hDir;
> > +}
> >
> 
>
Shi, Guohuai Nov. 1, 2022, 3:20 p.m. UTC | #3
> -----Original Message-----
> From: Shi, Guohuai
> Sent: Tuesday, November 1, 2022 23:13
> To: Christian Schoenebeck <qemu_oss@crudebyte.com>; qemu-devel@nongnu.org
> Cc: Greg Kurz <groug@kaod.org>; Meng, Bin <Bin.Meng@windriver.com>
> Subject: RE: [PATCH 07/16] hw/9pfs: Implement Windows specific utilities
> functions for 9pfs
> 
> 
> 
> > -----Original Message-----
> > From: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > Sent: Tuesday, November 1, 2022 22:28
> > To: qemu-devel@nongnu.org
> > Cc: Shi, Guohuai <Guohuai.Shi@windriver.com>; Greg Kurz
> > <groug@kaod.org>; Meng, Bin <Bin.Meng@windriver.com>
> > Subject: Re: [PATCH 07/16] hw/9pfs: Implement Windows specific
> > utilities functions for 9pfs
> >
> > [Please note: This e-mail is from an EXTERNAL e-mail address]
> >
> > On Monday, October 24, 2022 6:57:50 AM CET Bin Meng wrote:
> > > From: Guohuai Shi <guohuai.shi@windriver.com>
> > >
> > > Windows POSIX API and MinGW library do not provide the NO_FOLLOW
> > > flag, and do not allow opening a directory by POSIX open(). This
> > > causes all
> > > xxx_at() functions cannot work directly. However, we can provide
> > > Windows handle based functions to emulate xxx_at() functions (e.g.:
> > > openat_win32, utimensat_win32, etc.).
> > >
> > > Windows does not support extended attributes. 9pfs for Windows uses
> > > NTFS ADS (Alternate Data Streams) to emulate extended attributes.
> > >
> > > Windows does not provide POSIX compatible readlink(), and symbolic
> > > link feature in 9pfs will be disabled on Windows.
> >
> > Wouldn't it be more user friendly if the relevant error locations
> > would use something like error_report_once() and suggesting to enable
> > mapped(-xattr) to make 9p symlinks on guest working if desired by the user?
> >
> > Probably this error case would need to wrapped into a dedicated
> > function, otherwise I guess error_report_once() would fire several
> > times by different callers.
> >
> 
> Windows (MinGW) does not only support symlink, but also does not have symlink
> definitions.
> Windows does not support symlink flags S_IFLNK.
> 
> So even I add symlink support by mapped-xattr, the MinGW library does not
> have symlink flags and get a build error.
> And this flags is defined by Windows header files.
> The impact of adding a new flags to an pre-defined structure (struct stat) is
> unknown.
> 
> So I think it is not a good idea to do that.

Because Windows does not support symlink, so error_report_once() and report it to user will be OK.
But mapped-xattr could not work.

> 
> > > Signed-off-by: Guohuai Shi <guohuai.shi@windriver.com>
> > > Signed-off-by: Bin Meng <bin.meng@windriver.com>
> > > ---
> > >
> > >  hw/9pfs/9p-local.h      |   7 +
> > >  hw/9pfs/9p-util.h       |  40 +-
> > >  hw/9pfs/9p-local.c      |   4 -
> > >  hw/9pfs/9p-util-win32.c | 885
> > > ++++++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 931 insertions(+), 5 deletions(-)  create mode
> > > 100644 hw/9pfs/9p-util-win32.c
> > >
> > > diff --git a/hw/9pfs/9p-local.h b/hw/9pfs/9p-local.h index
> > > c8404063e5..02fd894ba3 100644
> > > --- a/hw/9pfs/9p-local.h
> > > +++ b/hw/9pfs/9p-local.h
> > > @@ -15,6 +15,13 @@
> > >
> > >  #include "9p-file-id.h"
> > >
> > > +typedef struct {
> > > +    P9_FILE_ID mountfd;
> > > +#ifdef CONFIG_WIN32
> > > +    char *root_path;
> > > +#endif
> > > +} LocalData;
> > > +
> > >  P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path,
> > > int
> > flags,
> > >                                 mode_t mode);  P9_FILE_ID
> > > local_opendir_nofollow(FsContext *fs_ctx, const char *path); diff
> > > --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h index
> > > 1e7dc76345..82b2d0c3e4 100644
> > > --- a/hw/9pfs/9p-util.h
> > > +++ b/hw/9pfs/9p-util.h
> > > @@ -90,26 +90,61 @@ static inline int errno_to_dotl(int err) {
> > >      return err;
> > >  }
> > >
> > > -#ifdef CONFIG_DARWIN
> > > +#if defined(CONFIG_DARWIN)
> > >  #define qemu_fgetxattr(...) fgetxattr(__VA_ARGS__, 0, 0)
> > > +#elif defined(CONFIG_WIN32)
> > > +#define qemu_fgetxattr fgetxattr_win32
> > >  #else
> > >  #define qemu_fgetxattr fgetxattr
> > >  #endif
> > >
> > > +#ifdef CONFIG_WIN32
> > > +#define qemu_openat     openat_win32
> > > +#define qemu_fstatat    fstatat_win32
> > > +#define qemu_mkdirat    mkdirat_win32
> > > +#define qemu_renameat   renameat_win32
> > > +#define qemu_utimensat  utimensat_win32
> > > +#define qemu_unlinkat   unlinkat_win32
> > > +#else
> > >  #define qemu_openat     openat
> > >  #define qemu_fstatat    fstatat
> > >  #define qemu_mkdirat    mkdirat
> > >  #define qemu_renameat   renameat
> > >  #define qemu_utimensat  utimensat
> > >  #define qemu_unlinkat   unlinkat
> > > +#endif
> > > +
> > > +#ifdef CONFIG_WIN32
> > > +char *get_full_path_win32(P9_FILE_ID fd, const char *name); ssize_t
> > > +fgetxattr_win32(int fd, const char *name, void *value, size_t
> > > +size); P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname,
> int flags,
> > > +                        mode_t mode); int fstatat_win32(P9_FILE_ID
> > > +dirfd, const char *pathname,
> > > +                  struct stat *statbuf, int flags); int
> > > +mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t mode);
> > > +int renameat_win32(P9_FILE_ID olddirfd, const char *oldpath,
> > > +                   P9_FILE_ID newdirfd, const char *newpath); int
> > > +utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
> > > +                    const struct timespec times[2], int flags); int
> > > +unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags);
> > > +int statfs_win32(const char *root_path, struct statfs *stbuf);
> > > +P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name);
> > > +P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
> > > +                       mode_t mode); #endif
> > >
> > >  static inline void close_preserve_errno(P9_FILE_ID fd)  {
> > >      int serrno = errno;
> > > +#ifndef CONFIG_WIN32
> > >      close(fd);
> > > +#else
> > > +    CloseHandle(fd);
> > > +#endif
> > >      errno = serrno;
> > >  }
> > >
> > > +#ifndef CONFIG_WIN32
> > >  static inline P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char
> > > *name)  {
> > >      return qemu_openat(dirfd, name, @@ -157,6 +192,7 @@ again:
> > >      errno = serrno;
> > >      return fd;
> > >  }
> > > +#endif
> > >
> > >  ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
> > >                               const char *name, void *value, size_t
> > > size); @@ -167,6 +203,7 @@ ssize_t flistxattrat_nofollow(P9_FILE_ID
> > > dirfd, const char *filename,  ssize_t fremovexattrat_nofollow(P9_FILE_ID
> > dirfd, const char *filename,
> > >                                  const char *name);
> > >
> > > +#ifndef CONFIG_WIN32
> > >  /*
> > >   * Darwin has d_seekoff, which appears to function similarly to d_off.
> > >   * However, it does not appear to be supported on all file systems,
> > > @@ -181,6 +218,7 @@ static inline off_t qemu_dirent_off(struct dirent
> > *dent)
> > >      return dent->d_off;
> > >  #endif
> > >  }
> > > +#endif /* !CONFIG_WIN32 */
> > >
> > >  /**
> > >   * qemu_dirent_dup() - Duplicate directory entry @dent.
> > > diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c index
> > > 7e8d8492ea..d0d6d93549 100644
> > > --- a/hw/9pfs/9p-local.c
> > > +++ b/hw/9pfs/9p-local.c
> > > @@ -53,10 +53,6 @@
> > >  #define BTRFS_SUPER_MAGIC 0x9123683E
> > >  #endif
> > >
> > > -typedef struct {
> > > -    P9_FILE_ID mountfd;
> > > -} LocalData;
> > > -
> > >  P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int
> > flags,
> > >                                 mode_t mode)  { diff --git
> > > a/hw/9pfs/9p-util-win32.c b/hw/9pfs/9p-util-win32.c new file mode
> > > 100644 index 0000000000..953e7da6fa
> > > --- /dev/null
> > > +++ b/hw/9pfs/9p-util-win32.c
> > > @@ -0,0 +1,885 @@
> > > +/*
> > > + * 9p utilities (Windows Implementation)
> > > + *
> > > + * Copyright (c) 2022 Wind River Systems, Inc.
> > > + *
> > > + * This work is licensed under the terms of the GNU GPL, version 2 or
> > later.
> > > + * See the COPYING file in the top-level directory.
> > > + */
> > > +
> > > +/*
> > > + * This file contains Windows only functions for 9pfs.
> > > + *
> > > + * For 9pfs Windows host, the following features are different from
> Linux
> > host:
> > > + *
> > > + * 1. Windows POSIX API does not provide the NO_FOLLOW flag, that means
> > MinGW
> > > + *    cannot detect if a path is a symbolic link or not. Also Windows do
> > not
> > > + *    provide POSIX compatible readlink(). Supporting symbolic link in
> > 9pfs on
> > > + *    Windows may cause security issues, so symbolic link support is
> > disabled
> > > + *    completely.
> > > + *
> > > + * 2. Windows file system does not support extended attributes directly.
> > 9pfs
> > > + *    for Windows uses NTFS ADS (Alternate Data Streams) to emulate
> > extended
> > > + *    attributes.
> > > + *
> > > + * 3. statfs() is not available on Windows. qemu_statfs() is used to
> > emulate it.
> > > + *
> > > + * 4. On Windows trying to open a directory with the open() API will
> fail.
> > > + *    This is because Windows does not allow opening directory in normal
> > usage.
> > > + *
> > > + *    As a result of this, all xxx_at() functions won't work directly on
> > > + *    Windows, e.g.: openat(), unlinkat(), etc.
> > > + *
> > > + *    As xxx_at() can prevent parent directory to be modified on Linux
> > host,
> > > + *    to support this and prevent security issue, all xxx_at() APIs are
> > replaced
> > > + *    by xxx_at_win32() and Windows handle is used to replace the
> > directory fd.
> > > + *
> > > + *    Windows file system does not allow replacing a file or directory
> if
> > it is
> > > + *    referenced by a handle. Keep the handle open will lock and protect
> > the
> > > + *    parent directory and make the access to files atomically.
> > > + *
> > > + *    If we don't protect (lock) the parent directory, the parent
> > directory may
> > > + *    be replaced by others (e.g.: a symbolic link) and cause security
> > issues.
> > > + */
> > > +
> > > +#include "qemu/osdep.h"
> > > +#include "qapi/error.h"
> > > +#include "qemu/error-report.h"
> > > +#include "9p.h"
> > > +#include "9p-util.h"
> > > +#include "9p-local.h"
> > > +
> > > +#include <windows.h>
> > > +#include <dirent.h>
> > > +
> > > +#define V9FS_MAGIC  0x53465039  /* string "9PFS" */
> > > +
> > > +/*
> > > + * build_ads_name - construct Windows ADS name
> > > + *
> > > + * This function constructs Windows NTFS ADS (Alternate Data Streams)
> > > +name
> > > + * to <namebuf>.
> > > + */
> > > +static int build_ads_name(char *namebuf, size_t namebuf_len,
> > > +                          const char *filename, const char *ads_name)
> > > +{
> > > +    size_t total_size;
> > > +
> > > +    total_size = strlen(filename) + strlen(ads_name) + 2;
> > > +    if (total_size  > namebuf_len) {
> > > +        return -1;
> > > +    }
> > > +
> > > +    /*
> > > +     * NTFS ADS (Alternate Data Streams) name format: filename:ads_name
> > > +     * e.g.: D:\1.txt:my_ads_name
> > > +     */
> > > +
> > > +    strcpy(namebuf, filename);
> > > +    strcat(namebuf, ":");
> > > +    strcat(namebuf, ads_name);
> > > +
> > > +    return 0;
> > > +}
> > > +
> > > +/*
> > > + * copy_ads_name - copy ADS name from buffer returned by
> > > +FindNextStreamW()
> > > + *
> > > + * This function removes string "$DATA" in ADS name string returned
> > > +by
> > > + * FindNextStreamW(), and copies the real ADS name to <namebuf>.
> > > + */
> > > +static ssize_t copy_ads_name(char *namebuf, size_t namebuf_len,
> > > +                             char *full_ads_name) {
> > > +    char *p1, *p2;
> > > +
> > > +    /*
> > > +     * NTFS ADS (Alternate Data Streams) name from enumerate data
> format:
> > > +     * :ads_name:$DATA, e.g.: :my_ads_name:$DATA
> > > +     *
> > > +     * ADS name from FindNextStreamW() always has ":$DATA" string at the
> > end.
> > > +     *
> > > +     * This function copies ADS name to namebuf.
> > > +     */
> > > +
> > > +    p1 = strchr(full_ads_name, ':');
> > > +    if (p1 == NULL) {
> > > +        return -1;
> > > +    }
> > > +
> > > +    p2 = strchr(p1 + 1, ':');
> > > +    if (p2 == NULL) {
> > > +        return -1;
> > > +    }
> > > +
> > > +    /* skip empty ads name */
> > > +    if (p2 - p1 == 1) {
> > > +        return 0;
> > > +    }
> > > +
> > > +    if (p2 - p1 + 1 > namebuf_len) {
> > > +        return -1;
> > > +    }
> > > +
> > > +    memcpy(namebuf, p1 + 1, p2 - p1 - 1);
> > > +    namebuf[p2 - p1 - 1] = '\0';
> > > +
> > > +    return p2 - p1;
> > > +}
> > > +
> > > +/*
> > > + * get_full_path_win32 - get full file name base on a handle
> > > + *
> > > + * This function gets full file name based on a handle specified by
> > > +<fd> to
> > > + * a file or directory.
> > > + *
> > > + * Caller function needs to free the file name string after use.
> > > + */
> > > +char *get_full_path_win32(P9_FILE_ID fd, const char *name) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    DWORD total_size;
> > > +    DWORD name_size;
> > > +
> > > +    full_file_name = g_malloc0(NAME_MAX);
> > > +
> > > +    /* get parent directory full file name */
> > > +    name_size = GetFinalPathNameByHandle(fd, full_file_name,
> > > +                                         NAME_MAX - 1,
> > FILE_NAME_NORMALIZED);
> > > +    if (name_size == 0 || name_size > NAME_MAX - 1) {
> > > +        return NULL;
> > > +    }
> > > +
> > > +    /* full path returned is the "\\?\" syntax, remove the lead string
> */
> > > +    memmove(full_file_name, full_file_name + 4, NAME_MAX - 4);
> > > +
> > > +    if (name != NULL) {
> > > +        total_size = strlen(full_file_name) + strlen(name) + 2;
> > > +
> > > +        if (total_size > NAME_MAX) {
> > > +            return NULL;
> > > +        }
> > > +
> > > +        /* build sub-directory file name */
> > > +        strcat(full_file_name, "\\");
> > > +        strcat(full_file_name, name);
> > > +    }
> > > +
> > > +    return g_steal_pointer(&full_file_name); }
> > > +
> > > +/*
> > > + * fgetxattr_win32 - get extended attribute by fd
> > > + *
> > > + * This function gets extened attribute by <fd>. <fd> will be
> > > +translated to
> > > + * Windows handle.
> > > + *
> > > + * This function emulates extended attribute by NTFS ADS.
> > > + */
> > > +ssize_t fgetxattr_win32(int fd, const char *name, void *value, size_t
> > > +size) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    char ads_file_name[NAME_MAX + 1] = {0};
> > > +    DWORD dwBytesRead;
> > > +    HANDLE hStream;
> > > +    HANDLE hFile;
> > > +
> > > +    hFile = (HANDLE)_get_osfhandle(fd);
> > > +
> > > +    full_file_name = get_full_path_win32(hFile, NULL);
> > > +    if (full_file_name == NULL) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) <
> 0)
> > {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ,
> > NULL,
> > > +                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
> > > +    if (hStream == INVALID_HANDLE_VALUE &&
> > > +        GetLastError() == ERROR_FILE_NOT_FOUND) {
> > > +        errno = ENODATA;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
> > > +        errno = EIO;
> > > +        CloseHandle(hStream);
> > > +        return -1;
> > > +    }
> > > +
> > > +    CloseHandle(hStream);
> > > +
> > > +    return dwBytesRead;
> > > +}
> > > +
> > > +/*
> > > + * openat_win32 - emulate openat()
> > > + *
> > > + * This function emulates openat().
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So openat_win32() has to use a directory handle instead of a
> directory
> > fd.
> > > + *
> > > + * For symbolic access:
> > > + * 1. Parent directory handle <dirfd> should not be a symbolic link
> > because
> > > + *    it is opened by openat_dir() which can prevent from opening a link
> > to
> > > + *    a dirctory.
> > > + * 2. Link flag in <mode> is not set because Windows does not have this
> > flag.
> > > + *    Create a new symbolic link will be denied.
> > > + * 3. This function checks file symbolic link attribute after open.
> > > + *
> > > + * So symbolic link will not be accessed by 9p client.
> > > + */
> > > +P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int
> flags,
> > > +                        mode_t mode)
> > > +{
> > > +    g_autofree char *full_file_name1 = NULL;
> > > +    g_autofree char *full_file_name2 = NULL;
> > > +    HANDLE hFile = INVALID_HANDLE_VALUE;
> > > +    int fd;
> > > +
> > > +    full_file_name1 = get_full_path_win32(dirfd, pathname);
> > > +    if (full_file_name1 == NULL) {
> > > +        return hFile;
> > > +    }
> > > +
> > > +    fd = open(full_file_name1, flags, mode);
> > > +    if (fd > 0) {
> > > +        DWORD attribute;
> > > +        hFile = (HANDLE)_get_osfhandle(fd);
> > > +
> > > +        full_file_name2 = get_full_path_win32(hFile, NULL);
> > > +        attribute = GetFileAttributes(full_file_name2);
> > > +
> > > +        /* check if it is a symbolic link */
> > > +        if ((attribute == INVALID_FILE_ATTRIBUTES)
> > > +            || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > > +            errno = EACCES;
> > > +            hFile = INVALID_HANDLE_VALUE;
> > > +            close(fd);
> > > +        }
> > > +    }
> > > +
> > > +    return hFile;
> > > +}
> > > +
> > > +/*
> > > + * fstatat_win32 - emulate fstatat()
> > > + *
> > > + * This function emulates fstatat().
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So fstatat_win32() has to use a directory handle instead of a
> directory
> > fd.
> > > + *
> > > + * Access to a symbolic link will be denied to prevent security issues.
> > > + */
> > > +int fstatat_win32(P9_FILE_ID dirfd, const char *pathname,
> > > +                  struct stat *statbuf, int flags) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    HANDLE hFile = INVALID_HANDLE_VALUE;
> > > +    DWORD attribute;
> > > +    int err = 0;
> > > +    int ret = -1;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > > +    if (full_file_name == NULL) {
> > > +        return ret;
> > > +    }
> > > +
> > > +    /* open file to lock it */
> > > +    hFile = CreateFile(full_file_name, GENERIC_READ,
> > > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> > FILE_SHARE_DELETE,
> > > +                       NULL,
> > > +                       OPEN_EXISTING,
> > > +                       FILE_FLAG_BACKUP_SEMANTICS
> > > +                       | FILE_FLAG_OPEN_REPARSE_POINT,
> > > +                       NULL);
> > > +
> > > +    if (hFile == INVALID_HANDLE_VALUE) {
> > > +        err = EACCES;
> > > +        goto out;
> > > +    }
> > > +
> > > +    attribute = GetFileAttributes(full_file_name);
> > > +
> > > +    /* check if it is a symbolic link */
> > > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > > +        errno = EACCES;
> > > +        goto out;
> > > +    }
> > > +
> > > +    ret = stat(full_file_name, statbuf);
> > > +
> > > +out:
> > > +    if (hFile != INVALID_HANDLE_VALUE) {
> > > +        CloseHandle(hFile);
> > > +    }
> > > +
> > > +    if (err != 0) {
> > > +        errno = err;
> > > +    }
> > > +    return ret;
> > > +}
> > > +
> > > +/*
> > > + * mkdirat_win32 - emulate mkdirat()
> > > + *
> > > + * This function emulates mkdirat().
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So mkdirat_win32() has to use a directory handle instead of a
> directory
> > fd.
> > > + */
> > > +int mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t
> > > +mode) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    int ret = -1;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > > +    if (full_file_name == NULL) {
> > > +        return ret;
> > > +    }
> > > +
> > > +    ret = mkdir(full_file_name);
> > > +
> > > +    return ret;
> > > +}
> > > +
> > > +/*
> > > + * renameat_win32 - emulate renameat()
> > > + *
> > > + * This function emulates renameat().
> > > + *
> > > + * Windows POSIX API does not support openning a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So renameat_win32() has to use a directory handle instead of a
> > directory fd.
> > > + *
> > > + * Access to a symbolic link will be denied to prevent security issues.
> > > + */
> > > +int renameat_win32(HANDLE olddirfd, const char *oldpath,
> > > +                   HANDLE newdirfd, const char *newpath) {
> > > +    g_autofree char *full_old_name = NULL;
> > > +    g_autofree char *full_new_name = NULL;
> > > +    HANDLE hFile;
> > > +    DWORD attribute;
> > > +    int err = 0;
> > > +    int ret = -1;
> > > +
> > > +    full_old_name = get_full_path_win32(olddirfd, oldpath);
> > > +    full_new_name = get_full_path_win32(newdirfd, newpath);
> > > +    if (full_old_name == NULL || full_new_name == NULL) {
> > > +        return ret;
> > > +    }
> > > +
> > > +    /* open file to lock it */
> > > +    hFile = CreateFile(full_old_name, GENERIC_READ,
> > > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> > FILE_SHARE_DELETE,
> > > +                       NULL,
> > > +                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
> > > + NULL);
> > > +
> > > +    attribute = GetFileAttributes(full_old_name);
> > > +
> > > +    /* check if it is a symbolic link */
> > > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > > +        err = EACCES;
> > > +        goto out;
> > > +    }
> > > +
> > > +    CloseHandle(hFile);
> > > +
> > > +    ret = rename(full_old_name, full_new_name);
> > > +out:
> > > +    if (err != 0) {
> > > +        errno = err;
> > > +    }
> > > +    return ret;
> > > +}
> > > +
> > > +/*
> > > + * utimensat_win32 - emulate utimensat()
> > > + *
> > > + * This function emulates utimensat().
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So utimensat_win32() has to use a directory handle instead of a
> > directory fd.
> > > + *
> > > + * Access to a symbolic link will be denied to prevent security issues.
> > > + */
> > > +int utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
> > > +                    const struct timespec times[2], int flags) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    HANDLE hFile = INVALID_HANDLE_VALUE;
> > > +    DWORD attribute;
> > > +    struct utimbuf tm;
> > > +    int err = 0;
> > > +    int ret = -1;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > > +    if (full_file_name == NULL) {
> > > +        return ret;
> > > +    }
> > > +
> > > +    /* open file to lock it */
> > > +    hFile = CreateFile(full_file_name, GENERIC_READ,
> > > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> > FILE_SHARE_DELETE,
> > > +                       NULL,
> > > +                       OPEN_EXISTING,
> > > +                       FILE_FLAG_BACKUP_SEMANTICS
> > > +                       | FILE_FLAG_OPEN_REPARSE_POINT,
> > > +                       NULL);
> > > +
> > > +    if (hFile == INVALID_HANDLE_VALUE) {
> > > +        err = EACCES;
> > > +        goto out;
> > > +    }
> > > +
> > > +    attribute = GetFileAttributes(full_file_name);
> > > +
> > > +    /* check if it is a symbolic link */
> > > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > > +        errno = EACCES;
> > > +        goto out;
> > > +    }
> > > +
> > > +    tm.actime = times[0].tv_sec;
> > > +    tm.modtime = times[1].tv_sec;
> > > +
> > > +    ret = utime(full_file_name, &tm);
> > > +
> > > +out:
> > > +    if (hFile != INVALID_HANDLE_VALUE) {
> > > +        CloseHandle(hFile);
> > > +    }
> > > +
> > > +    if (err != 0) {
> > > +        errno = err;
> > > +    }
> > > +    return ret;
> > > +}
> > > +
> > > +/*
> > > + * unlinkat_win32 - emulate unlinkat()
> > > + *
> > > + * This function emulates unlinkat().
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So unlinkat_win32() has to use a directory handle instead of a
> > directory fd.
> > > + *
> > > + * Access to a symbolic link will be denied to prevent security issues.
> > > + */
> > > +
> > > +int unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags)
> > > +{
> > > +    g_autofree char *full_file_name = NULL;
> > > +    HANDLE hFile;
> > > +    DWORD attribute;
> > > +    int err = 0;
> > > +    int ret = -1;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, pathname);
> > > +    if (full_file_name == NULL) {
> > > +        return ret;
> > > +    }
> > > +
> > > +    /* open file to prevent other one modify it */
> > > +    hFile = CreateFile(full_file_name, GENERIC_READ,
> > > +                       FILE_SHARE_READ | FILE_SHARE_WRITE |
> > FILE_SHARE_DELETE,
> > > +                       NULL,
> > > +                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
> > > + NULL);
> > > +
> > > +    attribute = GetFileAttributes(full_file_name);
> > > +
> > > +    /* check if it is a symbolic link */
> > > +    if ((attribute == INVALID_FILE_ATTRIBUTES)
> > > +        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > > +        err = EACCES;
> > > +        goto out;
> > > +    }
> > > +
> > > +    if (flags == AT_REMOVEDIR) { /* remove directory */
> > > +        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
> > > +            err = ENOTDIR;
> > > +            goto out;
> > > +        }
> > > +        ret = rmdir(full_file_name);
> > > +    } else { /* remove regular file */
> > > +        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) != 0) {
> > > +            err = EISDIR;
> > > +            goto out;
> > > +        }
> > > +        ret = remove(full_file_name);
> > > +    }
> > > +
> > > +    /* after last handle closed, file will be removed */
> > > +    CloseHandle(hFile);
> > > +
> > > +out:
> > > +    if (err != 0) {
> > > +        errno = err;
> > > +    }
> > > +    return ret;
> > > +}
> > > +
> > > +/*
> > > + * statfs_win32 - statfs() on Windows
> > > + *
> > > + * This function emulates statfs() on Windows host.
> > > + */
> > > +int statfs_win32(const char *path, struct statfs *stbuf) {
> > > +    char RealPath[4] = { 0 };
> > > +    unsigned long SectorsPerCluster;
> > > +    unsigned long BytesPerSector;
> > > +    unsigned long NumberOfFreeClusters;
> > > +    unsigned long TotalNumberOfClusters;
> > > +
> > > +    /* only need first 3 bytes, e.g. "C:\ABC", only need "C:\" */
> > > +    memcpy(RealPath, path, 3);
> > > +
> > > +    if (GetDiskFreeSpace(RealPath, &SectorsPerCluster, &BytesPerSector,
> > > +                         &NumberOfFreeClusters, &TotalNumberOfClusters)
> ==
> > 0) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    stbuf->f_type = V9FS_MAGIC;
> > > +    stbuf->f_bsize =
> > > +        (__fsword_t)SectorsPerCluster * (__fsword_t)BytesPerSector;
> > > +    stbuf->f_blocks = (fsblkcnt_t)TotalNumberOfClusters;
> > > +    stbuf->f_bfree = (fsblkcnt_t)NumberOfFreeClusters;
> > > +    stbuf->f_bavail = (fsblkcnt_t)NumberOfFreeClusters;
> > > +    stbuf->f_files = -1;
> > > +    stbuf->f_ffree = -1;
> > > +    stbuf->f_namelen = NAME_MAX;
> > > +    stbuf->f_frsize = 0;
> > > +    stbuf->f_flags = 0;
> > > +
> > > +    return 0;
> > > +}
> > > +
> > > +/*
> > > + * openat_dir - emulate openat_dir()
> > > + *
> > > + * This function emulates openat_dir().
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * So openat_dir() has to use a directory handle instead of a directory
> > fd.
> > > + *
> > > + * Access to a symbolic link will be denied to prevent security issues.
> > > + */
> > > +P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    HANDLE hSubDir;
> > > +    DWORD attribute;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, name);
> > > +    if (full_file_name == NULL) {
> > > +        return INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    attribute = GetFileAttributes(full_file_name);
> > > +    if (attribute == INVALID_FILE_ATTRIBUTES) {
> > > +        return INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    /* check if it is a directory */
> > > +    if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
> > > +        return INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    /* do not allow opening a symbolic link */
> > > +    if ((attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
> > > +        return INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    /* open it */
> > > +    hSubDir = CreateFile(full_file_name, GENERIC_READ,
> > > +                         FILE_SHARE_READ | FILE_SHARE_WRITE |
> > FILE_SHARE_DELETE,
> > > +                         NULL,
> > > +                         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS,
> NULL);
> > > +    return hSubDir;
> > > +}
> > > +
> > > +P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
> > > +                       mode_t mode)
> > > +{
> > > +    return openat_win32(dirfd, name, flags | _O_BINARY, mode); }
> > > +
> > > +/*
> > > + * fgetxattrat_nofollow - get extended attribute
> > > + *
> > > + * This function gets extended attribute from file <path> in the
> > > +directory
> > > + * specified by <dirfd>. The extended atrribute name is specified by
> > > +<name>
> > > + * and return value will be put in <value>.
> > > + *
> > > + * This function emulates extended attribute by NTFS ADS.
> > > + */
> > > +ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
> > > +                             const char *name, void *value, size_t
> > > +size) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    char ads_file_name[NAME_MAX + 1] = { 0 };
> > > +    DWORD dwBytesRead;
> > > +    HANDLE hStream;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, path);
> > > +    if (full_file_name == NULL) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) <
> 0)
> > {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ,
> > NULL,
> > > +                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
> > > +    if (hStream == INVALID_HANDLE_VALUE &&
> > > +        GetLastError() == ERROR_FILE_NOT_FOUND) {
> > > +        errno = ENODATA;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
> > > +        errno = EIO;
> > > +        CloseHandle(hStream);
> > > +        return -1;
> > > +    }
> > > +
> > > +    CloseHandle(hStream);
> > > +
> > > +    return dwBytesRead;
> > > +}
> > > +
> > > +/*
> > > + * fsetxattrat_nofollow - set extended attribute
> > > + *
> > > + * This function set extended attribute to file <path> in the
> > > +directory
> > > + * specified by <dirfd>.
> > > + *
> > > + * This function emulates extended attribute by NTFS ADS.
> > > + */
> > > +
> > > +int fsetxattrat_nofollow(P9_FILE_ID dirfd, const char *path, const char
> > *name,
> > > +                         void *value, size_t size, int flags) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    char ads_file_name[NAME_MAX + 1] = { 0 };
> > > +    DWORD dwBytesWrite;
> > > +    HANDLE hStream;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, path);
> > > +    if (full_file_name == NULL) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) <
> 0)
> > {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    hStream = CreateFile(ads_file_name, GENERIC_WRITE, FILE_SHARE_READ,
> > NULL,
> > > +                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
> > > +    if (hStream == INVALID_HANDLE_VALUE) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (WriteFile(hStream, value, size, &dwBytesWrite, NULL) == FALSE) {
> > > +        errno = EIO;
> > > +        CloseHandle(hStream);
> > > +        return -1;
> > > +    }
> > > +
> > > +    CloseHandle(hStream);
> > > +
> > > +    return 0;
> > > +}
> > > +
> > > +/*
> > > + * flistxattrat_nofollow - list extended attribute
> > > + *
> > > + * This function gets extended attribute lists from file <filename>
> > > +in the
> > > + * directory specified by <dirfd>. Lists returned will be put in <list>.
> > > + *
> > > + * This function emulates extended attribute by NTFS ADS.
> > > + */
> > > +ssize_t flistxattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
> > > +                              char *list, size_t size) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    WCHAR WideCharStr[NAME_MAX + 1] = { 0 };
> > > +    char full_ads_name[NAME_MAX + 1];
> > > +    WIN32_FIND_STREAM_DATA fsd;
> > > +    BOOL bFindNext;
> > > +    char *list_ptr = list;
> > > +    size_t list_left_size = size;
> > > +    HANDLE hFind;
> > > +    int ret;
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, filename);
> > > +    if (full_file_name == NULL) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    /*
> > > +     * ADS enumerate function only has WCHAR version, so we need to
> > > +     * covert filename to utf-8 string.
> > > +     */
> > > +    ret = MultiByteToWideChar(CP_UTF8, 0, full_file_name,
> > > +                              strlen(full_file_name), WideCharStr,
> > NAME_MAX);
> > > +    if (ret == 0) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    hFind = FindFirstStreamW(WideCharStr, FindStreamInfoStandard, &fsd,
> > 0);
> > > +    if (hFind == INVALID_HANDLE_VALUE) {
> > > +        errno = ENODATA;
> > > +        return -1;
> > > +    }
> > > +
> > > +    do {
> > > +        memset(full_ads_name, 0, sizeof(full_ads_name));
> > > +
> > > +        /*
> > > +         * ADS enumerate function only has WCHAR version, so we need to
> > > +         * covert cStreamName to utf-8 string.
> > > +         */
> > > +        ret = WideCharToMultiByte(CP_UTF8, 0,
> > > +                                  fsd.cStreamName,
> wcslen(fsd.cStreamName)
> > + 1,
> > > +                                  full_ads_name, sizeof(full_ads_name) -
> > 1,
> > > +                                  NULL, NULL);
> > > +        if (ret == 0) {
> > > +            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
> > > +                errno = ERANGE;
> > > +            }
> > > +            CloseHandle(hFind);
> > > +            return -1;
> > > +        }
> > > +
> > > +        ret = copy_ads_name(list_ptr, list_left_size, full_ads_name);
> > > +        if (ret < 0) {
> > > +            errno = ERANGE;
> > > +            CloseHandle(hFind);
> > > +            return -1;
> > > +        }
> > > +
> > > +        list_ptr = list_ptr + ret;
> > > +        list_left_size = list_left_size - ret;
> > > +
> > > +        bFindNext = FindNextStreamW(hFind, &fsd);
> > > +    } while (bFindNext);
> > > +
> > > +    CloseHandle(hFind);
> > > +
> > > +    return size - list_left_size;
> > > +}
> > > +
> > > +/*
> > > + * fremovexattrat_nofollow - remove extended attribute
> > > + *
> > > + * This function removes an extended attribute from file <filename>
> > > +in the
> > > + * directory specified by <dirfd>.
> > > + *
> > > + * This function emulates extended attribute by NTFS ADS.
> > > + */
> > > +ssize_t fremovexattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
> > > +                                const char *name) {
> > > +    g_autofree char *full_file_name = NULL;
> > > +    char ads_file_name[NAME_MAX + 1] = { 0 };
> > > +
> > > +    full_file_name = get_full_path_win32(dirfd, filename);
> > > +    if (full_file_name == NULL) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (build_ads_name(ads_file_name, NAME_MAX, filename, name) < 0) {
> > > +        errno = EIO;
> > > +        return -1;
> > > +    }
> > > +
> > > +    if (DeleteFile(ads_file_name) != 0) {
> > > +        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
> > > +            errno = ENODATA;
> > > +            return -1;
> > > +        }
> > > +    }
> > > +
> > > +    return 0;
> > > +}
> > > +
> > > +/*
> > > + * local_opendir_nofollow - open a Windows directory
> > > + *
> > > + * This function returns a Windows file handle of the directory
> > > +specified by
> > > + * <dirpath> based on 9pfs mount point.
> > > + *
> > > + * Windows POSIX API does not support opening a directory by open().
> > > +Only
> > > + * handle of directory can be opened by CreateFile().
> > > + *
> > > + * This function checks the resolved path of <dirpath>. If the
> > > +resolved
> > > + * path is not in the scope of root directory (e.g. by symbolic
> > > +link), then
> > > + * this function will fail to prevent any security issues.
> > > + */
> > > +HANDLE local_opendir_nofollow(FsContext *fs_ctx, const char *dirpath)
> > > +{
> > > +    g_autofree char *full_file_name = NULL;
> > > +    LocalData *data = fs_ctx->private;
> > > +    HANDLE hDir;
> > > +
> > > +    hDir = openat_dir(data->mountfd, dirpath);
> > > +    if (hDir == INVALID_HANDLE_VALUE) {
> > > +        return INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    full_file_name = get_full_path_win32(hDir, NULL);
> > > +    if (full_file_name == NULL) {
> > > +        CloseHandle(hDir);
> > > +        return INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    /*
> > > +     * Check if the resolved path is in the root directory scope:
> > > +     * data->root_path and full_file_name are full path with symbolic
> > > +     * link resolved, so fs_ctx->root_path must be in the head of
> > > +     * full_file_name. If not, that means guest OS tries to open a file
> > not
> > > +     * in the scope of mount point. This operation should be denied.
> > > +     */
> > > +    if (memcmp(full_file_name, data->root_path,
> > > +               strlen(data->root_path)) != 0) {
> > > +        CloseHandle(hDir);
> > > +        hDir = INVALID_HANDLE_VALUE;
> > > +    }
> > > +
> > > +    return hDir;
> > > +}
> > >
> >
> >
Christian Schoenebeck Nov. 1, 2022, 6:22 p.m. UTC | #4
On Tuesday, November 1, 2022 4:20:53 PM CET Shi, Guohuai wrote:
> 
[...]
> > > > Windows POSIX API and MinGW library do not provide the NO_FOLLOW
> > > > flag, and do not allow opening a directory by POSIX open(). This
> > > > causes all
> > > > xxx_at() functions cannot work directly. However, we can provide
> > > > Windows handle based functions to emulate xxx_at() functions (e.g.:
> > > > openat_win32, utimensat_win32, etc.).
> > > >
> > > > Windows does not support extended attributes. 9pfs for Windows uses
> > > > NTFS ADS (Alternate Data Streams) to emulate extended attributes.
> > > >
> > > > Windows does not provide POSIX compatible readlink(), and symbolic
> > > > link feature in 9pfs will be disabled on Windows.
> > >
> > > Wouldn't it be more user friendly if the relevant error locations
> > > would use something like error_report_once() and suggesting to enable
> > > mapped(-xattr) to make 9p symlinks on guest working if desired by the user?
> > >
> > > Probably this error case would need to wrapped into a dedicated
> > > function, otherwise I guess error_report_once() would fire several
> > > times by different callers.
> > >
> > 
> > Windows (MinGW) does not only support symlink, but also does not have symlink
> > definitions.
> > Windows does not support symlink flags S_IFLNK.
> > 
> > So even I add symlink support by mapped-xattr, the MinGW library does not
> > have symlink flags and get a build error.
> > And this flags is defined by Windows header files.
> > The impact of adding a new flags to an pre-defined structure (struct stat) is
> > unknown.
> > 
> > So I think it is not a good idea to do that.
> 
> Because Windows does not support symlink, so error_report_once() and report it to user will be OK.
> But mapped-xattr could not work.

Showing an error makes sense for "passthrough" security model, but not for the
"mapped" one.

Just to avoid misapprehensions: are you aware that there is already a system-
agnostic implementation for symlinks if "mapped" is used?

When mapped security model is enabled, then creating symlinks on guest will
simply create a corresponding *regular* file on host and the content of that
regular file on host is the pointing path as raw string. Additionally the
symlink flag is added to "virtfs.mode" xattr to mark that regular file as a
symlink, a virtual one that is. So this does not require any support for
symlinks by either the underlying host file system, nor by host OS.

Likewise interpreting and walking those virtual symlinks in "mapped" mode is
also implemented in the local fs driver already.

Best regards,
Christian Schoenebeck
Shi, Guohuai Nov. 2, 2022, 3:07 a.m. UTC | #5
> -----Original Message-----
> From: Christian Schoenebeck <qemu_oss@crudebyte.com>
> Sent: Wednesday, November 2, 2022 02:22
> To: qemu-devel@nongnu.org
> Cc: Greg Kurz <groug@kaod.org>; Meng, Bin <Bin.Meng@windriver.com>; Shi,
> Guohuai <Guohuai.Shi@windriver.com>
> Subject: Re: [PATCH 07/16] hw/9pfs: Implement Windows specific utilities
> functions for 9pfs
> 
> [Please note: This e-mail is from an EXTERNAL e-mail address]
> 
> On Tuesday, November 1, 2022 4:20:53 PM CET Shi, Guohuai wrote:
> >
> [...]
> > > > > Windows POSIX API and MinGW library do not provide the NO_FOLLOW
> > > > > flag, and do not allow opening a directory by POSIX open(). This
> > > > > causes all
> > > > > xxx_at() functions cannot work directly. However, we can provide
> > > > > Windows handle based functions to emulate xxx_at() functions (e.g.:
> > > > > openat_win32, utimensat_win32, etc.).
> > > > >
> > > > > Windows does not support extended attributes. 9pfs for Windows
> > > > > uses NTFS ADS (Alternate Data Streams) to emulate extended
> attributes.
> > > > >
> > > > > Windows does not provide POSIX compatible readlink(), and
> > > > > symbolic link feature in 9pfs will be disabled on Windows.
> > > >
> > > > Wouldn't it be more user friendly if the relevant error locations
> > > > would use something like error_report_once() and suggesting to
> > > > enable
> > > > mapped(-xattr) to make 9p symlinks on guest working if desired by the
> user?
> > > >
> > > > Probably this error case would need to wrapped into a dedicated
> > > > function, otherwise I guess error_report_once() would fire several
> > > > times by different callers.
> > > >
> > >
> > > Windows (MinGW) does not only support symlink, but also does not
> > > have symlink definitions.
> > > Windows does not support symlink flags S_IFLNK.
> > >
> > > So even I add symlink support by mapped-xattr, the MinGW library
> > > does not have symlink flags and get a build error.
> > > And this flags is defined by Windows header files.
> > > The impact of adding a new flags to an pre-defined structure (struct
> > > stat) is unknown.
> > >
> > > So I think it is not a good idea to do that.
> >
> > Because Windows does not support symlink, so error_report_once() and report
> it to user will be OK.
> > But mapped-xattr could not work.
> 
> Showing an error makes sense for "passthrough" security model, but not for
> the "mapped" one.
> 
> Just to avoid misapprehensions: are you aware that there is already a system-
> agnostic implementation for symlinks if "mapped" is used?
> 
> When mapped security model is enabled, then creating symlinks on guest will
> simply create a corresponding *regular* file on host and the content of that
> regular file on host is the pointing path as raw string. Additionally the
> symlink flag is added to "virtfs.mode" xattr to mark that regular file as a
> symlink, a virtual one that is. So this does not require any support for
> symlinks by either the underlying host file system, nor by host OS.
> 
> Likewise interpreting and walking those virtual symlinks in "mapped" mode is
> also implemented in the local fs driver already.

Yes, symlink can be supported by "mapped" mode.
I mean that MinGW does not provide symlink mode flags "S_IFLNK" and some other related functions and defines.
You can check with "9p.c": S_ISLNK, S_ISSOCK and S_ISFIFO are not valid on Windows (MinGW) host.
So even I enabled symlink supported by "mapped" mode on local-agent code, "9p.c" can not be built.

So I disabled symlink totally, because MinGW interface does not support it.

To resolve this issue, MinGW should add the missing defines at first.

> 
> Best regards,
> Christian Schoenebeck
> 


Thanks
Guohuai
Christian Schoenebeck Nov. 2, 2022, 11:05 a.m. UTC | #6
On Wednesday, November 2, 2022 4:07:35 AM CET Shi, Guohuai wrote:
> 
> > -----Original Message-----
> > From: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > Sent: Wednesday, November 2, 2022 02:22
> > To: qemu-devel@nongnu.org
> > Cc: Greg Kurz <groug@kaod.org>; Meng, Bin <Bin.Meng@windriver.com>; Shi,
> > Guohuai <Guohuai.Shi@windriver.com>
> > Subject: Re: [PATCH 07/16] hw/9pfs: Implement Windows specific utilities
> > functions for 9pfs
> > 
> > [Please note: This e-mail is from an EXTERNAL e-mail address]
> > 
> > On Tuesday, November 1, 2022 4:20:53 PM CET Shi, Guohuai wrote:
> > >
> > [...]
> > > > > > Windows POSIX API and MinGW library do not provide the NO_FOLLOW
> > > > > > flag, and do not allow opening a directory by POSIX open(). This
> > > > > > causes all
> > > > > > xxx_at() functions cannot work directly. However, we can provide
> > > > > > Windows handle based functions to emulate xxx_at() functions (e.g.:
> > > > > > openat_win32, utimensat_win32, etc.).
> > > > > >
> > > > > > Windows does not support extended attributes. 9pfs for Windows
> > > > > > uses NTFS ADS (Alternate Data Streams) to emulate extended
> > attributes.
> > > > > >
> > > > > > Windows does not provide POSIX compatible readlink(), and
> > > > > > symbolic link feature in 9pfs will be disabled on Windows.
> > > > >
> > > > > Wouldn't it be more user friendly if the relevant error locations
> > > > > would use something like error_report_once() and suggesting to
> > > > > enable
> > > > > mapped(-xattr) to make 9p symlinks on guest working if desired by the
> > user?
> > > > >
> > > > > Probably this error case would need to wrapped into a dedicated
> > > > > function, otherwise I guess error_report_once() would fire several
> > > > > times by different callers.
> > > > >
> > > >
> > > > Windows (MinGW) does not only support symlink, but also does not
> > > > have symlink definitions.
> > > > Windows does not support symlink flags S_IFLNK.
> > > >
> > > > So even I add symlink support by mapped-xattr, the MinGW library
> > > > does not have symlink flags and get a build error.
> > > > And this flags is defined by Windows header files.
> > > > The impact of adding a new flags to an pre-defined structure (struct
> > > > stat) is unknown.
> > > >
> > > > So I think it is not a good idea to do that.
> > >
> > > Because Windows does not support symlink, so error_report_once() and report
> > it to user will be OK.
> > > But mapped-xattr could not work.
> > 
> > Showing an error makes sense for "passthrough" security model, but not for
> > the "mapped" one.
> > 
> > Just to avoid misapprehensions: are you aware that there is already a system-
> > agnostic implementation for symlinks if "mapped" is used?
> > 
> > When mapped security model is enabled, then creating symlinks on guest will
> > simply create a corresponding *regular* file on host and the content of that
> > regular file on host is the pointing path as raw string. Additionally the
> > symlink flag is added to "virtfs.mode" xattr to mark that regular file as a
> > symlink, a virtual one that is. So this does not require any support for
> > symlinks by either the underlying host file system, nor by host OS.
> > 
> > Likewise interpreting and walking those virtual symlinks in "mapped" mode is
> > also implemented in the local fs driver already.
> 
> Yes, symlink can be supported by "mapped" mode.
> I mean that MinGW does not provide symlink mode flags "S_IFLNK" and some other related functions and defines.
> You can check with "9p.c": S_ISLNK, S_ISSOCK and S_ISFIFO are not valid on Windows (MinGW) host.
> So even I enabled symlink supported by "mapped" mode on local-agent code, "9p.c" can not be built.
> 
> So I disabled symlink totally, because MinGW interface does not support it.
> 
> To resolve this issue, MinGW should add the missing defines at first.

And what's wrong with something like the following?

#ifdef CONFIG_WIN32
...
#ifndef S_ISLNK
#define S_ISLNK(x) ...
#endif
...
#endif

Best regards,
Christian Schoenebeck
Shi, Guohuai Nov. 2, 2022, 11:28 a.m. UTC | #7
> -----Original Message-----
> From: Christian Schoenebeck <qemu_oss@crudebyte.com>
> Sent: Wednesday, November 2, 2022 19:06
> To: qemu-devel@nongnu.org
> Cc: Greg Kurz <groug@kaod.org>; Meng, Bin <Bin.Meng@windriver.com>; Shi,
> Guohuai <Guohuai.Shi@windriver.com>
> Subject: Re: [PATCH 07/16] hw/9pfs: Implement Windows specific utilities
> functions for 9pfs
> 
> CAUTION: This email comes from a non Wind River email account!
> Do not click links or open attachments unless you recognize the sender and
> know the content is safe.
> 
> On Wednesday, November 2, 2022 4:07:35 AM CET Shi, Guohuai wrote:
> >
> > > -----Original Message-----
> > > From: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > Sent: Wednesday, November 2, 2022 02:22
> > > To: qemu-devel@nongnu.org
> > > Cc: Greg Kurz <groug@kaod.org>; Meng, Bin <Bin.Meng@windriver.com>;
> > > Shi, Guohuai <Guohuai.Shi@windriver.com>
> > > Subject: Re: [PATCH 07/16] hw/9pfs: Implement Windows specific
> > > utilities functions for 9pfs
> > >
> > > [Please note: This e-mail is from an EXTERNAL e-mail address]
> > >
> > > On Tuesday, November 1, 2022 4:20:53 PM CET Shi, Guohuai wrote:
> > > >
> > > [...]
> > > > > > > Windows POSIX API and MinGW library do not provide the
> > > > > > > NO_FOLLOW flag, and do not allow opening a directory by
> > > > > > > POSIX open(). This causes all
> > > > > > > xxx_at() functions cannot work directly. However, we can
> > > > > > > provide Windows handle based functions to emulate xxx_at()
> functions (e.g.:
> > > > > > > openat_win32, utimensat_win32, etc.).
> > > > > > >
> > > > > > > Windows does not support extended attributes. 9pfs for
> > > > > > > Windows uses NTFS ADS (Alternate Data Streams) to emulate
> > > > > > > extended
> > > attributes.
> > > > > > >
> > > > > > > Windows does not provide POSIX compatible readlink(), and
> > > > > > > symbolic link feature in 9pfs will be disabled on Windows.
> > > > > >
> > > > > > Wouldn't it be more user friendly if the relevant error
> > > > > > locations would use something like error_report_once() and
> > > > > > suggesting to enable
> > > > > > mapped(-xattr) to make 9p symlinks on guest working if desired
> > > > > > by the
> > > user?
> > > > > >
> > > > > > Probably this error case would need to wrapped into a
> > > > > > dedicated function, otherwise I guess error_report_once()
> > > > > > would fire several times by different callers.
> > > > > >
> > > > >
> > > > > Windows (MinGW) does not only support symlink, but also does not
> > > > > have symlink definitions.
> > > > > Windows does not support symlink flags S_IFLNK.
> > > > >
> > > > > So even I add symlink support by mapped-xattr, the MinGW library
> > > > > does not have symlink flags and get a build error.
> > > > > And this flags is defined by Windows header files.
> > > > > The impact of adding a new flags to an pre-defined structure
> > > > > (struct
> > > > > stat) is unknown.
> > > > >
> > > > > So I think it is not a good idea to do that.
> > > >
> > > > Because Windows does not support symlink, so error_report_once()
> > > > and report
> > > it to user will be OK.
> > > > But mapped-xattr could not work.
> > >
> > > Showing an error makes sense for "passthrough" security model, but
> > > not for the "mapped" one.
> > >
> > > Just to avoid misapprehensions: are you aware that there is already
> > > a system- agnostic implementation for symlinks if "mapped" is used?
> > >
> > > When mapped security model is enabled, then creating symlinks on
> > > guest will simply create a corresponding *regular* file on host and
> > > the content of that regular file on host is the pointing path as raw
> > > string. Additionally the symlink flag is added to "virtfs.mode"
> > > xattr to mark that regular file as a symlink, a virtual one that is.
> > > So this does not require any support for symlinks by either the
> underlying host file system, nor by host OS.
> > >
> > > Likewise interpreting and walking those virtual symlinks in "mapped"
> > > mode is also implemented in the local fs driver already.
> >
> > Yes, symlink can be supported by "mapped" mode.
> > I mean that MinGW does not provide symlink mode flags "S_IFLNK" and some
> other related functions and defines.
> > You can check with "9p.c": S_ISLNK, S_ISSOCK and S_ISFIFO are not valid on
> Windows (MinGW) host.
> > So even I enabled symlink supported by "mapped" mode on local-agent code,
> "9p.c" can not be built.
> >
> > So I disabled symlink totally, because MinGW interface does not support it.
> >
> > To resolve this issue, MinGW should add the missing defines at first.
> 
> And what's wrong with something like the following?
> 
> #ifdef CONFIG_WIN32
> ...
> #ifndef S_ISLNK
> #define S_ISLNK(x) ...
> #endif
> ...
> #endif
> 

It is OK to add this just for current solution.
My concern is:
mode_t is a 16-bit value which store permission value in lower 12-bit and file type in higher 4-bit.
Windows does not document the other value for file type defines:

// from MS SDK header file:

#define _S_IFMT   0xF000 // File type mask
#define _S_IFDIR  0x4000 // Directory
#define _S_IFCHR  0x2000 // Character special
#define _S_IFIFO  0x1000 // Pipe
#define _S_IFREG  0x8000 // Regular

If we add a new type, is it a risk that the un-document value may be used by Windows internally and cause some compatible issue?
Or if Windows use this some values in the future cause conflict?

Thanks
Guohuai

> Best regards,
> Christian Schoenebeck
>
Christian Schoenebeck Nov. 2, 2022, 11:51 a.m. UTC | #8
On Wednesday, November 2, 2022 12:28:08 PM CET Shi, Guohuai wrote:
> 
[...]
> > >
> > > Yes, symlink can be supported by "mapped" mode.
> > > I mean that MinGW does not provide symlink mode flags "S_IFLNK" and some
> > other related functions and defines.
> > > You can check with "9p.c": S_ISLNK, S_ISSOCK and S_ISFIFO are not valid on
> > Windows (MinGW) host.
> > > So even I enabled symlink supported by "mapped" mode on local-agent code,
> > "9p.c" can not be built.
> > >
> > > So I disabled symlink totally, because MinGW interface does not support it.
> > >
> > > To resolve this issue, MinGW should add the missing defines at first.
> > 
> > And what's wrong with something like the following?
> > 
> > #ifdef CONFIG_WIN32
> > ...
> > #ifndef S_ISLNK
> > #define S_ISLNK(x) ...
> > #endif
> > ...
> > #endif
> > 
> 
> It is OK to add this just for current solution.
> My concern is:
> mode_t is a 16-bit value which store permission value in lower 12-bit and file type in higher 4-bit.
> Windows does not document the other value for file type defines:
> 
> // from MS SDK header file:
> 
> #define _S_IFMT   0xF000 // File type mask
> #define _S_IFDIR  0x4000 // Directory
> #define _S_IFCHR  0x2000 // Character special
> #define _S_IFIFO  0x1000 // Pipe
> #define _S_IFREG  0x8000 // Regular
> 
> If we add a new type, is it a risk that the un-document value may be used by Windows internally and cause some compatible issue?
> Or if Windows use this some values in the future cause conflict?

We don't have a crystal ball, but there are ways to handle this responsibly:
At compile-time you could error out badly if there is anything we don't
expect, like all of a sudden some of the macros we explicitly define for
Windows are unexpectly there, and telling developers, hey somebody should look
at this.

And at runtime you could add an assert() if some these values are all of a
sudden filled. Because that's what we currently don't expect, as we are
occupying them.

Best regards,
Christian Schoenebeck
Christian Schoenebeck Nov. 2, 2022, 12:06 p.m. UTC | #9
On Wednesday, November 2, 2022 12:51:23 PM CET Christian Schoenebeck wrote:
> On Wednesday, November 2, 2022 12:28:08 PM CET Shi, Guohuai wrote:
> > 
> [...]
> > > >
> > > > Yes, symlink can be supported by "mapped" mode.
> > > > I mean that MinGW does not provide symlink mode flags "S_IFLNK" and some
> > > other related functions and defines.
> > > > You can check with "9p.c": S_ISLNK, S_ISSOCK and S_ISFIFO are not valid on
> > > Windows (MinGW) host.
> > > > So even I enabled symlink supported by "mapped" mode on local-agent code,
> > > "9p.c" can not be built.
> > > >
> > > > So I disabled symlink totally, because MinGW interface does not support it.
> > > >
> > > > To resolve this issue, MinGW should add the missing defines at first.
> > > 
> > > And what's wrong with something like the following?
> > > 
> > > #ifdef CONFIG_WIN32
> > > ...
> > > #ifndef S_ISLNK
> > > #define S_ISLNK(x) ...
> > > #endif
> > > ...
> > > #endif
> > > 
> > 
> > It is OK to add this just for current solution.
> > My concern is:
> > mode_t is a 16-bit value which store permission value in lower 12-bit and file type in higher 4-bit.
> > Windows does not document the other value for file type defines:
> > 
> > // from MS SDK header file:
> > 
> > #define _S_IFMT   0xF000 // File type mask
> > #define _S_IFDIR  0x4000 // Directory
> > #define _S_IFCHR  0x2000 // Character special
> > #define _S_IFIFO  0x1000 // Pipe
> > #define _S_IFREG  0x8000 // Regular
> > 
> > If we add a new type, is it a risk that the un-document value may be used by Windows internally and cause some compatible issue?
> > Or if Windows use this some values in the future cause conflict?
> 
> We don't have a crystal ball, but there are ways to handle this responsibly:
> At compile-time you could error out badly if there is anything we don't
> expect, like all of a sudden some of the macros we explicitly define for
> Windows are unexpectly there, and telling developers, hey somebody should look
> at this.
> 
> And at runtime you could add an assert() if some these values are all of a
> sudden filled. Because that's what we currently don't expect, as we are
> occupying them.

On a second thought, as far as the runtime aspect is concerned: killing QEMU
with assert() is probably overkill and unnecessarily too harsh. What can
happen if Windows starts to use the new bits for some purpose? As long as you
explicitly clear those bits out after retrieval from Windows, we can still use
them for our purposes. And instead you could simply log one less drastical
warning once to the user.

Does that make sense?

Best regards,
Christian Schoenebeck
diff mbox series

Patch

diff --git a/hw/9pfs/9p-local.h b/hw/9pfs/9p-local.h
index c8404063e5..02fd894ba3 100644
--- a/hw/9pfs/9p-local.h
+++ b/hw/9pfs/9p-local.h
@@ -15,6 +15,13 @@ 
 
 #include "9p-file-id.h"
 
+typedef struct {
+    P9_FILE_ID mountfd;
+#ifdef CONFIG_WIN32
+    char *root_path;
+#endif
+} LocalData;
+
 P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
                                mode_t mode);
 P9_FILE_ID local_opendir_nofollow(FsContext *fs_ctx, const char *path);
diff --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h
index 1e7dc76345..82b2d0c3e4 100644
--- a/hw/9pfs/9p-util.h
+++ b/hw/9pfs/9p-util.h
@@ -90,26 +90,61 @@  static inline int errno_to_dotl(int err) {
     return err;
 }
 
-#ifdef CONFIG_DARWIN
+#if defined(CONFIG_DARWIN)
 #define qemu_fgetxattr(...) fgetxattr(__VA_ARGS__, 0, 0)
+#elif defined(CONFIG_WIN32)
+#define qemu_fgetxattr fgetxattr_win32
 #else
 #define qemu_fgetxattr fgetxattr
 #endif
 
+#ifdef CONFIG_WIN32
+#define qemu_openat     openat_win32
+#define qemu_fstatat    fstatat_win32
+#define qemu_mkdirat    mkdirat_win32
+#define qemu_renameat   renameat_win32
+#define qemu_utimensat  utimensat_win32
+#define qemu_unlinkat   unlinkat_win32
+#else
 #define qemu_openat     openat
 #define qemu_fstatat    fstatat
 #define qemu_mkdirat    mkdirat
 #define qemu_renameat   renameat
 #define qemu_utimensat  utimensat
 #define qemu_unlinkat   unlinkat
+#endif
+
+#ifdef CONFIG_WIN32
+char *get_full_path_win32(P9_FILE_ID fd, const char *name);
+ssize_t fgetxattr_win32(int fd, const char *name, void *value, size_t size);
+P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int flags,
+                        mode_t mode);
+int fstatat_win32(P9_FILE_ID dirfd, const char *pathname,
+                  struct stat *statbuf, int flags);
+int mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t mode);
+int renameat_win32(P9_FILE_ID olddirfd, const char *oldpath,
+                   P9_FILE_ID newdirfd, const char *newpath);
+int utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
+                    const struct timespec times[2], int flags);
+int unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags);
+int statfs_win32(const char *root_path, struct statfs *stbuf);
+P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name);
+P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
+                       mode_t mode);
+#endif
 
 static inline void close_preserve_errno(P9_FILE_ID fd)
 {
     int serrno = errno;
+#ifndef CONFIG_WIN32
     close(fd);
+#else
+    CloseHandle(fd);
+#endif
     errno = serrno;
 }
 
+#ifndef CONFIG_WIN32
 static inline P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name)
 {
     return qemu_openat(dirfd, name,
@@ -157,6 +192,7 @@  again:
     errno = serrno;
     return fd;
 }
+#endif
 
 ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
                              const char *name, void *value, size_t size);
@@ -167,6 +203,7 @@  ssize_t flistxattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
 ssize_t fremovexattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
                                 const char *name);
 
+#ifndef CONFIG_WIN32
 /*
  * Darwin has d_seekoff, which appears to function similarly to d_off.
  * However, it does not appear to be supported on all file systems,
@@ -181,6 +218,7 @@  static inline off_t qemu_dirent_off(struct dirent *dent)
     return dent->d_off;
 #endif
 }
+#endif /* !CONFIG_WIN32 */
 
 /**
  * qemu_dirent_dup() - Duplicate directory entry @dent.
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 7e8d8492ea..d0d6d93549 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -53,10 +53,6 @@ 
 #define BTRFS_SUPER_MAGIC 0x9123683E
 #endif
 
-typedef struct {
-    P9_FILE_ID mountfd;
-} LocalData;
-
 P9_FILE_ID local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
                                mode_t mode)
 {
diff --git a/hw/9pfs/9p-util-win32.c b/hw/9pfs/9p-util-win32.c
new file mode 100644
index 0000000000..953e7da6fa
--- /dev/null
+++ b/hw/9pfs/9p-util-win32.c
@@ -0,0 +1,885 @@ 
+/*
+ * 9p utilities (Windows Implementation)
+ *
+ * Copyright (c) 2022 Wind River Systems, Inc.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * This file contains Windows only functions for 9pfs.
+ *
+ * For 9pfs Windows host, the following features are different from Linux host:
+ *
+ * 1. Windows POSIX API does not provide the NO_FOLLOW flag, that means MinGW
+ *    cannot detect if a path is a symbolic link or not. Also Windows do not
+ *    provide POSIX compatible readlink(). Supporting symbolic link in 9pfs on
+ *    Windows may cause security issues, so symbolic link support is disabled
+ *    completely.
+ *
+ * 2. Windows file system does not support extended attributes directly. 9pfs
+ *    for Windows uses NTFS ADS (Alternate Data Streams) to emulate extended
+ *    attributes.
+ *
+ * 3. statfs() is not available on Windows. qemu_statfs() is used to emulate it.
+ *
+ * 4. On Windows trying to open a directory with the open() API will fail.
+ *    This is because Windows does not allow opening directory in normal usage.
+ *
+ *    As a result of this, all xxx_at() functions won't work directly on
+ *    Windows, e.g.: openat(), unlinkat(), etc.
+ *
+ *    As xxx_at() can prevent parent directory to be modified on Linux host,
+ *    to support this and prevent security issue, all xxx_at() APIs are replaced
+ *    by xxx_at_win32() and Windows handle is used to replace the directory fd.
+ *
+ *    Windows file system does not allow replacing a file or directory if it is
+ *    referenced by a handle. Keep the handle open will lock and protect the
+ *    parent directory and make the access to files atomically.
+ *
+ *    If we don't protect (lock) the parent directory, the parent directory may
+ *    be replaced by others (e.g.: a symbolic link) and cause security issues.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "9p.h"
+#include "9p-util.h"
+#include "9p-local.h"
+
+#include <windows.h>
+#include <dirent.h>
+
+#define V9FS_MAGIC  0x53465039  /* string "9PFS" */
+
+/*
+ * build_ads_name - construct Windows ADS name
+ *
+ * This function constructs Windows NTFS ADS (Alternate Data Streams) name
+ * to <namebuf>.
+ */
+static int build_ads_name(char *namebuf, size_t namebuf_len,
+                          const char *filename, const char *ads_name)
+{
+    size_t total_size;
+
+    total_size = strlen(filename) + strlen(ads_name) + 2;
+    if (total_size  > namebuf_len) {
+        return -1;
+    }
+
+    /*
+     * NTFS ADS (Alternate Data Streams) name format: filename:ads_name
+     * e.g.: D:\1.txt:my_ads_name
+     */
+
+    strcpy(namebuf, filename);
+    strcat(namebuf, ":");
+    strcat(namebuf, ads_name);
+
+    return 0;
+}
+
+/*
+ * copy_ads_name - copy ADS name from buffer returned by FindNextStreamW()
+ *
+ * This function removes string "$DATA" in ADS name string returned by
+ * FindNextStreamW(), and copies the real ADS name to <namebuf>.
+ */
+static ssize_t copy_ads_name(char *namebuf, size_t namebuf_len,
+                             char *full_ads_name)
+{
+    char *p1, *p2;
+
+    /*
+     * NTFS ADS (Alternate Data Streams) name from enumerate data format:
+     * :ads_name:$DATA, e.g.: :my_ads_name:$DATA
+     *
+     * ADS name from FindNextStreamW() always has ":$DATA" string at the end.
+     *
+     * This function copies ADS name to namebuf.
+     */
+
+    p1 = strchr(full_ads_name, ':');
+    if (p1 == NULL) {
+        return -1;
+    }
+
+    p2 = strchr(p1 + 1, ':');
+    if (p2 == NULL) {
+        return -1;
+    }
+
+    /* skip empty ads name */
+    if (p2 - p1 == 1) {
+        return 0;
+    }
+
+    if (p2 - p1 + 1 > namebuf_len) {
+        return -1;
+    }
+
+    memcpy(namebuf, p1 + 1, p2 - p1 - 1);
+    namebuf[p2 - p1 - 1] = '\0';
+
+    return p2 - p1;
+}
+
+/*
+ * get_full_path_win32 - get full file name base on a handle
+ *
+ * This function gets full file name based on a handle specified by <fd> to
+ * a file or directory.
+ *
+ * Caller function needs to free the file name string after use.
+ */
+char *get_full_path_win32(P9_FILE_ID fd, const char *name)
+{
+    g_autofree char *full_file_name = NULL;
+    DWORD total_size;
+    DWORD name_size;
+
+    full_file_name = g_malloc0(NAME_MAX);
+
+    /* get parent directory full file name */
+    name_size = GetFinalPathNameByHandle(fd, full_file_name,
+                                         NAME_MAX - 1, FILE_NAME_NORMALIZED);
+    if (name_size == 0 || name_size > NAME_MAX - 1) {
+        return NULL;
+    }
+
+    /* full path returned is the "\\?\" syntax, remove the lead string */
+    memmove(full_file_name, full_file_name + 4, NAME_MAX - 4);
+
+    if (name != NULL) {
+        total_size = strlen(full_file_name) + strlen(name) + 2;
+
+        if (total_size > NAME_MAX) {
+            return NULL;
+        }
+
+        /* build sub-directory file name */
+        strcat(full_file_name, "\\");
+        strcat(full_file_name, name);
+    }
+
+    return g_steal_pointer(&full_file_name);
+}
+
+/*
+ * fgetxattr_win32 - get extended attribute by fd
+ *
+ * This function gets extened attribute by <fd>. <fd> will be translated to
+ * Windows handle.
+ *
+ * This function emulates extended attribute by NTFS ADS.
+ */
+ssize_t fgetxattr_win32(int fd, const char *name, void *value, size_t size)
+{
+    g_autofree char *full_file_name = NULL;
+    char ads_file_name[NAME_MAX + 1] = {0};
+    DWORD dwBytesRead;
+    HANDLE hStream;
+    HANDLE hFile;
+
+    hFile = (HANDLE)_get_osfhandle(fd);
+
+    full_file_name = get_full_path_win32(hFile, NULL);
+    if (full_file_name == NULL) {
+        errno = EIO;
+        return -1;
+    }
+
+    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0) {
+        errno = EIO;
+        return -1;
+    }
+
+    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ, NULL,
+                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (hStream == INVALID_HANDLE_VALUE &&
+        GetLastError() == ERROR_FILE_NOT_FOUND) {
+        errno = ENODATA;
+        return -1;
+    }
+
+    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
+        errno = EIO;
+        CloseHandle(hStream);
+        return -1;
+    }
+
+    CloseHandle(hStream);
+
+    return dwBytesRead;
+}
+
+/*
+ * openat_win32 - emulate openat()
+ *
+ * This function emulates openat().
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So openat_win32() has to use a directory handle instead of a directory fd.
+ *
+ * For symbolic access:
+ * 1. Parent directory handle <dirfd> should not be a symbolic link because
+ *    it is opened by openat_dir() which can prevent from opening a link to
+ *    a dirctory.
+ * 2. Link flag in <mode> is not set because Windows does not have this flag.
+ *    Create a new symbolic link will be denied.
+ * 3. This function checks file symbolic link attribute after open.
+ *
+ * So symbolic link will not be accessed by 9p client.
+ */
+P9_FILE_ID openat_win32(P9_FILE_ID dirfd, const char *pathname, int flags,
+                        mode_t mode)
+{
+    g_autofree char *full_file_name1 = NULL;
+    g_autofree char *full_file_name2 = NULL;
+    HANDLE hFile = INVALID_HANDLE_VALUE;
+    int fd;
+
+    full_file_name1 = get_full_path_win32(dirfd, pathname);
+    if (full_file_name1 == NULL) {
+        return hFile;
+    }
+
+    fd = open(full_file_name1, flags, mode);
+    if (fd > 0) {
+        DWORD attribute;
+        hFile = (HANDLE)_get_osfhandle(fd);
+
+        full_file_name2 = get_full_path_win32(hFile, NULL);
+        attribute = GetFileAttributes(full_file_name2);
+
+        /* check if it is a symbolic link */
+        if ((attribute == INVALID_FILE_ATTRIBUTES)
+            || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+            errno = EACCES;
+            hFile = INVALID_HANDLE_VALUE;
+            close(fd);
+        }
+    }
+
+    return hFile;
+}
+
+/*
+ * fstatat_win32 - emulate fstatat()
+ *
+ * This function emulates fstatat().
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So fstatat_win32() has to use a directory handle instead of a directory fd.
+ *
+ * Access to a symbolic link will be denied to prevent security issues.
+ */
+int fstatat_win32(P9_FILE_ID dirfd, const char *pathname,
+                  struct stat *statbuf, int flags)
+{
+    g_autofree char *full_file_name = NULL;
+    HANDLE hFile = INVALID_HANDLE_VALUE;
+    DWORD attribute;
+    int err = 0;
+    int ret = -1;
+
+    full_file_name = get_full_path_win32(dirfd, pathname);
+    if (full_file_name == NULL) {
+        return ret;
+    }
+
+    /* open file to lock it */
+    hFile = CreateFile(full_file_name, GENERIC_READ,
+                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                       NULL,
+                       OPEN_EXISTING,
+                       FILE_FLAG_BACKUP_SEMANTICS
+                       | FILE_FLAG_OPEN_REPARSE_POINT,
+                       NULL);
+
+    if (hFile == INVALID_HANDLE_VALUE) {
+        err = EACCES;
+        goto out;
+    }
+
+    attribute = GetFileAttributes(full_file_name);
+
+    /* check if it is a symbolic link */
+    if ((attribute == INVALID_FILE_ATTRIBUTES)
+        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+        errno = EACCES;
+        goto out;
+    }
+
+    ret = stat(full_file_name, statbuf);
+
+out:
+    if (hFile != INVALID_HANDLE_VALUE) {
+        CloseHandle(hFile);
+    }
+
+    if (err != 0) {
+        errno = err;
+    }
+    return ret;
+}
+
+/*
+ * mkdirat_win32 - emulate mkdirat()
+ *
+ * This function emulates mkdirat().
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So mkdirat_win32() has to use a directory handle instead of a directory fd.
+ */
+int mkdirat_win32(P9_FILE_ID dirfd, const char *pathname, mode_t mode)
+{
+    g_autofree char *full_file_name = NULL;
+    int ret = -1;
+
+    full_file_name = get_full_path_win32(dirfd, pathname);
+    if (full_file_name == NULL) {
+        return ret;
+    }
+
+    ret = mkdir(full_file_name);
+
+    return ret;
+}
+
+/*
+ * renameat_win32 - emulate renameat()
+ *
+ * This function emulates renameat().
+ *
+ * Windows POSIX API does not support openning a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So renameat_win32() has to use a directory handle instead of a directory fd.
+ *
+ * Access to a symbolic link will be denied to prevent security issues.
+ */
+int renameat_win32(HANDLE olddirfd, const char *oldpath,
+                   HANDLE newdirfd, const char *newpath)
+{
+    g_autofree char *full_old_name = NULL;
+    g_autofree char *full_new_name = NULL;
+    HANDLE hFile;
+    DWORD attribute;
+    int err = 0;
+    int ret = -1;
+
+    full_old_name = get_full_path_win32(olddirfd, oldpath);
+    full_new_name = get_full_path_win32(newdirfd, newpath);
+    if (full_old_name == NULL || full_new_name == NULL) {
+        return ret;
+    }
+
+    /* open file to lock it */
+    hFile = CreateFile(full_old_name, GENERIC_READ,
+                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                       NULL,
+                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+    attribute = GetFileAttributes(full_old_name);
+
+    /* check if it is a symbolic link */
+    if ((attribute == INVALID_FILE_ATTRIBUTES)
+        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+        err = EACCES;
+        goto out;
+    }
+
+    CloseHandle(hFile);
+
+    ret = rename(full_old_name, full_new_name);
+out:
+    if (err != 0) {
+        errno = err;
+    }
+    return ret;
+}
+
+/*
+ * utimensat_win32 - emulate utimensat()
+ *
+ * This function emulates utimensat().
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So utimensat_win32() has to use a directory handle instead of a directory fd.
+ *
+ * Access to a symbolic link will be denied to prevent security issues.
+ */
+int utimensat_win32(P9_FILE_ID dirfd, const char *pathname,
+                    const struct timespec times[2], int flags)
+{
+    g_autofree char *full_file_name = NULL;
+    HANDLE hFile = INVALID_HANDLE_VALUE;
+    DWORD attribute;
+    struct utimbuf tm;
+    int err = 0;
+    int ret = -1;
+
+    full_file_name = get_full_path_win32(dirfd, pathname);
+    if (full_file_name == NULL) {
+        return ret;
+    }
+
+    /* open file to lock it */
+    hFile = CreateFile(full_file_name, GENERIC_READ,
+                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                       NULL,
+                       OPEN_EXISTING,
+                       FILE_FLAG_BACKUP_SEMANTICS
+                       | FILE_FLAG_OPEN_REPARSE_POINT,
+                       NULL);
+
+    if (hFile == INVALID_HANDLE_VALUE) {
+        err = EACCES;
+        goto out;
+    }
+
+    attribute = GetFileAttributes(full_file_name);
+
+    /* check if it is a symbolic link */
+    if ((attribute == INVALID_FILE_ATTRIBUTES)
+        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+        errno = EACCES;
+        goto out;
+    }
+
+    tm.actime = times[0].tv_sec;
+    tm.modtime = times[1].tv_sec;
+
+    ret = utime(full_file_name, &tm);
+
+out:
+    if (hFile != INVALID_HANDLE_VALUE) {
+        CloseHandle(hFile);
+    }
+
+    if (err != 0) {
+        errno = err;
+    }
+    return ret;
+}
+
+/*
+ * unlinkat_win32 - emulate unlinkat()
+ *
+ * This function emulates unlinkat().
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So unlinkat_win32() has to use a directory handle instead of a directory fd.
+ *
+ * Access to a symbolic link will be denied to prevent security issues.
+ */
+
+int unlinkat_win32(P9_FILE_ID dirfd, const char *pathname, int flags)
+{
+    g_autofree char *full_file_name = NULL;
+    HANDLE hFile;
+    DWORD attribute;
+    int err = 0;
+    int ret = -1;
+
+    full_file_name = get_full_path_win32(dirfd, pathname);
+    if (full_file_name == NULL) {
+        return ret;
+    }
+
+    /* open file to prevent other one modify it */
+    hFile = CreateFile(full_file_name, GENERIC_READ,
+                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                       NULL,
+                       OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+    attribute = GetFileAttributes(full_file_name);
+
+    /* check if it is a symbolic link */
+    if ((attribute == INVALID_FILE_ATTRIBUTES)
+        || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+        err = EACCES;
+        goto out;
+    }
+
+    if (flags == AT_REMOVEDIR) { /* remove directory */
+        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+            err = ENOTDIR;
+            goto out;
+        }
+        ret = rmdir(full_file_name);
+    } else { /* remove regular file */
+        if ((attribute & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+            err = EISDIR;
+            goto out;
+        }
+        ret = remove(full_file_name);
+    }
+
+    /* after last handle closed, file will be removed */
+    CloseHandle(hFile);
+
+out:
+    if (err != 0) {
+        errno = err;
+    }
+    return ret;
+}
+
+/*
+ * statfs_win32 - statfs() on Windows
+ *
+ * This function emulates statfs() on Windows host.
+ */
+int statfs_win32(const char *path, struct statfs *stbuf)
+{
+    char RealPath[4] = { 0 };
+    unsigned long SectorsPerCluster;
+    unsigned long BytesPerSector;
+    unsigned long NumberOfFreeClusters;
+    unsigned long TotalNumberOfClusters;
+
+    /* only need first 3 bytes, e.g. "C:\ABC", only need "C:\" */
+    memcpy(RealPath, path, 3);
+
+    if (GetDiskFreeSpace(RealPath, &SectorsPerCluster, &BytesPerSector,
+                         &NumberOfFreeClusters, &TotalNumberOfClusters) == 0) {
+        errno = EIO;
+        return -1;
+    }
+
+    stbuf->f_type = V9FS_MAGIC;
+    stbuf->f_bsize =
+        (__fsword_t)SectorsPerCluster * (__fsword_t)BytesPerSector;
+    stbuf->f_blocks = (fsblkcnt_t)TotalNumberOfClusters;
+    stbuf->f_bfree = (fsblkcnt_t)NumberOfFreeClusters;
+    stbuf->f_bavail = (fsblkcnt_t)NumberOfFreeClusters;
+    stbuf->f_files = -1;
+    stbuf->f_ffree = -1;
+    stbuf->f_namelen = NAME_MAX;
+    stbuf->f_frsize = 0;
+    stbuf->f_flags = 0;
+
+    return 0;
+}
+
+/*
+ * openat_dir - emulate openat_dir()
+ *
+ * This function emulates openat_dir().
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * So openat_dir() has to use a directory handle instead of a directory fd.
+ *
+ * Access to a symbolic link will be denied to prevent security issues.
+ */
+P9_FILE_ID openat_dir(P9_FILE_ID dirfd, const char *name)
+{
+    g_autofree char *full_file_name = NULL;
+    HANDLE hSubDir;
+    DWORD attribute;
+
+    full_file_name = get_full_path_win32(dirfd, name);
+    if (full_file_name == NULL) {
+        return INVALID_HANDLE_VALUE;
+    }
+
+    attribute = GetFileAttributes(full_file_name);
+    if (attribute == INVALID_FILE_ATTRIBUTES) {
+        return INVALID_HANDLE_VALUE;
+    }
+
+    /* check if it is a directory */
+    if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+        return INVALID_HANDLE_VALUE;
+    }
+
+    /* do not allow opening a symbolic link */
+    if ((attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+        return INVALID_HANDLE_VALUE;
+    }
+
+    /* open it */
+    hSubDir = CreateFile(full_file_name, GENERIC_READ,
+                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                         NULL,
+                         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+    return hSubDir;
+}
+
+P9_FILE_ID openat_file(P9_FILE_ID dirfd, const char *name, int flags,
+                       mode_t mode)
+{
+    return openat_win32(dirfd, name, flags | _O_BINARY, mode);
+}
+
+/*
+ * fgetxattrat_nofollow - get extended attribute
+ *
+ * This function gets extended attribute from file <path> in the directory
+ * specified by <dirfd>. The extended atrribute name is specified by <name>
+ * and return value will be put in <value>.
+ *
+ * This function emulates extended attribute by NTFS ADS.
+ */
+ssize_t fgetxattrat_nofollow(P9_FILE_ID dirfd, const char *path,
+                             const char *name, void *value, size_t size)
+{
+    g_autofree char *full_file_name = NULL;
+    char ads_file_name[NAME_MAX + 1] = { 0 };
+    DWORD dwBytesRead;
+    HANDLE hStream;
+
+    full_file_name = get_full_path_win32(dirfd, path);
+    if (full_file_name == NULL) {
+        errno = EIO;
+        return -1;
+    }
+
+    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0) {
+        errno = EIO;
+        return -1;
+    }
+
+    hStream = CreateFile(ads_file_name, GENERIC_READ, FILE_SHARE_READ, NULL,
+                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (hStream == INVALID_HANDLE_VALUE &&
+        GetLastError() == ERROR_FILE_NOT_FOUND) {
+        errno = ENODATA;
+        return -1;
+    }
+
+    if (ReadFile(hStream, value, size, &dwBytesRead, NULL) == FALSE) {
+        errno = EIO;
+        CloseHandle(hStream);
+        return -1;
+    }
+
+    CloseHandle(hStream);
+
+    return dwBytesRead;
+}
+
+/*
+ * fsetxattrat_nofollow - set extended attribute
+ *
+ * This function set extended attribute to file <path> in the directory
+ * specified by <dirfd>.
+ *
+ * This function emulates extended attribute by NTFS ADS.
+ */
+
+int fsetxattrat_nofollow(P9_FILE_ID dirfd, const char *path, const char *name,
+                         void *value, size_t size, int flags)
+{
+    g_autofree char *full_file_name = NULL;
+    char ads_file_name[NAME_MAX + 1] = { 0 };
+    DWORD dwBytesWrite;
+    HANDLE hStream;
+
+    full_file_name = get_full_path_win32(dirfd, path);
+    if (full_file_name == NULL) {
+        errno = EIO;
+        return -1;
+    }
+
+    if (build_ads_name(ads_file_name, NAME_MAX, full_file_name, name) < 0) {
+        errno = EIO;
+        return -1;
+    }
+
+    hStream = CreateFile(ads_file_name, GENERIC_WRITE, FILE_SHARE_READ, NULL,
+                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (hStream == INVALID_HANDLE_VALUE) {
+        errno = EIO;
+        return -1;
+    }
+
+    if (WriteFile(hStream, value, size, &dwBytesWrite, NULL) == FALSE) {
+        errno = EIO;
+        CloseHandle(hStream);
+        return -1;
+    }
+
+    CloseHandle(hStream);
+
+    return 0;
+}
+
+/*
+ * flistxattrat_nofollow - list extended attribute
+ *
+ * This function gets extended attribute lists from file <filename> in the
+ * directory specified by <dirfd>. Lists returned will be put in <list>.
+ *
+ * This function emulates extended attribute by NTFS ADS.
+ */
+ssize_t flistxattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
+                              char *list, size_t size)
+{
+    g_autofree char *full_file_name = NULL;
+    WCHAR WideCharStr[NAME_MAX + 1] = { 0 };
+    char full_ads_name[NAME_MAX + 1];
+    WIN32_FIND_STREAM_DATA fsd;
+    BOOL bFindNext;
+    char *list_ptr = list;
+    size_t list_left_size = size;
+    HANDLE hFind;
+    int ret;
+
+    full_file_name = get_full_path_win32(dirfd, filename);
+    if (full_file_name == NULL) {
+        errno = EIO;
+        return -1;
+    }
+
+    /*
+     * ADS enumerate function only has WCHAR version, so we need to
+     * covert filename to utf-8 string.
+     */
+    ret = MultiByteToWideChar(CP_UTF8, 0, full_file_name,
+                              strlen(full_file_name), WideCharStr, NAME_MAX);
+    if (ret == 0) {
+        errno = EIO;
+        return -1;
+    }
+
+    hFind = FindFirstStreamW(WideCharStr, FindStreamInfoStandard, &fsd, 0);
+    if (hFind == INVALID_HANDLE_VALUE) {
+        errno = ENODATA;
+        return -1;
+    }
+
+    do {
+        memset(full_ads_name, 0, sizeof(full_ads_name));
+
+        /*
+         * ADS enumerate function only has WCHAR version, so we need to
+         * covert cStreamName to utf-8 string.
+         */
+        ret = WideCharToMultiByte(CP_UTF8, 0,
+                                  fsd.cStreamName, wcslen(fsd.cStreamName) + 1,
+                                  full_ads_name, sizeof(full_ads_name) - 1,
+                                  NULL, NULL);
+        if (ret == 0) {
+            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+                errno = ERANGE;
+            }
+            CloseHandle(hFind);
+            return -1;
+        }
+
+        ret = copy_ads_name(list_ptr, list_left_size, full_ads_name);
+        if (ret < 0) {
+            errno = ERANGE;
+            CloseHandle(hFind);
+            return -1;
+        }
+
+        list_ptr = list_ptr + ret;
+        list_left_size = list_left_size - ret;
+
+        bFindNext = FindNextStreamW(hFind, &fsd);
+    } while (bFindNext);
+
+    CloseHandle(hFind);
+
+    return size - list_left_size;
+}
+
+/*
+ * fremovexattrat_nofollow - remove extended attribute
+ *
+ * This function removes an extended attribute from file <filename> in the
+ * directory specified by <dirfd>.
+ *
+ * This function emulates extended attribute by NTFS ADS.
+ */
+ssize_t fremovexattrat_nofollow(P9_FILE_ID dirfd, const char *filename,
+                                const char *name)
+{
+    g_autofree char *full_file_name = NULL;
+    char ads_file_name[NAME_MAX + 1] = { 0 };
+
+    full_file_name = get_full_path_win32(dirfd, filename);
+    if (full_file_name == NULL) {
+        errno = EIO;
+        return -1;
+    }
+
+    if (build_ads_name(ads_file_name, NAME_MAX, filename, name) < 0) {
+        errno = EIO;
+        return -1;
+    }
+
+    if (DeleteFile(ads_file_name) != 0) {
+        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
+            errno = ENODATA;
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * local_opendir_nofollow - open a Windows directory
+ *
+ * This function returns a Windows file handle of the directory specified by
+ * <dirpath> based on 9pfs mount point.
+ *
+ * Windows POSIX API does not support opening a directory by open(). Only
+ * handle of directory can be opened by CreateFile().
+ *
+ * This function checks the resolved path of <dirpath>. If the resolved
+ * path is not in the scope of root directory (e.g. by symbolic link), then
+ * this function will fail to prevent any security issues.
+ */
+HANDLE local_opendir_nofollow(FsContext *fs_ctx, const char *dirpath)
+{
+    g_autofree char *full_file_name = NULL;
+    LocalData *data = fs_ctx->private;
+    HANDLE hDir;
+
+    hDir = openat_dir(data->mountfd, dirpath);
+    if (hDir == INVALID_HANDLE_VALUE) {
+        return INVALID_HANDLE_VALUE;
+    }
+
+    full_file_name = get_full_path_win32(hDir, NULL);
+    if (full_file_name == NULL) {
+        CloseHandle(hDir);
+        return INVALID_HANDLE_VALUE;
+    }
+
+    /*
+     * Check if the resolved path is in the root directory scope:
+     * data->root_path and full_file_name are full path with symbolic
+     * link resolved, so fs_ctx->root_path must be in the head of
+     * full_file_name. If not, that means guest OS tries to open a file not
+     * in the scope of mount point. This operation should be denied.
+     */
+    if (memcmp(full_file_name, data->root_path,
+               strlen(data->root_path)) != 0) {
+        CloseHandle(hDir);
+        hDir = INVALID_HANDLE_VALUE;
+    }
+
+    return hDir;
+}