diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /stream/stream_file.c | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'stream/stream_file.c')
-rw-r--r-- | stream/stream_file.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/stream/stream_file.c b/stream/stream_file.c new file mode 100644 index 0000000..4895a83 --- /dev/null +++ b/stream/stream_file.c @@ -0,0 +1,377 @@ +/* + * Original authors: Albeu, probably Arpi + * + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#ifndef __MINGW32__ +#include <poll.h> +#endif + +#include "osdep/io.h" + +#include "common/common.h" +#include "common/msg.h" +#include "misc/thread_tools.h" +#include "stream.h" +#include "options/m_option.h" +#include "options/path.h" + +#if HAVE_BSD_FSTATFS +#include <sys/param.h> +#include <sys/mount.h> +#endif + +#if HAVE_LINUX_FSTATFS +#include <sys/vfs.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#include <winternl.h> +#include <io.h> + +#ifndef FILE_REMOTE_DEVICE +#define FILE_REMOTE_DEVICE (0x10) +#endif +#endif + +struct priv { + int fd; + bool close; + bool use_poll; + bool regular_file; + bool appending; + int64_t orig_size; + struct mp_cancel *cancel; +}; + +// Total timeout = RETRY_TIMEOUT * MAX_RETRIES +#define RETRY_TIMEOUT 0.2 +#define MAX_RETRIES 10 + +static int64_t get_size(stream_t *s) +{ + struct priv *p = s->priv; + struct stat st; + if (fstat(p->fd, &st) == 0) { + if (st.st_size <= 0 && !s->seekable) + st.st_size = -1; + if (st.st_size >= 0) + return st.st_size; + } + return -1; +} + +static int fill_buffer(stream_t *s, void *buffer, int max_len) +{ + struct priv *p = s->priv; + +#ifndef __MINGW32__ + if (p->use_poll) { + int c = mp_cancel_get_fd(p->cancel); + struct pollfd fds[2] = { + {.fd = p->fd, .events = POLLIN}, + {.fd = c, .events = POLLIN}, + }; + poll(fds, c >= 0 ? 2 : 1, -1); + if (fds[1].revents & POLLIN) + return -1; + } +#endif + + for (int retries = 0; retries < MAX_RETRIES; retries++) { + int r = read(p->fd, buffer, max_len); + if (r > 0) + return r; + + // Try to detect and handle files being appended during playback. + int64_t size = get_size(s); + if (p->regular_file && size > p->orig_size && !p->appending) { + MP_WARN(s, "File is apparently being appended to, will keep " + "retrying with timeouts.\n"); + p->appending = true; + } + + if (!p->appending || p->use_poll) + break; + + if (mp_cancel_wait(p->cancel, RETRY_TIMEOUT)) + break; + } + + return 0; +} + +static int write_buffer(stream_t *s, void *buffer, int len) +{ + struct priv *p = s->priv; + return write(p->fd, buffer, len); +} + +static int seek(stream_t *s, int64_t newpos) +{ + struct priv *p = s->priv; + return lseek(p->fd, newpos, SEEK_SET) != (off_t)-1; +} + +static void s_close(stream_t *s) +{ + struct priv *p = s->priv; + if (p->close) + close(p->fd); +} + +// If url is a file:// URL, return the local filename, otherwise return NULL. +char *mp_file_url_to_filename(void *talloc_ctx, bstr url) +{ + bstr proto = mp_split_proto(url, &url); + if (bstrcasecmp0(proto, "file") != 0) + return NULL; + char *filename = bstrto0(talloc_ctx, url); + mp_url_unescape_inplace(filename); +#if HAVE_DOS_PATHS + // extract '/' from '/x:/path' + if (filename[0] == '/' && filename[1] && filename[2] == ':') + memmove(filename, filename + 1, strlen(filename)); // including \0 +#endif + return filename; +} + +// Return talloc_strdup's filesystem path if local, otherwise NULL. +// Unlike mp_file_url_to_filename(), doesn't return NULL if already local. +char *mp_file_get_path(void *talloc_ctx, bstr url) +{ + if (mp_split_proto(url, &(bstr){0}).len) { + return mp_file_url_to_filename(talloc_ctx, url); + } else { + return bstrto0(talloc_ctx, url); + } +} + +#if HAVE_BSD_FSTATFS +static bool check_stream_network(int fd) +{ + struct statfs fs; + const char *stypes[] = { "afpfs", "nfs", "smbfs", "webdav", "osxfusefs", + "fuse", "fusefs.sshfs", "macfuse", NULL }; + if (fstatfs(fd, &fs) == 0) + for (int i=0; stypes[i]; i++) + if (strcmp(stypes[i], fs.f_fstypename) == 0) + return true; + return false; + +} +#elif HAVE_LINUX_FSTATFS +static bool check_stream_network(int fd) +{ + struct statfs fs; + const uint32_t stypes[] = { + 0x5346414F /*AFS*/, 0x61756673 /*AUFS*/, 0x00C36400 /*CEPH*/, + 0xFF534D42 /*CIFS*/, 0x73757245 /*CODA*/, 0x19830326 /*FHGFS*/, + 0x65735546 /*FUSEBLK*/,0x65735543 /*FUSECTL*/,0x1161970 /*GFS*/, + 0x47504653 /*GPFS*/, 0x6B414653 /*KAFS*/, 0x0BD00BD0 /*LUSTRE*/, + 0x564C /*NCP*/, 0x6969 /*NFS*/, 0x6E667364 /*NFSD*/, + 0xAAD7AAEA /*PANFS*/, 0x50495045 /*PIPEFS*/, 0x517B /*SMB*/, + 0xBEEFDEAD /*SNFS*/, 0xBACBACBC /*VMHGFS*/, 0x7461636f /*OCFS2*/, + 0xFE534D42 /*SMB2*/, 0x61636673 /*ACFS*/, 0x013111A8 /*IBRIX*/, + 0 + }; + if (fstatfs(fd, &fs) == 0) { + for (int i=0; stypes[i]; i++) { + if (stypes[i] == fs.f_type) + return true; + } + } + return false; + +} +#elif defined(_WIN32) +static bool check_stream_network(int fd) +{ + NTSTATUS (NTAPI *pNtQueryVolumeInformationFile)(HANDLE, + PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS) = NULL; + + // NtQueryVolumeInformationFile is an internal Windows function. It has + // been present since Windows XP, however this code should fail gracefully + // if it's removed from a future version of Windows. + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + pNtQueryVolumeInformationFile = (NTSTATUS (NTAPI*)(HANDLE, + PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS)) + GetProcAddress(ntdll, "NtQueryVolumeInformationFile"); + + if (!pNtQueryVolumeInformationFile) + return false; + + HANDLE h = (HANDLE)_get_osfhandle(fd); + if (h == INVALID_HANDLE_VALUE) + return false; + + FILE_FS_DEVICE_INFORMATION info = { 0 }; + IO_STATUS_BLOCK io; + NTSTATUS status = pNtQueryVolumeInformationFile(h, &io, &info, + sizeof(info), FileFsDeviceInformation); + if (!NT_SUCCESS(status)) + return false; + + return info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM || + (info.Characteristics & FILE_REMOTE_DEVICE); +} +#else +static bool check_stream_network(int fd) +{ + return false; +} +#endif + +static int open_f(stream_t *stream, const struct stream_open_args *args) +{ + struct priv *p = talloc_ptrtype(stream, p); + *p = (struct priv) { + .fd = -1, + }; + stream->priv = p; + stream->is_local_file = true; + + bool strict_fs = args->flags & STREAM_LOCAL_FS_ONLY; + bool write = stream->mode == STREAM_WRITE; + int m = O_CLOEXEC | (write ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY); + + char *filename = stream->path; + char *url = ""; + if (!strict_fs) { + char *fn = mp_file_url_to_filename(stream, bstr0(stream->url)); + if (fn) + filename = stream->path = fn; + url = stream->url; + } + + bool is_fdclose = strncmp(url, "fdclose://", 10) == 0; + if (strncmp(url, "fd://", 5) == 0 || is_fdclose) { + char *begin = strstr(stream->url, "://") + 3, *end = NULL; + p->fd = strtol(begin, &end, 0); + if (!end || end == begin || end[0]) { + MP_ERR(stream, "Invalid FD: %s\n", stream->url); + return STREAM_ERROR; + } + if (is_fdclose) + p->close = true; + } else if (!strict_fs && !strcmp(filename, "-")) { + if (!write) { + MP_INFO(stream, "Reading from stdin...\n"); + p->fd = 0; + } else { + MP_INFO(stream, "Writing to stdout...\n"); + p->fd = 1; + } + } else { + if (bstr_startswith0(bstr0(stream->url), "appending://")) + p->appending = true; + + mode_t openmode = S_IRUSR | S_IWUSR; +#ifndef __MINGW32__ + openmode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + if (!write) + m |= O_NONBLOCK; +#endif + p->fd = open(filename, m | O_BINARY, openmode); + if (p->fd < 0) { + MP_ERR(stream, "Cannot open file '%s': %s\n", + filename, mp_strerror(errno)); + return STREAM_ERROR; + } + p->close = true; + } + + struct stat st; + if (fstat(p->fd, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + stream->is_directory = true; + if (!(args->flags & STREAM_LESS_NOISE)) + MP_INFO(stream, "This is a directory - adding to playlist.\n"); + } else if (S_ISREG(st.st_mode)) { + p->regular_file = true; +#ifndef __MINGW32__ + // O_NONBLOCK has weird semantics on file locks; remove it. + int val = fcntl(p->fd, F_GETFL) & ~(unsigned)O_NONBLOCK; + fcntl(p->fd, F_SETFL, val); +#endif + } else { + p->use_poll = true; + } + } + +#ifdef __MINGW32__ + setmode(p->fd, O_BINARY); +#endif + + off_t len = lseek(p->fd, 0, SEEK_END); + lseek(p->fd, 0, SEEK_SET); + if (len != (off_t)-1) { + stream->seek = seek; + stream->seekable = true; + } + + stream->fast_skip = true; + stream->fill_buffer = fill_buffer; + stream->write_buffer = write_buffer; + stream->get_size = get_size; + stream->close = s_close; + + if (check_stream_network(p->fd)) { + stream->streaming = true; +#if HAVE_COCOA + if (fcntl(p->fd, F_RDAHEAD, 0) < 0) { + MP_VERBOSE(stream, "Cannot disable read ahead on file '%s': %s\n", + filename, mp_strerror(errno)); + } +#endif + } + + p->orig_size = get_size(stream); + + p->cancel = mp_cancel_new(p); + if (stream->cancel) + mp_cancel_set_parent(p->cancel, stream->cancel); + + return STREAM_OK; +} + +const stream_info_t stream_info_file = { + .name = "file", + .open2 = open_f, + .protocols = (const char*const[]){ "file", "", "appending", NULL }, + .can_write = true, + .local_fs = true, + .stream_origin = STREAM_ORIGIN_FS, +}; + +const stream_info_t stream_info_fd = { + .name = "fd", + .open2 = open_f, + .protocols = (const char*const[]){ "fd", "fdclose", NULL }, + .can_write = true, + .stream_origin = STREAM_ORIGIN_UNSAFE, +}; |