Coverage Report

Created: 2025-10-27 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/osdep/io.c
Line
Count
Source
1
/*
2
 * unicode/utf-8 I/O helpers and wrappers for Windows
3
 *
4
 * Contains parts based on libav code (http://libav.org).
5
 *
6
 * This file is part of mpv.
7
 *
8
 * mpv is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * mpv is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include <assert.h>
23
#include <errno.h>
24
#include <fcntl.h>
25
#include <stdint.h>
26
#include <stdlib.h>
27
#include <sys/types.h>
28
#include <sys/stat.h>
29
#include <limits.h>
30
31
#include "mpv_talloc.h"
32
33
#include "config.h"
34
#include "common/common.h"
35
#include "misc/random.h"
36
#include "osdep/io.h"
37
#include "osdep/terminal.h"
38
39
#if HAVE_UWP
40
// Missing from MinGW headers.
41
#include <windows.h>
42
WINBASEAPI UINT WINAPI GetTempFileNameW(LPCWSTR lpPathName, LPCWSTR lpPrefixString,
43
                                        UINT uUnique, LPWSTR lpTempFileName);
44
WINBASEAPI DWORD WINAPI GetCurrentDirectoryW(DWORD nBufferLength, LPWSTR lpBuffer);
45
WINBASEAPI DWORD WINAPI GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength,
46
                                         LPWSTR lpBuffer, LPWSTR *lpFilePart);
47
#endif
48
49
// Set the CLOEXEC flag on the given fd.
50
// On error, false is returned (and errno set).
51
bool mp_set_cloexec(int fd)
52
24.9k
{
53
24.9k
#if defined(F_SETFD)
54
24.9k
    if (fd >= 0) {
55
24.9k
        int flags = fcntl(fd, F_GETFD);
56
24.9k
        if (flags == -1)
57
0
            return false;
58
24.9k
        if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
59
0
            return false;
60
24.9k
    }
61
24.9k
#endif
62
24.9k
    return true;
63
24.9k
}
64
65
#ifndef _WIN32
66
int mp_make_cloexec_pipe(int pipes[2])
67
7.74k
{
68
7.74k
    if (pipe(pipes) != 0) {
69
0
        pipes[0] = pipes[1] = -1;
70
0
        return -1;
71
0
    }
72
73
23.2k
    for (int i = 0; i < 2; i++)
74
15.4k
        mp_set_cloexec(pipes[i]);
75
7.74k
    return 0;
76
7.74k
}
77
78
// create a pipe, and set it to non-blocking (and also set FD_CLOEXEC)
79
int mp_make_wakeup_pipe(int pipes[2])
80
7.74k
{
81
7.74k
    if (mp_make_cloexec_pipe(pipes) < 0)
82
0
        return -1;
83
84
23.2k
    for (int i = 0; i < 2; i++) {
85
15.4k
        int val = fcntl(pipes[i], F_GETFL) | O_NONBLOCK;
86
15.4k
        fcntl(pipes[i], F_SETFL, val);
87
15.4k
    }
88
7.74k
    return 0;
89
7.74k
}
90
91
void mp_flush_wakeup_pipe(int pipe_end)
92
4.70k
{
93
4.70k
    char buf[100];
94
4.70k
    (void)read(pipe_end, buf, sizeof(buf));
95
4.70k
}
96
#endif
97
98
#ifdef _WIN32
99
100
#include <windows.h>
101
#include <wchar.h>
102
#include <stdio.h>
103
#include <stddef.h>
104
105
#include "osdep/windows_utils.h"
106
107
//copied and modified from libav
108
//http://git.libav.org/?p=libav.git;a=blob;f=libavformat/os_support.c;h=a0fcd6c9ba2be4b0dbcc476f6c53587345cc1152;hb=HEADl30
109
110
wchar_t *mp_from_utf8(void *talloc_ctx, const char *s)
111
{
112
    int count = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
113
    if (count <= 0)
114
        abort();
115
    wchar_t *ret = talloc_array(talloc_ctx, wchar_t, count);
116
    MultiByteToWideChar(CP_UTF8, 0, s, -1, ret, count);
117
    if (count <= 0)
118
        abort();
119
    return ret;
120
}
121
122
char *mp_to_utf8(void *talloc_ctx, const wchar_t *s)
123
{
124
    int count = WideCharToMultiByte(CP_UTF8, 0, s, -1, NULL, 0, NULL, NULL);
125
    if (count <= 0)
126
        abort();
127
    char *ret = talloc_array(talloc_ctx, char, count);
128
    WideCharToMultiByte(CP_UTF8, 0, s, -1, ret, count, NULL, NULL);
129
    if (count <= 0)
130
        abort();
131
    return ret;
132
}
133
134
#endif // _WIN32
135
136
#ifdef _WIN32
137
138
#include <stdatomic.h>
139
140
#include <io.h>
141
#include <fcntl.h>
142
143
#include "osdep/threads.h"
144
#include "osdep/getpid.h"
145
146
static void set_errno_from_lasterror(void)
147
{
148
    // This just handles the error codes expected from CreateFile at the moment
149
    switch (GetLastError()) {
150
    case ERROR_FILE_NOT_FOUND:
151
        errno = ENOENT;
152
        break;
153
    case ERROR_SHARING_VIOLATION:
154
    case ERROR_ACCESS_DENIED:
155
        errno = EACCES;
156
        break;
157
    case ERROR_FILE_EXISTS:
158
    case ERROR_ALREADY_EXISTS:
159
        errno = EEXIST;
160
        break;
161
    case ERROR_PIPE_BUSY:
162
        errno = EAGAIN;
163
        break;
164
    default:
165
        errno = EINVAL;
166
        break;
167
    }
168
}
169
170
static time_t filetime_to_unix_time(int64_t wintime)
171
{
172
    static const int64_t hns_per_second = 10000000ll;
173
    static const int64_t win_to_unix_epoch = 11644473600ll;
174
    return wintime / hns_per_second - win_to_unix_epoch;
175
}
176
177
static bool get_file_ids_win8(HANDLE h, dev_t *dev, ino_t *ino)
178
{
179
    FILE_ID_INFO ii;
180
    if (!GetFileInformationByHandleEx(h, FileIdInfo, &ii, sizeof(ii)))
181
        return false;
182
    *dev = ii.VolumeSerialNumber;
183
    // The definition of FILE_ID_128 differs between mingw-w64 and the Windows
184
    // SDK, but we can ignore that by just memcpying it. This will also
185
    // truncate the file ID on 32-bit Windows, which doesn't support __int128.
186
    // 128-bit file IDs are only used for ReFS, so that should be okay.
187
    static_assert(sizeof(*ino) <= sizeof(ii.FileId), "");
188
    memcpy(ino, &ii.FileId, sizeof(*ino));
189
    return true;
190
}
191
192
#if HAVE_UWP
193
static bool get_file_ids(HANDLE h, dev_t *dev, ino_t *ino)
194
{
195
    return false;
196
}
197
#else
198
static bool get_file_ids(HANDLE h, dev_t *dev, ino_t *ino)
199
{
200
    // GetFileInformationByHandle works on FAT partitions and Windows 7, but
201
    // doesn't work in UWP and can produce non-unique IDs on ReFS
202
    BY_HANDLE_FILE_INFORMATION bhfi;
203
    if (!GetFileInformationByHandle(h, &bhfi))
204
        return false;
205
    *dev = bhfi.dwVolumeSerialNumber;
206
    *ino = ((ino_t)bhfi.nFileIndexHigh << 32) | bhfi.nFileIndexLow;
207
    return true;
208
}
209
#endif
210
211
// Like fstat(), but with a Windows HANDLE
212
static int hstat(HANDLE h, struct mp_stat *buf)
213
{
214
    // Handle special (or unknown) file types first
215
    switch (GetFileType(h) & ~FILE_TYPE_REMOTE) {
216
    case FILE_TYPE_PIPE:
217
        *buf = (struct mp_stat){ .st_nlink = 1, .st_mode = _S_IFIFO | 0644 };
218
        return 0;
219
    case FILE_TYPE_CHAR: // character device
220
        *buf = (struct mp_stat){ .st_nlink = 1, .st_mode = _S_IFCHR | 0644 };
221
        return 0;
222
    case FILE_TYPE_UNKNOWN:
223
        errno = EBADF;
224
        return -1;
225
    }
226
227
    struct mp_stat st = { 0 };
228
229
    FILE_BASIC_INFO bi;
230
    if (!GetFileInformationByHandleEx(h, FileBasicInfo, &bi, sizeof(bi))) {
231
        errno = EBADF;
232
        return -1;
233
    }
234
    st.st_atime = filetime_to_unix_time(bi.LastAccessTime.QuadPart);
235
    st.st_mtime = filetime_to_unix_time(bi.LastWriteTime.QuadPart);
236
    st.st_ctime = filetime_to_unix_time(bi.ChangeTime.QuadPart);
237
238
    FILE_STANDARD_INFO si;
239
    if (!GetFileInformationByHandleEx(h, FileStandardInfo, &si, sizeof(si))) {
240
        errno = EBADF;
241
        return -1;
242
    }
243
    st.st_nlink = si.NumberOfLinks;
244
245
    // Here we pretend Windows has POSIX permissions by pretending all
246
    // directories are 755 and regular files are 644
247
    if (si.Directory) {
248
        st.st_mode |= _S_IFDIR | 0755;
249
    } else {
250
        st.st_mode |= _S_IFREG | 0644;
251
        st.st_size = si.EndOfFile.QuadPart;
252
    }
253
254
    if (!get_file_ids_win8(h, &st.st_dev, &st.st_ino)) {
255
        // Fall back to the Windows 7 method (also used for FAT in Win8)
256
        if (!get_file_ids(h, &st.st_dev, &st.st_ino)) {
257
            errno = EBADF;
258
            return -1;
259
        }
260
    }
261
262
    *buf = st;
263
    return 0;
264
}
265
266
int mp_stat(const char *path, struct mp_stat *buf)
267
{
268
    wchar_t *wpath = mp_from_utf8(NULL, path);
269
    HANDLE h = CreateFileW(wpath, FILE_READ_ATTRIBUTES,
270
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
271
        OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | SECURITY_SQOS_PRESENT |
272
        SECURITY_IDENTIFICATION, NULL);
273
    talloc_free(wpath);
274
    if (h == INVALID_HANDLE_VALUE) {
275
        set_errno_from_lasterror();
276
        return -1;
277
    }
278
279
    int ret = hstat(h, buf);
280
    CloseHandle(h);
281
    return ret;
282
}
283
284
int mp_fstat(int fd, struct mp_stat *buf)
285
{
286
    HANDLE h = (HANDLE)_get_osfhandle(fd);
287
    if (h == INVALID_HANDLE_VALUE) {
288
        errno = EBADF;
289
        return -1;
290
    }
291
    // Use mpv's hstat() function rather than MSVCRT's fstat() because mpv's
292
    // supports directories and device/inode numbers.
293
    return hstat(h, buf);
294
}
295
296
static inline HANDLE get_handle(FILE *stream)
297
{
298
    HANDLE wstream = INVALID_HANDLE_VALUE;
299
300
    if (stream == stdout || stream == stderr) {
301
        wstream = GetStdHandle(stream == stdout ?
302
                               STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
303
    }
304
    return wstream;
305
}
306
307
size_t mp_fwrite(const void *restrict buffer, size_t size, size_t count,
308
                 FILE *restrict stream)
309
{
310
    if (!size || !count)
311
        return 0;
312
313
    HANDLE wstream = get_handle(stream);
314
    if (mp_check_console(wstream)) {
315
        unsigned char *start = (unsigned char *)buffer;
316
        size_t c = 0;
317
        for (; c < count; ++c) {
318
            if (mp_console_write(wstream, (bstr){start, size}) <= 0)
319
                break;
320
            start += size;
321
        }
322
        return c;
323
    }
324
325
#undef fwrite
326
    return fwrite(buffer, size, count, stream);
327
}
328
329
#if HAVE_UWP
330
MP_PRINTF_ATTRIBUTE(2, 0)
331
static int mp_vfprintf(FILE *stream, const char *format, va_list args)
332
{
333
    return vfprintf(stream, format, args);
334
}
335
#else
336
337
MP_PRINTF_ATTRIBUTE(2, 0)
338
static int mp_vfprintf(FILE *stream, const char *format, va_list args)
339
{
340
    HANDLE wstream = get_handle(stream);
341
    if (mp_check_console(wstream))
342
        return mp_console_vfprintf(wstream, format, args);
343
344
    return vfprintf(stream, format, args);
345
}
346
#endif
347
348
int mp_fprintf(FILE *stream, const char *format, ...)
349
{
350
    int res;
351
    va_list args;
352
    va_start(args, format);
353
    res = mp_vfprintf(stream, format, args);
354
    va_end(args);
355
    return res;
356
}
357
358
int mp_printf(const char *format, ...)
359
{
360
    int res;
361
    va_list args;
362
    va_start(args, format);
363
    res = mp_vfprintf(stdout, format, args);
364
    va_end(args);
365
    return res;
366
}
367
368
int mp_open(const char *filename, int oflag, ...)
369
{
370
    // Always use all share modes, which is useful for opening files that are
371
    // open in other processes, and also more POSIX-like
372
    static const DWORD share =
373
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
374
    // Setting FILE_APPEND_DATA and avoiding GENERIC_WRITE/FILE_WRITE_DATA
375
    // will make the file handle use atomic append behavior
376
    // However to implement ftruncate we need FILE_WRITE_DATA
377
    static const DWORD append =
378
        FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_WRITE_DATA;
379
380
    DWORD access = 0;
381
    DWORD disposition = 0;
382
    DWORD flags = 0;
383
384
    switch (oflag & (_O_RDONLY | _O_RDWR | _O_WRONLY | _O_APPEND)) {
385
    case _O_RDONLY:
386
        access = GENERIC_READ;
387
        flags |= FILE_FLAG_BACKUP_SEMANTICS; // For opening directories
388
        break;
389
    case _O_RDWR:
390
        access = GENERIC_READ | GENERIC_WRITE;
391
        break;
392
    case _O_RDWR | _O_APPEND:
393
    case _O_RDONLY | _O_APPEND:
394
        access = GENERIC_READ | append;
395
        break;
396
    case _O_WRONLY:
397
        access = GENERIC_WRITE;
398
        break;
399
    case _O_WRONLY | _O_APPEND:
400
        access = append;
401
        break;
402
    default:
403
        errno = EINVAL;
404
        return -1;
405
    }
406
407
    switch (oflag & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
408
    case 0:
409
    case _O_EXCL: // Like MSVCRT, ignore invalid use of _O_EXCL
410
        disposition = OPEN_EXISTING;
411
        break;
412
    case _O_TRUNC:
413
    case _O_TRUNC | _O_EXCL:
414
        disposition = TRUNCATE_EXISTING;
415
        break;
416
    case _O_CREAT:
417
        disposition = OPEN_ALWAYS;
418
        flags |= FILE_ATTRIBUTE_NORMAL;
419
        break;
420
    case _O_CREAT | _O_TRUNC:
421
        disposition = CREATE_ALWAYS;
422
        break;
423
    case _O_CREAT | _O_EXCL:
424
    case _O_CREAT | _O_EXCL | _O_TRUNC:
425
        disposition = CREATE_NEW;
426
        flags |= FILE_ATTRIBUTE_NORMAL;
427
        break;
428
    }
429
430
    // Opening a named pipe as a file can allow the pipe server to impersonate
431
    // mpv's process, which could be a security issue. Set SQOS flags, so pipe
432
    // servers can only identify the mpv process, not impersonate it.
433
    if (disposition != CREATE_NEW)
434
        flags |= SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION;
435
436
    // Keep the same semantics for some MSVCRT-specific flags
437
    if (oflag & _O_TEMPORARY) {
438
        flags |= FILE_FLAG_DELETE_ON_CLOSE;
439
        access |= DELETE;
440
    }
441
    if (oflag & _O_SHORT_LIVED)
442
        flags |= FILE_ATTRIBUTE_TEMPORARY;
443
    if (oflag & _O_SEQUENTIAL) {
444
        flags |= FILE_FLAG_SEQUENTIAL_SCAN;
445
    } else if (oflag & _O_RANDOM) {
446
        flags |= FILE_FLAG_RANDOM_ACCESS;
447
    }
448
449
    // Open the Windows file handle
450
    wchar_t *wpath = mp_from_utf8(NULL, filename);
451
    HANDLE h = CreateFileW(wpath, access, share, NULL, disposition, flags, NULL);
452
    talloc_free(wpath);
453
    if (h == INVALID_HANDLE_VALUE) {
454
        set_errno_from_lasterror();
455
        return -1;
456
    }
457
458
    // Map the Windows file handle to a CRT file descriptor. Note: MSVCRT only
459
    // cares about the following oflags.
460
    oflag &= _O_APPEND | _O_RDONLY | _O_RDWR | _O_WRONLY;
461
    oflag |= _O_NOINHERIT; // We never create inheritable handles
462
    int fd = _open_osfhandle((intptr_t)h, oflag);
463
    if (fd < 0) {
464
        CloseHandle(h);
465
        return -1;
466
    }
467
468
    return fd;
469
}
470
471
int mp_creat(const char *filename, int mode)
472
{
473
    return mp_open(filename, _O_CREAT | _O_WRONLY | _O_TRUNC, mode);
474
}
475
476
int mp_rename(const char *oldpath, const char *newpath)
477
{
478
    wchar_t *woldpath = mp_from_utf8(NULL, oldpath),
479
        *wnewpath = mp_from_utf8(NULL, newpath);
480
    BOOL ok = MoveFileExW(woldpath, wnewpath, MOVEFILE_REPLACE_EXISTING);
481
    talloc_free(woldpath);
482
    talloc_free(wnewpath);
483
    if (!ok) {
484
        set_errno_from_lasterror();
485
        return -1;
486
    }
487
    return 0;
488
}
489
490
FILE *mp_fopen(const char *filename, const char *mode)
491
{
492
    if (!mode[0]) {
493
        errno = EINVAL;
494
        return NULL;
495
    }
496
497
    int rwmode;
498
    int oflags = 0;
499
    switch (mode[0]) {
500
    case 'r':
501
        rwmode = _O_RDONLY;
502
        break;
503
    case 'w':
504
        rwmode = _O_WRONLY;
505
        oflags |= _O_CREAT | _O_TRUNC;
506
        break;
507
    case 'a':
508
        rwmode = _O_WRONLY;
509
        oflags |= _O_CREAT | _O_APPEND;
510
        break;
511
    default:
512
        errno = EINVAL;
513
        return NULL;
514
    }
515
516
    // Parse extra mode flags
517
    for (const char *pos = mode + 1; *pos; pos++) {
518
        switch (*pos) {
519
        case '+': rwmode = _O_RDWR;  break;
520
        case 'x': oflags |= _O_EXCL; break;
521
        // Ignore unknown flags (glibc does too)
522
        default: break;
523
        }
524
    }
525
526
    // Open a CRT file descriptor
527
    int fd = mp_open(filename, rwmode | oflags);
528
    if (fd < 0)
529
        return NULL;
530
531
    // Add 'b' to the mode so the CRT knows the file is opened in binary mode
532
    char bmode[] = { mode[0], 'b', rwmode == _O_RDWR ? '+' : '\0', '\0' };
533
    FILE *fp = fdopen(fd, bmode);
534
    if (!fp) {
535
        close(fd);
536
        return NULL;
537
    }
538
539
    return fp;
540
}
541
542
// Windows' MAX_PATH/PATH_MAX/FILENAME_MAX is fixed to 260, but this limit
543
// applies to unicode paths encoded with wchar_t (2 bytes on Windows). The UTF-8
544
// version could end up bigger in memory. In the worst case each wchar_t is
545
// encoded to 3 bytes in UTF-8, so in the worst case we have:
546
//      wcslen(wpath) * 3 <= strlen(utf8path)
547
// Thus we need MP_PATH_MAX as the UTF-8/char version of PATH_MAX.
548
// Also make sure there's free space for the terminating \0.
549
// (For codepoints encoded as UTF-16 surrogate pairs, UTF-8 has the same length.)
550
// Lastly, note that neither _wdirent nor WIN32_FIND_DATA can store filenames
551
// longer than this, so long-path support for readdir() is impossible.
552
#define MP_FILENAME_MAX (FILENAME_MAX * 3 + 1)
553
554
struct mp_dir {
555
    DIR crap;   // must be first member
556
    _WDIR *wdir;
557
    union {
558
        struct dirent dirent;
559
        // dirent has space only for FILENAME_MAX bytes. _wdirent has space for
560
        // FILENAME_MAX wchar_t, which might end up bigger as UTF-8 in some
561
        // cases. Guarantee we can always hold _wdirent.d_name converted to
562
        // UTF-8 (see above).
563
        // This works because dirent.d_name is the last member of dirent.
564
        char space[MP_FILENAME_MAX];
565
    };
566
};
567
568
DIR* mp_opendir(const char *path)
569
{
570
    wchar_t *wpath = mp_from_utf8(NULL, path);
571
    _WDIR *wdir = _wopendir(wpath);
572
    talloc_free(wpath);
573
    if (!wdir)
574
        return NULL;
575
    struct mp_dir *mpdir = talloc(NULL, struct mp_dir);
576
    // DIR is supposed to be opaque, but unfortunately the MinGW headers still
577
    // define it. Make sure nobody tries to use it.
578
    memset(&mpdir->crap, 0xCD, sizeof(mpdir->crap));
579
    mpdir->wdir = wdir;
580
    return (DIR*)mpdir;
581
}
582
583
struct dirent* mp_readdir(DIR *dir)
584
{
585
    struct mp_dir *mpdir = (struct mp_dir*)dir;
586
    struct _wdirent *wdirent = _wreaddir(mpdir->wdir);
587
    if (!wdirent)
588
        return NULL;
589
    size_t buffersize = sizeof(mpdir->space) - offsetof(struct dirent, d_name);
590
    WideCharToMultiByte(CP_UTF8, 0, wdirent->d_name, -1, mpdir->dirent.d_name,
591
                        buffersize, NULL, NULL);
592
    mpdir->dirent.d_ino = 0;
593
    mpdir->dirent.d_reclen = 0;
594
    mpdir->dirent.d_namlen = strlen(mpdir->dirent.d_name);
595
    return &mpdir->dirent;
596
}
597
598
int mp_closedir(DIR *dir)
599
{
600
    struct mp_dir *mpdir = (struct mp_dir*)dir;
601
    int res = _wclosedir(mpdir->wdir);
602
    talloc_free(mpdir);
603
    return res;
604
}
605
606
int mp_mkdir(const char *path, int mode)
607
{
608
    wchar_t *wpath = mp_from_utf8(NULL, path);
609
    int res = _wmkdir(wpath);
610
    talloc_free(wpath);
611
    return res;
612
}
613
614
int mp_unlink(const char *path)
615
{
616
    wchar_t *wpath = mp_from_utf8(NULL, path);
617
    int res = _wunlink(wpath);
618
    talloc_free(wpath);
619
    return res;
620
}
621
622
char *mp_win32_getcwd(char *buf, size_t size)
623
{
624
    if (size >= SIZE_MAX / 3 - 1) {
625
        errno = ENOMEM;
626
        return NULL;
627
    }
628
    size_t wbuffer = size * 3 + 1;
629
    wchar_t *wres = talloc_array(NULL, wchar_t, wbuffer);
630
    DWORD wlen = GetFullPathNameW(L".", wbuffer, wres, NULL);
631
    if (wlen >= wbuffer || wlen == 0) {
632
        talloc_free(wres);
633
        errno = wlen ? ERANGE : ENOENT;
634
        return NULL;
635
    }
636
    char *t = mp_to_utf8(NULL, wres);
637
    talloc_free(wres);
638
    size_t st = strlen(t);
639
    if (st >= size) {
640
        talloc_free(t);
641
        errno = ERANGE;
642
        return NULL;
643
    }
644
    memcpy(buf, t, st + 1);
645
    talloc_free(t);
646
    return buf;
647
}
648
649
static char **utf8_environ;
650
static void *utf8_environ_ctx;
651
652
static void free_env(void)
653
{
654
    talloc_free(utf8_environ_ctx);
655
    utf8_environ_ctx = NULL;
656
    utf8_environ = NULL;
657
}
658
659
// Note: UNIX getenv() returns static strings, and we try to do the same. Since
660
// using putenv() is not multithreading safe, we don't expect env vars to change
661
// at runtime, and converting/allocating them in advance is ok.
662
static void init_getenv(void)
663
{
664
#if !HAVE_UWP
665
    wchar_t *wenv_begin = GetEnvironmentStringsW();
666
    if (!wenv_begin)
667
        return;
668
    utf8_environ_ctx = talloc_new(NULL);
669
    int num_env = 0;
670
    wchar_t *wenv = wenv_begin;
671
    while (1) {
672
        size_t len = wcslen(wenv);
673
        if (!len)
674
            break;
675
        char *s = mp_to_utf8(utf8_environ_ctx, wenv);
676
        MP_TARRAY_APPEND(utf8_environ_ctx, utf8_environ, num_env, s);
677
        wenv += len + 1;
678
    }
679
    FreeEnvironmentStringsW(wenv_begin);
680
    MP_TARRAY_APPEND(utf8_environ_ctx, utf8_environ, num_env, NULL);
681
    // Avoid showing up in leak detectors etc.
682
    atexit(free_env);
683
#endif
684
}
685
686
char *mp_getenv(const char *name)
687
{
688
    static mp_once once_init_getenv = MP_STATIC_ONCE_INITIALIZER;
689
    mp_exec_once(&once_init_getenv, init_getenv);
690
    // Copied from musl, http://git.musl-libc.org/cgit/musl/tree/COPYRIGHT
691
    // Copyright © 2005-2013 Rich Felker, standard MIT license
692
    int i;
693
    size_t l = strlen(name);
694
    if (!utf8_environ || !*name || strchr(name, '=')) return NULL;
695
    for (i=0; utf8_environ[i] && (strncmp(name, utf8_environ[i], l)
696
            || utf8_environ[i][l] != '='); i++) {}
697
    if (utf8_environ[i]) return utf8_environ[i] + l+1;
698
    return NULL;
699
}
700
701
char ***mp_penviron(void)
702
{
703
    mp_getenv("");  // ensure init
704
    return &utf8_environ;  // `environ' should be an l-value
705
}
706
707
off_t mp_lseek64(int fd, off_t offset, int whence)
708
{
709
    HANDLE h = (HANDLE)_get_osfhandle(fd);
710
    if (h != INVALID_HANDLE_VALUE && GetFileType(h) != FILE_TYPE_DISK) {
711
        errno = ESPIPE;
712
        return (off_t)-1;
713
    }
714
    return _lseeki64(fd, offset, whence);
715
}
716
717
int mp_ftruncate64(int fd, off_t length)
718
{
719
    if (_chsize_s(fd, length) == 0)
720
        return 0;
721
    return -1;
722
}
723
724
thread_local
725
static struct {
726
    DWORD errcode;
727
    char *errstring;
728
} mp_dl_result = {
729
    .errcode = 0,
730
    .errstring = NULL
731
};
732
733
static void mp_dl_free(void)
734
{
735
    talloc_free(mp_dl_result.errstring);
736
}
737
738
static void mp_dl_init(void)
739
{
740
    atexit(mp_dl_free);
741
}
742
743
void *mp_dlopen(const char *filename, int flag)
744
{
745
    HMODULE lib = NULL;
746
    void *ta_ctx = talloc_new(NULL);
747
    wchar_t *wfilename = mp_from_utf8(ta_ctx, filename);
748
749
    DWORD len = GetFullPathNameW(wfilename, 0, NULL, NULL);
750
    if (!len)
751
        goto err;
752
753
    wchar_t *path = talloc_array(ta_ctx, wchar_t, len);
754
    len = GetFullPathNameW(wfilename, len, path, NULL);
755
    if (!len)
756
        goto err;
757
758
    lib = LoadLibraryW(path);
759
760
err:
761
    talloc_free(ta_ctx);
762
    mp_dl_result.errcode = GetLastError();
763
    return (void *)lib;
764
}
765
766
void *mp_dlsym(void *handle, const char *symbol)
767
{
768
    FARPROC addr = GetProcAddress((HMODULE)handle, symbol);
769
    mp_dl_result.errcode = GetLastError();
770
    return (void *)addr;
771
}
772
773
char *mp_dlerror(void)
774
{
775
    static mp_once once_init_dlerror = MP_STATIC_ONCE_INITIALIZER;
776
    mp_exec_once(&once_init_dlerror, mp_dl_init);
777
    mp_dl_free();
778
779
    if (mp_dl_result.errcode == 0)
780
        return NULL;
781
782
    mp_dl_result.errstring = talloc_strdup(NULL, mp_HRESULT_to_str(mp_dl_result.errcode));
783
    mp_dl_result.errcode = 0;
784
785
    return mp_dl_result.errstring == NULL
786
        ? "unknown error"
787
        : mp_dl_result.errstring;
788
}
789
790
#if HAVE_UWP
791
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
792
{
793
    errno = ENOSYS;
794
    return MAP_FAILED;
795
}
796
797
int munmap(void *addr, size_t length)
798
{
799
    errno = ENOSYS;
800
    return -1;
801
}
802
803
int msync(void *addr, size_t length, int flags)
804
{
805
    errno = ENOSYS;
806
    return -1;
807
}
808
#else
809
// Limited mmap() wrapper, inspired by:
810
// http://code.google.com/p/mman-win32/source/browse/trunk/mman.c
811
812
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
813
{
814
    mp_assert(addr == NULL); // not implemented
815
    mp_assert(flags == MAP_SHARED); // not implemented
816
817
    HANDLE osf = (HANDLE)_get_osfhandle(fd);
818
    if (!osf) {
819
        errno = EBADF;
820
        return MAP_FAILED;
821
    }
822
823
    DWORD protect = 0;
824
    DWORD access = 0;
825
    if (prot & PROT_WRITE) {
826
        protect = PAGE_READWRITE;
827
        access = FILE_MAP_WRITE;
828
    } else if (prot & PROT_READ) {
829
        protect = PAGE_READONLY;
830
        access = FILE_MAP_READ;
831
    }
832
833
    DWORD l_low = (uint32_t)length;
834
    DWORD l_high = ((uint64_t)length) >> 32;
835
    HANDLE map = CreateFileMapping(osf, NULL, protect, l_high, l_low, NULL);
836
837
    if (!map) {
838
        errno = EACCES; // something random
839
        return MAP_FAILED;
840
    }
841
842
    DWORD o_low = (uint32_t)offset;
843
    DWORD o_high = ((uint64_t)offset) >> 32;
844
    void *p = MapViewOfFile(map, access, o_high, o_low, length);
845
846
    CloseHandle(map);
847
848
    if (!p) {
849
        errno = EINVAL;
850
        return MAP_FAILED;
851
    }
852
    return p;
853
}
854
855
int munmap(void *addr, size_t length)
856
{
857
    UnmapViewOfFile(addr);
858
    return 0;
859
}
860
861
int msync(void *addr, size_t length, int flags)
862
{
863
    FlushViewOfFile(addr, length);
864
    return 0;
865
}
866
#endif
867
868
locale_t newlocale(int category, const char *locale, locale_t base)
869
{
870
    return (locale_t)1;
871
}
872
873
locale_t uselocale(locale_t locobj)
874
{
875
    return (locale_t)1;
876
}
877
878
void freelocale(locale_t locobj)
879
{
880
}
881
882
#define MP_PIPE_BUF_SIZE 65536
883
884
int mp_make_cloexec_pipe(int pipes[2])
885
{
886
    if (_pipe(pipes, MP_PIPE_BUF_SIZE, _O_BINARY | _O_NOINHERIT) != 0) {
887
        pipes[0] = pipes[1] = -1;
888
        return -1;
889
    }
890
    return 0;
891
}
892
893
int mp_make_wakeup_pipe(int pipes[2])
894
{
895
    static atomic_ulong pipe_id = 0;
896
897
    pipes[0] = pipes[1] = -1;
898
    HANDLE handles[2];
899
    handles[0] = handles[1] = INVALID_HANDLE_VALUE;
900
901
    const char *pipe_name = mp_tprintf(55, "\\\\?\\pipe\\mpv\\%lu-%lu", mp_getpid(), atomic_fetch_add_explicit(&pipe_id, 1, memory_order_relaxed));
902
903
    handles[1] = CreateNamedPipeA(
904
        pipe_name,
905
        PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
906
        0,
907
        1,
908
        MP_PIPE_BUF_SIZE,
909
        MP_PIPE_BUF_SIZE,
910
        0,
911
        NULL
912
    );
913
    if (handles[1] == INVALID_HANDLE_VALUE) {
914
        set_errno_from_lasterror();
915
        goto error;
916
    }
917
918
    handles[0] = CreateFileA(
919
        pipe_name,
920
        GENERIC_READ,
921
        0,
922
        NULL,
923
        OPEN_EXISTING,
924
        FILE_FLAG_OVERLAPPED,
925
        NULL
926
    );
927
    if (handles[0] == INVALID_HANDLE_VALUE) {
928
        set_errno_from_lasterror();
929
        goto error;
930
    }
931
932
    if (!ConnectNamedPipe(handles[1], NULL) && GetLastError() != ERROR_PIPE_CONNECTED) {
933
        set_errno_from_lasterror();
934
        goto error;
935
    }
936
937
    for (int i = 0; i < 2; i++) {
938
        pipes[i] = _open_osfhandle((intptr_t)handles[i], 0);
939
        if (pipes[i] == -1)
940
            goto error;
941
    }
942
943
    return 0;
944
945
error:
946
    for (int i = 0; i < 2; i++) {
947
        if (pipes[i] != -1) {
948
            _close(pipes[i]);
949
            pipes[i] = -1;
950
        } else if (handles[i] != INVALID_HANDLE_VALUE) {
951
            CloseHandle(handles[i]);
952
        }
953
    }
954
955
    return -1;
956
}
957
958
void mp_flush_wakeup_pipe(int pipe_end)
959
{
960
    char buf[100];
961
    OVERLAPPED operation = {};
962
    HANDLE handle = (HANDLE)_get_osfhandle(pipe_end);
963
    if (handle == INVALID_HANDLE_VALUE)
964
        return;
965
    if (!ReadFile(handle, buf, sizeof(buf), NULL, &operation)) {
966
        if (GetLastError() != ERROR_IO_PENDING || !CancelIoEx(handle, &operation))
967
            set_errno_from_lasterror();
968
    }
969
}
970
971
#endif // __MINGW32__