[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH v5 04/16] hw/9pfs: Implement Windows specific xxxdir() APIs
From: |
Bin Meng |
Subject: |
[PATCH v5 04/16] hw/9pfs: Implement Windows specific xxxdir() APIs |
Date: |
Mon, 20 Feb 2023 18:08:03 +0800 |
From: Guohuai Shi <guohuai.shi@windriver.com>
This commit implements Windows specific xxxdir() APIs for safety
directory access.
Signed-off-by: Guohuai Shi <guohuai.shi@windriver.com>
Signed-off-by: Bin Meng <bin.meng@windriver.com>
---
hw/9pfs/9p-util.h | 6 +
hw/9pfs/9p-util-win32.c | 443 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 449 insertions(+)
diff --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h
index 0f159fb4ce..c1c251fbd1 100644
--- a/hw/9pfs/9p-util.h
+++ b/hw/9pfs/9p-util.h
@@ -141,6 +141,12 @@ int unlinkat_win32(int dirfd, const char *pathname, int
flags);
int statfs_win32(const char *root_path, struct statfs *stbuf);
int openat_dir(int dirfd, const char *name);
int openat_file(int dirfd, const char *name, int flags, mode_t mode);
+DIR *opendir_win32(const char *full_file_name);
+int closedir_win32(DIR *pDir);
+struct dirent *readdir_win32(DIR *pDir);
+void rewinddir_win32(DIR *pDir);
+void seekdir_win32(DIR *pDir, long pos);
+long telldir_win32(DIR *pDir);
#endif
static inline void close_preserve_errno(int fd)
diff --git a/hw/9pfs/9p-util-win32.c b/hw/9pfs/9p-util-win32.c
index a99d579a06..e9408f3c45 100644
--- a/hw/9pfs/9p-util-win32.c
+++ b/hw/9pfs/9p-util-win32.c
@@ -37,6 +37,16 @@
* Windows does not support opendir, the directory fd is created by
* CreateFile and convert to fd by _open_osfhandle(). Keep the fd open will
* lock and protect the directory (can not be modified or replaced)
+ *
+ * 5. Neither Windows native APIs, nor MinGW provide a POSIX compatible API for
+ * acquiring directory entries in a safe way. Calling those APIs (native
+ * _findfirst() and _findnext() or MinGW's readdir(), seekdir() and
+ * telldir()) directly can lead to an inconsistent state if directory is
+ * modified in between, e.g. the same directory appearing more than once
+ * in output, or directories not appearing at all in output even though they
+ * were neither newly created nor deleted. POSIX does not define what
happens
+ * with deleted or newly created directories in between, but it guarantees a
+ * consistent state.
*/
#include "qemu/osdep.h"
@@ -51,6 +61,25 @@
#define V9FS_MAGIC 0x53465039 /* string "9PFS" */
+/*
+ * MinGW and Windows does not provide a safe way to seek directory while other
+ * thread is modifying the same directory.
+ *
+ * This structure is used to store sorted file id and ensure directory seek
+ * consistency.
+ */
+struct dir_win32 {
+ struct dirent dd_dir;
+ uint32_t offset;
+ uint32_t total_entries;
+ HANDLE hDir;
+ uint32_t dir_name_len;
+ uint64_t dot_id;
+ uint64_t dot_dot_id;
+ uint64_t *file_id_list;
+ char dd_name[1];
+};
+
/*
* win32_error_to_posix - convert Win32 error to POSIX error number
*
@@ -977,3 +1006,417 @@ int qemu_mknodat(int dirfd, const char *filename, mode_t
mode, dev_t dev)
errno = ENOTSUP;
return -1;
}
+
+static int file_id_compare(const void *id_ptr1, const void *id_ptr2)
+{
+ uint64_t id[2];
+
+ id[0] = *(uint64_t *)id_ptr1;
+ id[1] = *(uint64_t *)id_ptr2;
+
+ if (id[0] > id[1]) {
+ return 1;
+ } else if (id[0] < id[1]) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+static int get_next_entry(struct dir_win32 *stream)
+{
+ HANDLE hDirEntry = INVALID_HANDLE_VALUE;
+ char *entry_name;
+ char *entry_start;
+ FILE_ID_DESCRIPTOR fid;
+ DWORD attribute;
+
+ if (stream->file_id_list[stream->offset] == stream->dot_id) {
+ strcpy(stream->dd_dir.d_name, ".");
+ return 0;
+ }
+
+ if (stream->file_id_list[stream->offset] == stream->dot_dot_id) {
+ strcpy(stream->dd_dir.d_name, "..");
+ return 0;
+ }
+
+ fid.dwSize = sizeof(fid);
+ fid.Type = FileIdType;
+
+ fid.FileId.QuadPart = stream->file_id_list[stream->offset];
+
+ hDirEntry = OpenFileById(stream->hDir, &fid, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE
+ | FILE_SHARE_DELETE,
+ NULL,
+ FILE_FLAG_BACKUP_SEMANTICS
+ | FILE_FLAG_OPEN_REPARSE_POINT);
+
+ if (hDirEntry == INVALID_HANDLE_VALUE) {
+ /*
+ * Not open it successfully, it may be deleted.
+ * Try next id.
+ */
+ return -1;
+ }
+
+ entry_name = get_full_path_win32(hDirEntry, NULL);
+
+ CloseHandle(hDirEntry);
+
+ if (entry_name == NULL) {
+ return -1;
+ }
+
+ attribute = GetFileAttributes(entry_name);
+
+ /* symlink is not allowed */
+ if (attribute == INVALID_FILE_ATTRIBUTES
+ || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+ return -1;
+ }
+
+ if (memcmp(entry_name, stream->dd_name, stream->dir_name_len) != 0) {
+ /*
+ * The full entry file name should be a part of parent directory name,
+ * except dot and dot_dot (is already handled).
+ * If not, this entry should not be returned.
+ */
+ return -1;
+ }
+
+ entry_start = entry_name + stream->dir_name_len;
+
+ /* skip slash */
+ while (*entry_start == '\\') {
+ entry_start++;
+ }
+
+ if (strchr(entry_start, '\\') != NULL) {
+ return -1;
+ }
+
+ if (strlen(entry_start) == 0
+ || strlen(entry_start) + 1 > sizeof(stream->dd_dir.d_name)) {
+ return -1;
+ }
+ strcpy(stream->dd_dir.d_name, entry_start);
+
+ return 0;
+}
+
+/*
+ * opendir_win32 - open a directory
+ *
+ * This function opens a directory and caches all directory entries.
+ */
+DIR *opendir_win32(const char *full_file_name)
+{
+ HANDLE hDir = INVALID_HANDLE_VALUE;
+ HANDLE hDirEntry = INVALID_HANDLE_VALUE;
+ char *full_dir_entry = NULL;
+ DWORD attribute;
+ intptr_t dd_handle = -1;
+ struct _finddata_t dd_data;
+ uint64_t file_id;
+ uint64_t *file_id_list = NULL;
+ BY_HANDLE_FILE_INFORMATION FileInfo;
+ struct dir_win32 *stream = NULL;
+ int err = 0;
+ int find_status;
+ int sort_first_two_entry = 0;
+ uint32_t list_count = 16;
+ uint32_t index = 0;
+
+ /* open directory to prevent it being removed */
+
+ hDir = 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 (hDir == INVALID_HANDLE_VALUE) {
+ err = win32_error_to_posix(GetLastError());
+ goto out;
+ }
+
+ attribute = GetFileAttributes(full_file_name);
+
+ /* symlink is not allow */
+ if (attribute == INVALID_FILE_ATTRIBUTES
+ || (attribute & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+ err = EACCES;
+ goto out;
+ }
+
+ /* check if it is a directory */
+ if ((attribute & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ err = ENOTDIR;
+ goto out;
+ }
+
+ file_id_list = g_malloc0(sizeof(uint64_t) * list_count);
+
+ /*
+ * findfirst() needs suffix format name like "\dir1\dir2\*",
+ * allocate more buffer to store suffix.
+ */
+ stream = g_malloc0(sizeof(struct dir_win32) + strlen(full_file_name) + 3);
+
+ strcpy(stream->dd_name, full_file_name);
+ strcat(stream->dd_name, "\\*");
+
+ stream->hDir = hDir;
+ stream->dir_name_len = strlen(full_file_name);
+
+ dd_handle = _findfirst(stream->dd_name, &dd_data);
+
+ if (dd_handle == -1) {
+ err = errno;
+ goto out;
+ }
+
+ /* read all entries to link list */
+ do {
+ full_dir_entry = get_full_path_win32(hDir, dd_data.name);
+
+ if (full_dir_entry == NULL) {
+ err = ENOMEM;
+ break;
+ }
+
+ /*
+ * Open every entry and get the file informations.
+ *
+ * Skip symbolic links during reading directory.
+ */
+ hDirEntry = CreateFile(full_dir_entry,
+ 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 (hDirEntry != INVALID_HANDLE_VALUE) {
+ if (GetFileInformationByHandle(hDirEntry,
+ &FileInfo) == TRUE) {
+ attribute = FileInfo.dwFileAttributes;
+
+ /* only save validate entries */
+ if ((attribute & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
+ if (index >= list_count) {
+ list_count = list_count + 16;
+ file_id_list = g_realloc(file_id_list,
+ sizeof(uint64_t)
+ * list_count);
+ }
+ file_id = (uint64_t)FileInfo.nFileIndexLow
+ + (((uint64_t)FileInfo.nFileIndexHigh) << 32);
+
+
+ file_id_list[index] = file_id;
+
+ if (strcmp(dd_data.name, ".") == 0) {
+ stream->dot_id = file_id_list[index];
+ if (index != 0) {
+ sort_first_two_entry = 1;
+ }
+ } else if (strcmp(dd_data.name, "..") == 0) {
+ stream->dot_dot_id = file_id_list[index];
+ if (index != 1) {
+ sort_first_two_entry = 1;
+ }
+ }
+ index++;
+ }
+ }
+ CloseHandle(hDirEntry);
+ }
+ g_free(full_dir_entry);
+ find_status = _findnext(dd_handle, &dd_data);
+ } while (find_status == 0);
+
+ if (errno == ENOENT) {
+ /* No more matching files could be found, clean errno */
+ errno = 0;
+ } else {
+ err = errno;
+ goto out;
+ }
+
+ stream->total_entries = index;
+ stream->file_id_list = file_id_list;
+
+ if (sort_first_two_entry == 0) {
+ /*
+ * If the first two entry is "." and "..", then do not sort them.
+ *
+ * If the guest OS always considers first two entries are "." and "..",
+ * sort the two entries may cause confused display in guest OS.
+ */
+ qsort(&file_id_list[2], index - 2, sizeof(file_id), file_id_compare);
+ } else {
+ qsort(&file_id_list[0], index, sizeof(file_id), file_id_compare);
+ }
+
+out:
+ if (err != 0) {
+ errno = err;
+ if (stream != NULL) {
+ if (file_id_list != NULL) {
+ g_free(file_id_list);
+ }
+ CloseHandle(hDir);
+ g_free(stream);
+ stream = NULL;
+ }
+ }
+
+ if (dd_handle != -1) {
+ _findclose(dd_handle);
+ }
+
+ return (DIR *)stream;
+}
+
+/*
+ * closedir_win32 - close a directory
+ *
+ * This function closes directory and free all cached resources.
+ */
+int closedir_win32(DIR *pDir)
+{
+ struct dir_win32 *stream = (struct dir_win32 *)pDir;
+
+ if (stream == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+
+ /* free all resources */
+ CloseHandle(stream->hDir);
+
+ g_free(stream->file_id_list);
+
+ g_free(stream);
+
+ return 0;
+}
+
+/*
+ * readdir_win32 - read a directory
+ *
+ * This function reads a directory entry from cached entry list.
+ */
+struct dirent *readdir_win32(DIR *pDir)
+{
+ struct dir_win32 *stream = (struct dir_win32 *)pDir;
+
+ if (stream == NULL) {
+ errno = EBADF;
+ return NULL;
+ }
+
+retry:
+
+ if (stream->offset >= stream->total_entries) {
+ /* reach to the end, return NULL without set errno */
+ return NULL;
+ }
+
+ if (get_next_entry(stream) != 0) {
+ stream->offset++;
+ goto retry;
+ }
+
+ /* Windows does not provide inode number */
+ stream->dd_dir.d_ino = 0;
+ stream->dd_dir.d_reclen = 0;
+ stream->dd_dir.d_namlen = strlen(stream->dd_dir.d_name);
+
+ stream->offset++;
+
+ return &stream->dd_dir;
+}
+
+/*
+ * rewinddir_win32 - reset directory stream
+ *
+ * This function resets the position of the directory stream to the
+ * beginning of the directory.
+ */
+void rewinddir_win32(DIR *pDir)
+{
+ struct dir_win32 *stream = (struct dir_win32 *)pDir;
+
+ if (stream == NULL) {
+ errno = EBADF;
+ return;
+ }
+
+ stream->offset = 0;
+
+ return;
+}
+
+/*
+ * seekdir_win32 - set the position of the next readdir() call in the directory
+ *
+ * This function sets the position of the next readdir() call in the directory
+ * from which the next readdir() call will start.
+ */
+void seekdir_win32(DIR *pDir, long pos)
+{
+ struct dir_win32 *stream = (struct dir_win32 *)pDir;
+
+ if (stream == NULL) {
+ errno = EBADF;
+ return;
+ }
+
+ if (pos < -1) {
+ errno = EINVAL;
+ return;
+ }
+
+ if (pos == -1 || pos >= (long)stream->total_entries) {
+ /* seek to the end */
+ stream->offset = stream->total_entries;
+ return;
+ }
+
+ if (pos - (long)stream->offset == 0) {
+ /* no need to seek */
+ return;
+ }
+
+ stream->offset = pos;
+
+ return;
+}
+
+/*
+ * telldir_win32 - return current location in directory
+ *
+ * This function returns current location in directory.
+ */
+long telldir_win32(DIR *pDir)
+{
+ struct dir_win32 *stream = (struct dir_win32 *)pDir;
+
+ if (stream == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (stream->offset > stream->total_entries) {
+ return -1;
+ }
+
+ return (long)stream->offset;
+}
--
2.25.1
- [PATCH v5 00/16] hw/9pfs: Add 9pfs support for Windows, Bin Meng, 2023/02/20
- [PATCH v5 01/16] hw/9pfs: Add missing definitions for Windows, Bin Meng, 2023/02/20
- [PATCH v5 04/16] hw/9pfs: Implement Windows specific xxxdir() APIs,
Bin Meng <=
- [PATCH v5 02/16] hw/9pfs: Implement Windows specific utilities functions for 9pfs, Bin Meng, 2023/02/20
- [PATCH v5 03/16] hw/9pfs: Replace the direct call to xxxdir() APIs with a wrapper, Bin Meng, 2023/02/20
- [PATCH v5 07/16] hw/9pfs: Update helper qemu_stat_rdev(), Bin Meng, 2023/02/20
- [PATCH v5 06/16] hw/9pfs: Support getting current directory offset for Windows, Bin Meng, 2023/02/20
- [PATCH v5 05/16] hw/9pfs: Update the local fs driver to support Windows, Bin Meng, 2023/02/20
- [PATCH v5 08/16] hw/9pfs: Add a helper qemu_stat_blksize(), Bin Meng, 2023/02/20
- [PATCH v5 09/16] hw/9pfs: Disable unsupported flags and features for Windows, Bin Meng, 2023/02/20
- [PATCH v5 10/16] hw/9pfs: Update v9fs_set_fd_limit() for Windows, Bin Meng, 2023/02/20
- [PATCH v5 11/16] hw/9pfs: Add Linux error number definition, Bin Meng, 2023/02/20
- [PATCH v5 12/16] hw/9pfs: Translate Windows errno to Linux value, Bin Meng, 2023/02/20