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