Coverage Report

Created: 2025-10-27 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/stream/stream_file.c
Line
Count
Source
1
/*
2
 * Original authors: Albeu, probably Arpi
3
 *
4
 * This file is part of mpv.
5
 *
6
 * mpv is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * mpv is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
#include "config.h"
21
22
#include <stdio.h>
23
#include <sys/types.h>
24
#include <sys/stat.h>
25
#include <fcntl.h>
26
#include <errno.h>
27
28
#ifndef _WIN32
29
#include <poll.h>
30
#endif
31
32
#include "osdep/io.h"
33
34
#include "common/common.h"
35
#include "common/msg.h"
36
#include "misc/thread_tools.h"
37
#include "stream.h"
38
#include "options/m_option.h"
39
#include "options/path.h"
40
41
#if HAVE_BSD_FSTATFS
42
#include <sys/param.h>
43
#include <sys/mount.h>
44
#endif
45
46
#if HAVE_LINUX_FSTATFS
47
#include <sys/vfs.h>
48
#endif
49
50
#ifdef _WIN32
51
#include <windows.h>
52
#include <winioctl.h>
53
#include <winternl.h>
54
#include <io.h>
55
56
#ifdef _MSC_VER
57
// Those are defined only in Windows DDK
58
typedef struct _FILE_FS_DEVICE_INFORMATION {
59
    DEVICE_TYPE DeviceType;
60
    ULONG Characteristics;
61
} FILE_FS_DEVICE_INFORMATION, *PFILE_FS_DEVICE_INFORMATION;
62
63
typedef enum _FSINFOCLASS {
64
    FileFsVolumeInformation          = 1,
65
    FileFsLabelInformation,         // 2
66
    FileFsSizeInformation,          // 3
67
    FileFsDeviceInformation,        // 4
68
    FileFsAttributeInformation,     // 5
69
    FileFsControlInformation,       // 6
70
    FileFsFullSizeInformation,      // 7
71
    FileFsObjectIdInformation,      // 8
72
    FileFsDriverPathInformation,    // 9
73
    FileFsVolumeFlagsInformation,   // 10
74
    FileFsSectorSizeInformation,    // 11
75
    FileFsDataCopyInformation,      // 12
76
    FileFsMetadataSizeInformation,  // 13
77
    FileFsFullSizeInformationEx,    // 14
78
    FileFsGuidInformation,          // 15
79
    FileFsMaximumInformation
80
} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;
81
#endif
82
83
#ifndef FILE_REMOTE_DEVICE
84
#define FILE_REMOTE_DEVICE (0x10)
85
#endif
86
#endif
87
88
struct priv {
89
    int fd;
90
    bool close;
91
    bool use_poll;
92
    bool regular_file;
93
    bool appending;
94
    int64_t orig_size;
95
    struct mp_cancel *cancel;
96
};
97
98
// Total timeout = RETRY_TIMEOUT * MAX_RETRIES
99
0
#define RETRY_TIMEOUT 0.2
100
548k
#define MAX_RETRIES 10
101
102
static int64_t get_size(stream_t *s)
103
632k
{
104
632k
    struct priv *p = s->priv;
105
632k
    struct stat st;
106
632k
    if (fstat(p->fd, &st) == 0) {
107
632k
        if (st.st_size <= 0 && !s->seekable)
108
0
            st.st_size = -1;
109
632k
        if (st.st_size >= 0)
110
632k
            return st.st_size;
111
632k
    }
112
0
    return -1;
113
632k
}
114
115
static int fill_buffer(stream_t *s, void *buffer, int max_len)
116
548k
{
117
548k
    struct priv *p = s->priv;
118
119
548k
#ifndef _WIN32
120
548k
    if (p->use_poll) {
121
0
        int c = mp_cancel_get_fd(p->cancel);
122
0
        struct pollfd fds[2] = {
123
0
            {.fd = p->fd, .events = POLLIN},
124
0
            {.fd = c, .events = POLLIN},
125
0
        };
126
0
        poll(fds, c >= 0 ? 2 : 1, -1);
127
0
        if (fds[1].revents & POLLIN)
128
0
            return -1;
129
0
    }
130
548k
#endif
131
132
548k
    for (int retries = 0; retries < MAX_RETRIES; retries++) {
133
548k
        int r = read(p->fd, buffer, max_len);
134
548k
        if (r > 0)
135
45.1k
            return r;
136
137
        // Try to detect and handle files being appended during playback.
138
503k
        int64_t size = get_size(s);
139
503k
        if (p->regular_file && size > p->orig_size && !p->appending) {
140
0
            MP_WARN(s, "File is apparently being appended to, will keep "
141
0
                    "retrying with timeouts.\n");
142
0
            p->appending = true;
143
0
        }
144
145
503k
        if (!p->appending || p->use_poll)
146
503k
            break;
147
148
0
        if (mp_cancel_wait(p->cancel, RETRY_TIMEOUT))
149
0
            break;
150
0
    }
151
152
503k
    return 0;
153
548k
}
154
155
static int write_buffer(stream_t *s, void *buffer, int len)
156
0
{
157
0
    struct priv *p = s->priv;
158
0
    return write(p->fd, buffer, len);
159
0
}
160
161
static int seek(stream_t *s, int64_t newpos)
162
1.81k
{
163
1.81k
    struct priv *p = s->priv;
164
1.81k
    return lseek(p->fd, newpos, SEEK_SET) != (off_t)-1;
165
1.81k
}
166
167
static void s_close(stream_t *s)
168
49.0k
{
169
49.0k
    struct priv *p = s->priv;
170
49.0k
    if (p->close)
171
4.00k
        close(p->fd);
172
49.0k
}
173
174
// If url is a file:// URL, return the local filename, otherwise return NULL.
175
char *mp_file_url_to_filename(void *talloc_ctx, bstr url)
176
504k
{
177
504k
    bstr proto = mp_split_proto(url, &url);
178
504k
    if (bstrcasecmp0(proto, "file") != 0)
179
473k
        return NULL;
180
31.3k
    char *filename = bstrto0(talloc_ctx, url);
181
31.3k
    mp_url_unescape_inplace(filename);
182
#if HAVE_DOS_PATHS
183
    // extract '/' from '/x:/path'
184
    if (filename[0] == '/' && filename[1] && filename[2] == ':')
185
        memmove(filename, filename + 1, strlen(filename)); // including \0
186
#endif
187
31.3k
    return filename;
188
504k
}
189
190
// Return talloc_strdup's filesystem path if local, otherwise NULL.
191
// Unlike mp_file_url_to_filename(), doesn't return NULL if already local.
192
char *mp_file_get_path(void *talloc_ctx, bstr url)
193
0
{
194
0
    if (mp_split_proto(url, &(bstr){0}).len) {
195
0
        return mp_file_url_to_filename(talloc_ctx, url);
196
0
    } else {
197
0
        return bstrto0(talloc_ctx, url);
198
0
    }
199
0
}
200
201
#if HAVE_BSD_FSTATFS
202
static bool check_stream_network(int fd)
203
{
204
    struct statfs fs;
205
    const char *stypes[] = { "afpfs", "nfs", "smbfs", "webdav", "osxfusefs",
206
                             "fuse", "fusefs.sshfs", "macfuse", NULL };
207
    if (fstatfs(fd, &fs) == 0)
208
        for (int i=0; stypes[i]; i++)
209
            if (strcmp(stypes[i], fs.f_fstypename) == 0)
210
                return true;
211
    return false;
212
213
}
214
#elif HAVE_LINUX_FSTATFS
215
static bool check_stream_network(int fd)
216
44.5k
{
217
44.5k
    struct statfs fs;
218
44.5k
    const uint32_t stypes[] = {
219
44.5k
        0x5346414F  /*AFS*/,    0x61756673  /*AUFS*/,   0x00C36400  /*CEPH*/,
