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 <unistd.h>
27 #include <errno.h>
28 
29 #ifndef __MINGW32__
30 #include <poll.h>
31 #endif
32 
33 #include "osdep/io.h"
34 
35 #include "common/common.h"
36 #include "common/msg.h"
37 #include "misc/thread_tools.h"
38 #include "stream.h"
39 #include "options/m_option.h"
40 #include "options/path.h"
41 
42 #if HAVE_BSD_FSTATFS
43 #include <sys/param.h>
44 #include <sys/mount.h>
45 #endif
46 
47 #if HAVE_LINUX_FSTATFS
48 #include <sys/vfs.h>
49 #endif
50 
51 #ifdef _WIN32
52 #include <windows.h>
53 #include <winternl.h>
54 #include <io.h>
55 
56 #ifndef FILE_REMOTE_DEVICE
57 #define FILE_REMOTE_DEVICE (0x10)
58 #endif
59 #endif
60 
61 struct priv {
62     int fd;
63     bool close;
64     bool use_poll;
65     bool regular_file;
66     bool appending;
67     int64_t orig_size;
68     struct mp_cancel *cancel;
69 };
70 
71 // Total timeout = RETRY_TIMEOUT * MAX_RETRIES
72 #define RETRY_TIMEOUT 0.2
73 #define MAX_RETRIES 10
74 
get_size(stream_t * s)75 static int64_t get_size(stream_t *s)
76 {
77     struct priv *p = s->priv;
78     struct stat st;
79     if (fstat(p->fd, &st) == 0) {
80         if (st.st_size <= 0 && !s->seekable)
81             st.st_size = -1;
82         if (st.st_size >= 0)
83             return st.st_size;
84     }
85     return -1;
86 }
87 
fill_buffer(stream_t * s,void * buffer,int max_len)88 static int fill_buffer(stream_t *s, void *buffer, int max_len)
89 {
90     struct priv *p = s->priv;
91 
92 #ifndef __MINGW32__
93     if (p->use_poll) {
94         int c = mp_cancel_get_fd(p->cancel);
95         struct pollfd fds[2] = {
96             {.fd = p->fd, .events = POLLIN},
97             {.fd = c, .events = POLLIN},
98         };
99         poll(fds, c >= 0 ? 2 : 1, -1);
100         if (fds[1].revents & POLLIN)
101             return -1;
102     }
103 #endif
104 
105     for (int retries = 0; retries < MAX_RETRIES; retries++) {
106         int r = read(p->fd, buffer, max_len);
107         if (r > 0)
108             return r;
109 
110         // Try to detect and handle files being appended during playback.
111         int64_t size = get_size(s);
112         if (p->regular_file && size > p->orig_size && !p->appending) {
113             MP_WARN(s, "File is apparently being appended to, will keep "
114                     "retrying with timeouts.\n");
115             p->appending = true;
116         }
117 
118         if (!p->appending || p->use_poll)
119             break;
120 
121         if (mp_cancel_wait(p->cancel, RETRY_TIMEOUT))
122             break;
123     }
124 
125     return 0;
126 }
127 
write_buffer(stream_t * s,void * buffer,int len)128 static int write_buffer(stream_t *s, void *buffer, int len)
129 {
130     struct priv *p = s->priv;
131     return write(p->fd, buffer, len);
132 }
133 
seek(stream_t * s,int64_t newpos)134 static int seek(stream_t *s, int64_t newpos)
135 {
136     struct priv *p = s->priv;
137     return lseek(p->fd, newpos, SEEK_SET) != (off_t)-1;
138 }
139 
s_close(stream_t * s)140 static void s_close(stream_t *s)
141 {
142     struct priv *p = s->priv;
143     if (p->close)
144         close(p->fd);
145 }
146 
147 // If url is a file:// URL, return the local filename, otherwise return NULL.
mp_file_url_to_filename(void * talloc_ctx,bstr url)148 char *mp_file_url_to_filename(void *talloc_ctx, bstr url)
149 {
150     bstr proto = mp_split_proto(url, &url);
151     if (bstrcasecmp0(proto, "file") != 0)
152         return NULL;
153     char *filename = bstrto0(talloc_ctx, url);
154     mp_url_unescape_inplace(filename);
155 #if HAVE_DOS_PATHS
156     // extract '/' from '/x:/path'
157     if (filename[0] == '/' && filename[1] && filename[2] == ':')
158         memmove(filename, filename + 1, strlen(filename)); // including \0
159 #endif
160     return filename;
161 }
162 
163 // Return talloc_strdup's filesystem path if local, otherwise NULL.
164 // Unlike mp_file_url_to_filename(), doesn't return NULL if already local.
mp_file_get_path(void * talloc_ctx,bstr url)165 char *mp_file_get_path(void *talloc_ctx, bstr url)
166 {
167     if (mp_split_proto(url, &(bstr){0}).len) {
168         return mp_file_url_to_filename(talloc_ctx, url);
169     } else {
170         return bstrto0(talloc_ctx, url);
171     }
172 }
173 
174 #if HAVE_BSD_FSTATFS
check_stream_network(int fd)175 static bool check_stream_network(int fd)
176 {
177     struct statfs fs;
178     const char *stypes[] = { "afpfs", "nfs", "smbfs", "webdav", "osxfusefs",
179                              "fuse", "fusefs.sshfs", "macfuse", NULL };
180     if (fstatfs(fd, &fs) == 0)
181         for (int i=0; stypes[i]; i++)
182             if (strcmp(stypes[i], fs.f_fstypename) == 0)
183                 return true;
184     return false;
185 
186 }
187 #elif HAVE_LINUX_FSTATFS
check_stream_network(int fd)188 static bool check_stream_network(int fd)
189 {
190     struct statfs fs;
191     const uint32_t stypes[] = {
192         0x5346414F  /*AFS*/,    0x61756673  /*AUFS*/,   0x00C36400  /*CEPH*/,
193         0xFF534D42  /*CIFS*/,   0x73757245  /*CODA*/,   0x19830326  /*FHGFS*/,
194         0x65735546  /*FUSEBLK*/,0x65735543  /*FUSECTL*/,0x1161970   /*GFS*/,
195         0x47504653  /*GPFS*/,   0x6B414653  /*KAFS*/,   0x0BD00BD0  /*LUSTRE*/,
196         0x564C      /*NCP*/,    0x6969      /*NFS*/,    0x6E667364  /*NFSD*/,
197         0xAAD7AAEA  /*PANFS*/,  0x50495045  /*PIPEFS*/, 0x517B      /*SMB*/,
198         0xBEEFDEAD  /*SNFS*/,   0xBACBACBC  /*VMHGFS*/, 0x7461636f  /*OCFS2*/,
199         0xFE534D42  /*SMB2*/,   0x61636673  /*ACFS*/,   0x013111A8  /*IBRIX*/,
200         0
201     };
202     if (fstatfs(fd, &fs) == 0) {
203         for (int i=0; stypes[i]; i++) {
204             if (stypes[i] == fs.f_type)
205                 return true;
206         }
207     }
208     return false;
209 
210 }
211 #elif defined(_WIN32)
check_stream_network(int fd)212 static bool check_stream_network(int fd)
213 {
214     NTSTATUS (NTAPI *pNtQueryVolumeInformationFile)(HANDLE,
215         PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS) = NULL;
216 
217     // NtQueryVolumeInformationFile is an internal Windows function. It has
218     // been present since Windows XP, however this code should fail gracefully
219     // if it's removed from a future version of Windows.
220     HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
221     pNtQueryVolumeInformationFile = (NTSTATUS (NTAPI*)(HANDLE,
222         PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS))
223         GetProcAddress(ntdll, "NtQueryVolumeInformationFile");
224 
225     if (!pNtQueryVolumeInformationFile)
226         return false;
227 
228     HANDLE h = (HANDLE)_get_osfhandle(fd);
229     if (h == INVALID_HANDLE_VALUE)
230         return false;
231 
232     FILE_FS_DEVICE_INFORMATION info = { 0 };
233     IO_STATUS_BLOCK io;
234     NTSTATUS status = pNtQueryVolumeInformationFile(h, &io, &info,
235         sizeof(info), FileFsDeviceInformation);
236     if (!NT_SUCCESS(status))
237         return false;
238 
239     return info.DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM ||
240            (info.Characteristics & FILE_REMOTE_DEVICE);
241 }
242 #else
check_stream_network(int fd)243 static bool check_stream_network(int fd)
244 {
245     return false;
246 }
247 #endif
248 
open_f(stream_t * stream,const struct stream_open_args * args)249 static int open_f(stream_t *stream, const struct stream_open_args *args)
250 {
251     struct priv *p = talloc_ptrtype(stream, p);
252     *p = (struct priv) {
253         .fd = -1,
254     };
255     stream->priv = p;
256     stream->is_local_file = true;
257 
258     bool strict_fs = args->flags & STREAM_LOCAL_FS_ONLY;
259     bool write = stream->mode == STREAM_WRITE;
260     int m = O_CLOEXEC | (write ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY);
261 
262     char *filename = stream->path;
263     char *url = "";
264     if (!strict_fs) {
265         char *fn = mp_file_url_to_filename(stream, bstr0(stream->url));
266         if (fn)
267             filename = stream->path = fn;
268         url = stream->url;
269     }
270 
271     bool is_fdclose = strncmp(url, "fdclose://", 10) == 0;
272     if (strncmp(url, "fd://", 5) == 0 || is_fdclose) {
273         char *begin = strstr(stream->url, "://") + 3, *end = NULL;
274         p->fd = strtol(begin, &end, 0);
275         if (!end || end == begin || end[0]) {
276             MP_ERR(stream, "Invalid FD: %s\n", stream->url);
277             return STREAM_ERROR;
278         }
279         if (is_fdclose)
280             p->close = true;
281     } else if (!strict_fs && !strcmp(filename, "-")) {
282         if (!write) {
283             MP_INFO(stream, "Reading from stdin...\n");
284             p->fd = 0;
285         } else {
286             MP_INFO(stream, "Writing to stdout...\n");
287             p->fd = 1;
288         }
289     } else {
290         if (bstr_startswith0(bstr0(stream->url), "appending://"))
291             p->appending = true;
292 
293         mode_t openmode = S_IRUSR | S_IWUSR;
294 #ifndef __MINGW32__
295         openmode |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
296         if (!write)
297             m |= O_NONBLOCK;
298 #endif
299         p->fd = open(filename, m | O_BINARY, openmode);
300         if (p->fd < 0) {
301             MP_ERR(stream, "Cannot open file '%s': %s\n",
302                    filename, mp_strerror(errno));
303             return STREAM_ERROR;
304         }
305         p->close = true;
306     }
307 
308     struct stat st;
309     if (fstat(p->fd, &st) == 0) {
310         if (S_ISDIR(st.st_mode)) {
311             stream->is_directory = true;
312             if (!(args->flags & STREAM_LESS_NOISE))
313                 MP_INFO(stream, "This is a directory - adding to playlist.\n");
314         } else if (S_ISREG(st.st_mode)) {
315             p->regular_file = true;
316 #ifndef __MINGW32__
317             // O_NONBLOCK has weird semantics on file locks; remove it.
318             int val = fcntl(p->fd, F_GETFL) & ~(unsigned)O_NONBLOCK;
319             fcntl(p->fd, F_SETFL, val);
320 #endif
321         } else {
322             p->use_poll = true;
323         }
324     }
325 
326 #ifdef __MINGW32__
327     setmode(p->fd, O_BINARY);
328 #endif
329 
330     off_t len = lseek(p->fd, 0, SEEK_END);
331     lseek(p->fd, 0, SEEK_SET);
332     if (len != (off_t)-1) {
333         stream->seek = seek;
334         stream->seekable = true;
335     }
336 
337     stream->fast_skip = true;
338     stream->fill_buffer = fill_buffer;
339     stream->write_buffer = write_buffer;
340     stream->get_size = get_size;
341     stream->close = s_close;
342 
343     if (check_stream_network(p->fd)) {
344         stream->streaming = true;
345 #if HAVE_COCOA
346         if (fcntl(p->fd, F_RDAHEAD, 0) < 0) {
347             MP_VERBOSE(stream, "Cannot disable read ahead on file '%s': %s\n",
348                        filename, mp_strerror(errno));
349         }
350 #endif
351     }
352 
353     p->orig_size = get_size(stream);
354 
355     p->cancel = mp_cancel_new(p);
356     if (stream->cancel)
357         mp_cancel_set_parent(p->cancel, stream->cancel);
358 
359     return STREAM_OK;
360 }
361 
362 const stream_info_t stream_info_file = {
363     .name = "file",
364     .open2 = open_f,
365     .protocols = (const char*const[]){ "file", "", "appending", NULL },
366     .can_write = true,
367     .local_fs = true,
368     .stream_origin = STREAM_ORIGIN_FS,
369 };
370 
371 const stream_info_t stream_info_fd = {
372     .name = "fd",
373     .open2 = open_f,
374     .protocols = (const char*const[]){ "fd", "fdclose", NULL },
375     .can_write = true,
376     .stream_origin = STREAM_ORIGIN_UNSAFE,
377 };
378