On Monday, October 24, 2022 6:57:50 AM CET Bin Meng wrote: > From: Guohuai Shi <guohuai....@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....@windriver.com> > Signed-off-by: Bin Meng <bin.m...@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; > +} >