220
44.5k
        0xFF534D42  /*CIFS*/,   0x73757245  /*CODA*/,   0x19830326  /*FHGFS*/,
221
44.5k
        0x65735546  /*FUSEBLK*/,0x65735543  /*FUSECTL*/,0x1161970   /*GFS*/,
222
44.5k
        0x47504653  /*GPFS*/,   0x6B414653  /*KAFS*/,   0x0BD00BD0  /*LUSTRE*/,
223
44.5k
        0x564C      /*NCP*/,    0x6969      /*NFS*/,    0x6E667364  /*NFSD*/,
224
44.5k
        0xAAD7AAEA  /*PANFS*/,  0x50495045  /*PIPEFS*/, 0x517B      /*SMB*/,
225
44.5k
        0xBEEFDEAD  /*SNFS*/,   0xBACBACBC  /*VMHGFS*/, 0x7461636f  /*OCFS2*/,
226
44.5k
        0xFE534D42  /*SMB2*/,   0x61636673  /*ACFS*/,   0x013111A8  /*IBRIX*/,
227
44.5k
        0
228
44.5k
    };
229
44.5k
    if (fstatfs(fd, &fs) == 0) {
230
1.11M
        for (int i=0; stypes[i]; i++) {
231
1.06M
            if (stypes[i] == fs.f_type)
232
0
                return true;
233
1.06M
        }
234
44.5k
    }
