diff options
Diffstat (limited to 'osdep')
-rw-r--r-- | osdep/io.c | 315 | ||||
-rw-r--r-- | osdep/io.h | 20 |
2 files changed, 296 insertions, 39 deletions
diff --git a/osdep/io.c b/osdep/io.c index 78af30be53..a49ee82638 100644 --- a/osdep/io.c +++ b/osdep/io.c @@ -141,37 +141,154 @@ char *mp_to_utf8(void *talloc_ctx, const wchar_t *s) #include <fcntl.h> #include <pthread.h> -static void copy_stat(struct mp_stat *dst, struct _stat64 *src) +static void set_errno_from_lasterror(void) +{ + // This just handles the error codes expected from CreateFile at the moment + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + errno = ENOENT; + break; + case ERROR_SHARING_VIOLATION: + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + errno = EEXIST; + break; + case ERROR_PIPE_BUSY: + errno = EAGAIN; + break; + default: + errno = EINVAL; + break; + } +} + +static time_t filetime_to_unix_time(int64_t wintime) +{ + static const int64_t hns_per_second = 10000000ll; + static const int64_t win_to_unix_epoch = 11644473600ll; + return wintime / hns_per_second - win_to_unix_epoch; +} + +static bool get_file_ids_win8(HANDLE h, dev_t *dev, ino_t *ino) { - dst->st_dev = src->st_dev; - dst->st_ino = src->st_ino; - dst->st_mode = src->st_mode; - dst->st_nlink = src->st_nlink; - dst->st_uid = src->st_uid; - dst->st_gid = src->st_gid; - dst->st_rdev = src->st_rdev; - dst->st_size = src->st_size; - dst->st_atime = src->st_atime; - dst->st_mtime = src->st_mtime; - dst->st_ctime = src->st_ctime; + FILE_ID_INFO ii; + if (!GetFileInformationByHandleEx(h, FileIdInfo, &ii, sizeof(ii))) + return false; + *dev = ii.VolumeSerialNumber; + // The definition of FILE_ID_128 differs between mingw-w64 and the Windows + // SDK, but we can ignore that by just memcpying it. This will also + // truncate the file ID on 32-bit Windows, which doesn't support __int128. + // 128-bit file IDs are only used for ReFS, so that should be okay. + assert(sizeof(*ino) <= sizeof(ii.FileId)); + memcpy(ino, &ii.FileId, sizeof(*ino)); + return true; +} + +#if HAVE_UWP +static bool get_file_ids(HANDLE h, dev_t *dev, ino_t *ino) +{ + return false; +} +#else +static bool get_file_ids(HANDLE h, dev_t *dev, ino_t *ino) +{ + // GetFileInformationByHandle works on FAT partitions and Windows 7, but + // doesn't work in UWP and can produce non-unique IDs on ReFS + BY_HANDLE_FILE_INFORMATION bhfi; + if (!GetFileInformationByHandle(h, &bhfi)) + return false; + *dev = bhfi.dwVolumeSerialNumber; + *ino = ((ino_t)bhfi.nFileIndexHigh << 32) | bhfi.nFileIndexLow; + return true; +} +#endif + +// Like fstat(), but with a Windows HANDLE +static int hstat(HANDLE h, struct mp_stat *buf) +{ + // Handle special (or unknown) file types first + switch (GetFileType(h) & ~FILE_TYPE_REMOTE) { + case FILE_TYPE_PIPE: + *buf = (struct mp_stat){ .st_nlink = 1, .st_mode = _S_IFIFO | 0644 }; + return 0; + case FILE_TYPE_CHAR: // character device + *buf = (struct mp_stat){ .st_nlink = 1, .st_mode = _S_IFCHR | 0644 }; + return 0; + case FILE_TYPE_UNKNOWN: + errno = EBADF; + return -1; + } + + struct mp_stat st = { 0 }; + + FILE_BASIC_INFO bi; + if (!GetFileInformationByHandleEx(h, FileBasicInfo, &bi, sizeof(bi))) { + errno = EBADF; + return -1; + } + st.st_atime = filetime_to_unix_time(bi.LastAccessTime.QuadPart); + st.st_mtime = filetime_to_unix_time(bi.LastWriteTime.QuadPart); + st.st_ctime = filetime_to_unix_time(bi.ChangeTime.QuadPart); + + FILE_STANDARD_INFO si; + if (!GetFileInformationByHandleEx(h, FileStandardInfo, &si, sizeof(si))) { + errno = EBADF; + return -1; + } + st.st_nlink = si.NumberOfLinks; + + // Here we pretend Windows has POSIX permissions by pretending all + // directories are 755 and regular files are 644 + if (si.Directory) { + st.st_mode |= _S_IFDIR | 0755; + } else { + st.st_mode |= _S_IFREG | 0644; + st.st_size = si.EndOfFile.QuadPart; + } + + if (!get_file_ids_win8(h, &st.st_dev, &st.st_ino)) { + // Fall back to the Windows 7 method (also used for FAT in Win8) + if (!get_file_ids(h, &st.st_dev, &st.st_ino)) { + errno = EBADF; + return -1; + } + } + + *buf = st; + return 0; } int mp_stat(const char *path, struct mp_stat *buf) { - struct _stat64 buf_; wchar_t *wpath = mp_from_utf8(NULL, path); - int res = _wstat64(wpath, &buf_); + HANDLE h = CreateFileW(wpath, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | SECURITY_SQOS_PRESENT | + SECURITY_IDENTIFICATION, NULL); talloc_free(wpath); - copy_stat(buf, &buf_); - return res; + if (h == INVALID_HANDLE_VALUE) { + set_errno_from_lasterror(); + return -1; + } + + int ret = hstat(h, buf); + CloseHandle(h); + return ret; } int mp_fstat(int fd, struct mp_stat *buf) { - struct _stat64 buf_; - int res = _fstat64(fd, &buf_); - copy_stat(buf, &buf_); - return res; + HANDLE h = (HANDLE)_get_osfhandle(fd); + if (h == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + // Use mpv's hstat() function rather than MSVCRT's fstat() because mpv's + // supports directories and device/inode numbers. + return hstat(h, buf); } #if HAVE_UWP @@ -255,31 +372,161 @@ int mp_printf(const char *format, ...) int mp_open(const char *filename, int oflag, ...) { - int mode = 0; - if (oflag & _O_CREAT) { - va_list va; - va_start(va, oflag); - mode = va_arg(va, int); - va_end(va); + // Always use all share modes, which is useful for opening files that are + // open in other processes, and also more POSIX-like + static const DWORD share = + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + // Setting FILE_APPEND_DATA and avoiding GENERIC_WRITE/FILE_WRITE_DATA + // will make the file handle use atomic append behavior + static const DWORD append = + FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA; + + DWORD access = 0; + DWORD disposition = 0; + DWORD flags = 0; + + switch (oflag & (_O_RDONLY | _O_RDWR | _O_WRONLY | _O_APPEND)) { + case _O_RDONLY: + access = GENERIC_READ; + flags |= FILE_FLAG_BACKUP_SEMANTICS; // For opening directories + break; + case _O_RDWR: + access = GENERIC_READ | GENERIC_WRITE; + break; + case _O_RDWR | _O_APPEND: + case _O_RDONLY | _O_APPEND: + access = GENERIC_READ | append; + break; + case _O_WRONLY: + access = GENERIC_WRITE; + break; + case _O_WRONLY | _O_APPEND: + access = append; + break; + default: + errno = EINVAL; + return -1; + } + + switch (oflag & (_O_CREAT | _O_EXCL | _O_TRUNC)) { + case 0: + case _O_EXCL: // Like MSVCRT, ignore invalid use of _O_EXCL + disposition = OPEN_EXISTING; + break; + case _O_TRUNC: + case _O_TRUNC | _O_EXCL: + disposition = TRUNCATE_EXISTING; + break; + case _O_CREAT: + disposition = OPEN_ALWAYS; + flags |= FILE_ATTRIBUTE_NORMAL; + break; + case _O_CREAT | _O_TRUNC: + disposition = CREATE_ALWAYS; + break; + case _O_CREAT | _O_EXCL: + case _O_CREAT | _O_EXCL | _O_TRUNC: + disposition = CREATE_NEW; + flags |= FILE_ATTRIBUTE_NORMAL; + break; + } + + // Opening a named pipe as a file can allow the pipe server to impersonate + // mpv's process, which could be a security issue. Set SQOS flags, so pipe + // servers can only identify the mpv process, not impersonate it. + if (disposition != CREATE_NEW) + flags |= SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION; + + // Keep the same semantics for some MSVCRT-specific flags + if (oflag & _O_TEMPORARY) { + flags |= FILE_FLAG_DELETE_ON_CLOSE; + access |= DELETE; + } + if (oflag & _O_SHORT_LIVED) + flags |= FILE_ATTRIBUTE_TEMPORARY; + if (oflag & _O_SEQUENTIAL) { + flags |= FILE_FLAG_SEQUENTIAL_SCAN; + } else if (oflag & _O_RANDOM) { + flags |= FILE_FLAG_RANDOM_ACCESS; } + + // Open the Windows file handle wchar_t *wpath = mp_from_utf8(NULL, filename); - int res = _wopen(wpath, oflag, mode); + HANDLE h = CreateFileW(wpath, access, share, NULL, disposition, flags, NULL); talloc_free(wpath); - return res; + if (h == INVALID_HANDLE_VALUE) { + set_errno_from_lasterror(); + return -1; + } + + // Map the Windows file handle to a CRT file descriptor. Note: MSVCRT only + // cares about the following oflags. + oflag &= _O_APPEND | _O_RDONLY | _O_RDWR | _O_WRONLY; + oflag |= _O_NOINHERIT; // We never create inheritable handles + int fd = _open_osfhandle((intptr_t)h, oflag); + if (fd < 0) { + CloseHandle(h); + return -1; + } + + return fd; } int mp_creat(const char *filename, int mode) { - return open(filename, O_CREAT|O_WRONLY|O_TRUNC, mode); + return mp_open(filename, _O_CREAT | _O_WRONLY | _O_TRUNC, mode); } FILE *mp_fopen(const char *filename, const char *mode) { - wchar_t *wpath = mp_from_utf8(NULL, filename); - wchar_t *wmode = mp_from_utf8(wpath, mode); - FILE *res = _wfopen(wpath, wmode); - talloc_free(wpath); - return res; + if (!mode[0]) { + errno = EINVAL; + return NULL; + } + + int rwmode; + int oflags = 0; + switch (mode[0]) { + case 'r': + rwmode = _O_RDONLY; + break; + case 'w': + rwmode = _O_WRONLY; + oflags |= _O_CREAT | _O_TRUNC; + break; + case 'a': + rwmode = _O_WRONLY; + oflags |= _O_CREAT | _O_APPEND; + break; + default: + errno = EINVAL; + return NULL; + } + + // Parse extra mode flags + for (const char *pos = mode + 1; *pos; pos++) { + switch (*pos) { + case '+': rwmode = _O_RDWR; break; + case 'x': oflags |= _O_EXCL; break; + // Ignore unknown flags (glibc does too) + default: break; + } + } + + // Open a CRT file descriptor + int fd = mp_open(filename, rwmode | oflags); + if (fd < 0) + return NULL; + + // Add 'b' to the mode so the CRT knows the file is opened in binary mode + char bmode[] = { mode[0], 'b', rwmode == _O_RDWR ? '+' : '\0', '\0' }; + FILE *fp = fdopen(fd, bmode); + if (!fp) { + close(fd); + return NULL; + } + + return fp; } // Windows' MAX_PATH/PATH_MAX/FILENAME_MAX is fixed to 260, but this limit diff --git a/osdep/io.h b/osdep/io.h index 70ba5ac1ae..55173ea1d7 100644 --- a/osdep/io.h +++ b/osdep/io.h @@ -107,15 +107,25 @@ FILE *mp_tmpfile(void); char *mp_getenv(const char *name); off_t mp_lseek(int fd, off_t offset, int whence); -// MinGW-w64 will define "stat" to something useless. Since this affects both -// the type (struct stat) and the stat() function, it makes us harder to -// override these separately. -// Corresponds to struct _stat64 (copy & pasted, but using public types). +// mp_stat types. MSVCRT's dev_t and ino_t are way too short to be unique. +typedef uint64_t mp_dev_t_; +#ifdef _WIN64 +typedef unsigned __int128 mp_ino_t_; +#else +// 32-bit Windows doesn't have a __int128-type, which means ReFS file IDs will +// be truncated and might collide. This is probably not a problem because ReFS +// is not available in consumer versions of Windows. +typedef uint64_t mp_ino_t_; +#endif +#define dev_t mp_dev_t_ +#define ino_t mp_ino_t_ + +// mp_stat uses a different structure to MSVCRT, with 64-bit inodes struct mp_stat { dev_t st_dev; ino_t st_ino; unsigned short st_mode; - short st_nlink; + unsigned int st_nlink; short st_uid; short st_gid; dev_t st_rdev; |