235
44.5k
    return false;
236
237
44.5k
}
238
#elif defined(_WIN32)
239
static bool check_stream_network(int fd)
240
{
241
    NTSTATUS (NTAPI *pNtQueryVolumeInformationFile)(HANDLE,
242
        PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS) = NULL;
243
244
    // NtQueryVolumeInformationFile is an internal Windows function. It has
245
    // been present since Windows XP, however this code should fail gracefully
246
    // if it's removed from a future version of Windows.
247
    HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
248
    pNtQueryVolumeInformationFile = (NTSTATUS (NTAPI*)(HANDLE,
249
        PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS))
250
        GetProcAddress(ntdll, "NtQueryVolumeInformationFile");
251
252
    if (!pNtQueryVolumeInformationFile)
253
        return false;
254
255
    HANDLE h = (HANDLE)_get_osfhandle(fd);
256
    if (h == INVALID_HANDLE_VALUE)
257
        return false;
258
259
    FILE_FS_DEVICE_INFORMATION info = { 0 };
260
    IO_STATUS_BLOCK io;
261
    NTSTATUS status = pNtQueryVolumeInformationFile(h, &io, &info,
262
        sizeof(info), FileFsDeviceInformation);
263
    if (!NT_SUCCESS(status))
264
        return false;
265
266
    return info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM ||
267
           (info.Characteristics & FILE_REMOTE_DEVICE);
268
}
269
#else
270
static bool check_stream_network(int fd)
271
{
272
    return false;
273
}
274
#endif
275
276
static int open_f(stream_t *stream, const struct stream_open_args *args)
277
52.4k
{
278
52.4k
    struct priv *p = talloc_ptrtype(stream, p);
279
52.4k
    *p = (struct priv) {
280
52.4k
        .fd = -1,
281
52.4k
    };
282
52.4k
    stream->priv = p;
283
52.4k
    stream->is_local_fs = true;
284
285
52.4k
    bool strict_fs = args->flags & STREAM_LOCAL_FS_ONLY;
286
52.4k
    bool write = stream->mode == STREAM_WRITE;
287
52.4k
    int m = O_CLOEXEC | (write ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY);
288
289
52.4k
    char *filename = stream->path;
290
52.4k
    char *url = "";
291
52.4k
    if (!strict_fs) {
292
52.4k
        char *fn = mp_file_url_to_filename(stream, bstr0(stream->url));
293
52.4k
        if (fn)
294
47
            filename = stream->path = fn;
295
52.4k
        url = stream->url;
296
52.4k
    }
297
298
52.4k
    bool is_fdclose = strncmp(url, "fdclose://", 10) == 0;
299
52.4k
    if (strncmp(url, "fd://", 5) == 0 || is_fdclose) {
300
44.6k
        stream->is_local_fs = false;
301
44.6k
        char *begin = strstr(stream->url, "://") + 3, *end = NULL;
302
44.6k
        p->fd = strtol(begin, &end, 0);
303
44.6k
        if (!end || end == begin || end[0] || p->fd < 0) {
304
40
            MP_ERR(stream, "Invalid FD number: %s\n", stream->url);
305
40
            return STREAM_ERROR;
306
40
        }
307
44.5k
#ifdef F_SETFD
308
44.5k
        if (fcntl(p->fd, F_GETFD) == -1) {
309
2
            MP_ERR(stream, "Invalid FD: %d\n", p->fd);
310
2
            return STREAM_ERROR;
311
2
        }
312
44.5k
#endif
313
44.5k
        if (is_fdclose)
314
30
            p->close = true;
315
44.5k
    } else if (!strict_fs && !strcmp(filename, "-")) {
316
531
        stream->is_local_fs = false;
317
531
        if (!write) {
318
531
            MP_INFO(stream, "Reading from stdin...\n");
319
531
            p->fd = 0;
320
531
        } else {
321
0
            MP_INFO(stream, "Writing to stdout...\n");
322
0
            p->fd = 1;
323
0
        }
324
7.30k
    } else {
325
7.30k
        if (bstr_startswith0(bstr0(stream->url), "appending://"))
326
8
            p->appending = true;
327
328
7.30k
        mode_t openmode = S_IRUSR | S_IWUSR;
329
7.30k
#ifndef __MINGW32__
330
7.30k
        openmode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
331
7.30k
        if (!write)
332
7.30k
            m |= O_NONBLOCK;
333
7.30k
#endif
334
7.30k
        p->fd = open(filename, m | O_BINARY, openmode);
335
7.30k
        if (p->fd < 0) {
336
3.33k
            MP_ERR(stream, "Cannot open file '%s': %s\n",
337
3.33k
                   filename, mp_strerror(errno));
338
3.33k
            return STREAM_ERROR;
339
3.33k
        }
340
3.97k
        p->close = true;
341
3.97k
    }
342
343
49.0k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
344
49.0k
    if (p->fd != 42) {
345
4.53k
        s_close(stream);
346
4.53k
        return STREAM_ERROR;
347
4.53k
    }
348
44.5k
#endif
349
350
44.5k
    struct stat st;
351
44.5k
    bool is_sock_or_fifo = false;
352
44.5k
    if (fstat(p->fd, &st) == 0) {
353
44.5k
        if (S_ISDIR(st.st_mode)) {
354
0
            stream->is_directory = true;
355
44.5k
        } else if (S_ISREG(st.st_mode)) {
356
44.5k
            p->regular_file = true;
357
44.5k
#ifndef _WIN32
358
            // O_NONBLOCK has weird semantics on file locks; remove it.
359
44.5k
            int val = fcntl(p->fd, F_GETFL) & ~(unsigned)O_NONBLOCK;
360
44.5k
            fcntl(p->fd, F_SETFL, val);
361
44.5k
#endif
362
44.5k
        } else {
363
0
#ifndef __MINGW32__
364
0
            is_sock_or_fifo = S_ISSOCK(st.st_mode) || S_ISFIFO(st.st_mode);
365
0
#endif
366
0
            p->use_poll = true;
367
0
        }
368
44.5k
    }
369
370
#ifdef _WIN32
371
    setmode(p->fd, O_BINARY);
372
#endif
373
374
44.5k
    off_t len = lseek(p->fd, 0, SEEK_END);
375
44.5k
    lseek(p->fd, 0, SEEK_SET);
376
44.5k
    if (len != (off_t)-1) {
377
44.5k
        stream->seek = seek;
378
44.5k
        stream->seekable = true;
379
44.5k
    }
380
381
44.5k
    stream->fast_skip = true;
382
44.5k
    stream->fill_buffer = fill_buffer;
383
44.5k
    stream->write_buffer = write_buffer;
384
44.5k
    stream->get_size = get_size;
385
44.5k
    stream->close = s_close;
386
387
44.5k
    if (is_sock_or_fifo || check_stream_network(p->fd)) {
388
0
        stream->streaming = true;
389
#if HAVE_COCOA
390
        if (fcntl(p->fd, F_RDAHEAD, 0) < 0) {
391
            MP_VERBOSE(stream, "Cannot disable read ahead on file '%s': %s\n",
392
                       filename, mp_strerror(errno));
393
        }
394
#endif
395
0
    }
396
397
44.5k
    p->orig_size = get_size(stream);
398
399
44.5k
    p->cancel = mp_cancel_new(p);
400
44.5k
    if (stream->cancel)
401
26.6k
        mp_cancel_set_parent(p->cancel, stream->cancel);
402
403
44.5k
    return STREAM_OK;
404
49.0k
}
405
406
const stream_info_t stream_info_file = {
407
    .name = "file",
408
    .open2 = open_f,
409
    .protocols = (const char*const[]){ "file", "", "appending", NULL },
410
    .can_write = true,
411
    .local_fs = true,
412
    .stream_origin = STREAM_ORIGIN_FS,
413
};
414
415
const stream_info_t stream_info_fd = {
416
    .name = "fd",
417
    .open2 = open_f,
418
    .protocols = (const char*const[]){ "fd", "fdclose", NULL },
419
    .can_write = true,
420
    .stream_origin = STREAM_ORIGIN_UNSAFE,
421
};