Coverage Report

Created: 2026-07-30 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/goo/gfile.cc
Line
Count
Source
1
//========================================================================
2
//
3
// gfile.cc
4
//
5
// Miscellaneous file and directory name manipulation.
6
//
7
// Copyright 1996-2003 Glyph & Cog, LLC
8
//
9
//========================================================================
10
11
//========================================================================
12
//
13
// Modified under the Poppler project - http://poppler.freedesktop.org
14
//
15
// All changes made under the Poppler project to this file are licensed
16
// under GPL version 2 or later
17
//
18
// Copyright (C) 2006 Takashi Iwai <tiwai@suse.de>
19
// Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com>
20
// Copyright (C) 2008 Adam Batkin <adam@batkin.net>
21
// Copyright (C) 2008, 2010, 2012, 2013 Hib Eris <hib@hiberis.nl>
22
// Copyright (C) 2009, 2012, 2014, 2017, 2018, 2021, 2022, 2024-2026 Albert Astals Cid <aacid@kde.org>
23
// Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net>
24
// Copyright (C) 2013, 2018 Adam Reichold <adamreichold@myopera.com>
25
// Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com>
26
// Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de>
27
// Copyright (C) 2013, 2017 Thomas Freitag <Thomas.Freitag@alfa.de>
28
// Copyright (C) 2017 Christoph Cullmann <cullmann@kde.org>
29
// Copyright (C) 2018 Mojca Miklavec <mojca@macports.org>
30
// Copyright (C) 2019, 2021 Christian Persch <chpe@src.gnome.org>
31
// Copyright (C) 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
32
// Copyright (C) 2026 Kleis Auke Wolthuizen <github@kleisauke.nl>
33
//
34
// To see a description of the changes please see the Changelog file that
35
// came with your tarball or type make ChangeLog if you are building from git
36
//
37
//========================================================================
38
39
#include <config.h>
40
41
#ifndef _WIN32
42
#    include <sys/types.h>
43
#    include <sys/stat.h>
44
#    include <fcntl.h>
45
#    include <cstring>
46
#    include <pwd.h>
47
#endif // _WIN32
48
#include <cstdio>
49
#include <limits>
50
#include "gfile.h"
51
52
#ifndef _WIN32
53
54
using namespace std::string_literals;
55
56
namespace {
57
58
template<class T>
59
inline constexpr bool has_st_mtim = requires(const T &s) { s.st_mtim; };
60
61
template<class T>
62
inline constexpr bool has_st_mtimespec = requires(const T &s) { s.st_mtimespec; };
63
64
template<typename Stat>
65
const struct timespec &mtim(const Stat &stbuf)
66
7.13k
{
67
7.13k
    static_assert(has_st_mtim<Stat> || has_st_mtimespec<Stat>, "stat must contain st_mtim or st_mtimespec");
68
69
7.13k
    if constexpr (has_st_mtim<Stat>) {
70
7.13k
        return stbuf.st_mtim;
71
    } else {
72
        // Mac OS X uses a different field name than POSIX and this makes it work
73
        return stbuf.st_mtimespec;
74
    }
75
7.13k
}
76
77
}
78
79
#endif
80
81
//------------------------------------------------------------------------
82
83
#ifndef _WIN32
84
85
static bool makeFileDescriptorCloexec(int fd)
86
0
{
87
0
#    ifdef FD_CLOEXEC
88
0
    int flags = fcntl(fd, F_GETFD);
89
0
    if (flags >= 0 && !(flags & FD_CLOEXEC)) {
90
0
        flags = fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
91
0
    }
92
93
0
    return flags >= 0;
94
#    else
95
    return true;
96
#    endif
97
0
}
98
99
static int openFileDescriptor(const char *path, int flags)
100
7.13k
{
101
7.13k
#    ifdef O_CLOEXEC
102
7.13k
    return open(path, flags | O_CLOEXEC);
103
#    else
104
    int fd = open(path, flags);
105
    if (fd == -1)
106
        return fd;
107
108
    if (!makeFileDescriptorCloexec(fd)) {
109
        close(fd);
110
        return -1;
111
    }
112
113
    return fd;
114
#    endif
115
7.13k
}
116
117
#endif
118
119
FILE *openFile(const char *path, const char *mode)
120
0
{
121
#ifdef _WIN32
122
    OSVERSIONINFO version;
123
    wchar_t wPath[MAX_PATH + 1];
124
    char nPath[MAX_PATH + 1];
125
    wchar_t wMode[8];
126
    const char *p;
127
    size_t i;
128
129
    // NB: _wfopen is only available in NT
130
    version.dwOSVersionInfoSize = sizeof(version);
131
    GetVersionEx(&version);
132
    if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
133
        for (p = path, i = 0; *p && i < MAX_PATH; ++i) {
134
            if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) {
135
                wPath[i] = (wchar_t)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f));
136
                p += 2;
137
            } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) {
138
                wPath[i] = (wchar_t)(((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f));
139
                p += 3;
140
            } else {
141
                wPath[i] = (wchar_t)(p[0] & 0xff);
142
                p += 1;
143
            }
144
        }
145
        wPath[i] = (wchar_t)0;
146
        for (i = 0; (i < sizeof(mode) - 1) && mode[i]; ++i) {
147
            wMode[i] = (wchar_t)(mode[i] & 0xff);
148
        }
149
        wMode[i] = (wchar_t)0;
150
        return _wfopen(wPath, wMode);
151
    } else {
152
        for (p = path, i = 0; *p && i < MAX_PATH; ++i) {
153
            if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) {
154
                nPath[i] = (char)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f));
155
                p += 2;
156
            } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) {
157
                nPath[i] = (char)(((p[1] & 0x3f) << 6) | (p[2] & 0x3f));
158
                p += 3;
159
            } else {
160
                nPath[i] = p[0];
161
                p += 1;
162
            }
163
        }
164
        nPath[i] = '\0';
165
        return fopen(nPath, mode);
166
    }
167
#else
168
    // First try to atomically open the file with CLOEXEC
169
0
    const std::string modeStr = mode + "e"s;
170
0
    FILE *file = fopen(path, modeStr.c_str());
171
0
    if (file != nullptr) {
172
0
        return file;
173
0
    }
174
175
    // Fall back to the provided mode and apply CLOEXEC afterwards
176
0
    file = fopen(path, mode);
177
0
    if (file == nullptr) {
178
0
        return nullptr;
179
0
    }
180
181
0
    if (!makeFileDescriptorCloexec(fileno(file))) {
182
0
        fclose(file);
183
0
        return nullptr;
184
0
    }
185
186
0
    return file;
187
0
#endif
188
0
}
189
190
char *getLine(char *buf, int size, FILE *f)
191
0
{
192
0
    int c, i;
193
194
0
    i = 0;
195
0
    while (i < size - 1) {
196
0
        if ((c = fgetc(f)) == EOF) {
197
0
            break;
198
0
        }
199
0
        buf[i++] = static_cast<char>(c);
200
0
        if (c == '\x0a') {
201
0
            break;
202
0
        }
203
0
        if (c == '\x0d') {
204
0
            c = fgetc(f);
205
0
            if (c == '\x0a' && i < size - 1) {
206
0
                buf[i++] = static_cast<char>(c);
207
0
            } else if (c != EOF) {
208
0
                ungetc(c, f);
209
0
            }
210
0
            break;
211
0
        }
212
0
    }
213
0
    buf[i] = '\0';
214
0
    if (i == 0) {
215
0
        return nullptr;
216
0
    }
217
0
    return buf;
218
0
}
219
220
int Gfseek(FILE *f, Goffset offset, int whence)
221
0
{
222
0
#if HAVE_FSEEKO
223
0
    return fseeko(f, offset, whence);
224
#elif HAVE_FSEEK64
225
    return fseek64(f, offset, whence);
226
#elif defined(__MINGW32__)
227
    return fseeko64(f, offset, whence);
228
#elif defined(_WIN32)
229
    return _fseeki64(f, offset, whence);
230
#else
231
    return fseek(f, offset, whence);
232
#endif
233
0
}
234
235
Goffset Gftell(FILE *f)
236
0
{
237
0
#if HAVE_FSEEKO
238
0
    return ftello(f);
239
#elif HAVE_FSEEK64
240
    return ftell64(f);
241
#elif defined(__MINGW32__)
242
    return ftello64(f);
243
#elif defined(_WIN32)
244
    return _ftelli64(f);
245
#else
246
    return ftell(f);
247
#endif
248
0
}
249
250
Goffset GoffsetMax()
251
0
{
252
0
#if HAVE_FSEEKO
253
0
    return (std::numeric_limits<off_t>::max)();
254
#elif HAVE_FSEEK64 || defined(__MINGW32__)
255
    return (std::numeric_limits<off64_t>::max)();
256
#elif defined(_WIN32)
257
    return (std::numeric_limits<__int64>::max)();
258
#else
259
    return (std::numeric_limits<long>::max)();
260
#endif
261
0
}
262
263
//------------------------------------------------------------------------
264
// GooFile
265
//------------------------------------------------------------------------
266
267
#ifdef _WIN32
268
269
GooFile::GooFile(HANDLE handleA) : handle(handleA)
270
{
271
    GetFileTime(handleA, nullptr, nullptr, &modifiedTimeOnOpen);
272
}
273
274
int GooFile::read(char *buf, int n, Goffset offset) const
275
{
276
    DWORD m;
277
278
    LARGE_INTEGER largeInteger = {};
279
    largeInteger.QuadPart = offset;
280
281
    OVERLAPPED overlapped = {};
282
    overlapped.Offset = largeInteger.LowPart;
283
    overlapped.OffsetHigh = largeInteger.HighPart;
284
285
    return FALSE == ReadFile(handle, buf, n, &m, &overlapped) ? -1 : m;
286
}
287
288
Goffset GooFile::size() const
289
{
290
    LARGE_INTEGER size = { (DWORD)-1, -1 };
291
292
    GetFileSizeEx(handle, &size);
293
294
    return size.QuadPart;
295
}
296
297
std::unique_ptr<GooFile> GooFile::open(const std::string &fileName)
298
{
299
    HANDLE handle = CreateFileA(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
300
301
    return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle));
302
}
303
304
std::unique_ptr<GooFile> GooFile::open(const wchar_t *fileName)
305
{
306
    HANDLE handle = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
307
308
    return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle));
309
}
310
311
bool GooFile::modificationTimeChangedSinceOpen() const
312
{
313
    struct _FILETIME lastModified;
314
    GetFileTime(handle, nullptr, nullptr, &lastModified);
315
316
    return modifiedTimeOnOpen.dwHighDateTime != lastModified.dwHighDateTime || modifiedTimeOnOpen.dwLowDateTime != lastModified.dwLowDateTime;
317
}
318
319
#else
320
321
int GooFile::read(char *buf, int n, Goffset offset) const
322
441k
{
323
441k
#    if HAVE_PREAD64
324
441k
    return pread64(fd, buf, n, offset);
325
#    else
326
    return pread(fd, buf, n, offset);
327
#    endif
328
441k
}
329
330
Goffset GooFile::size() const
331
32.2k
{
332
32.2k
#    if HAVE_LSEEK64
333
32.2k
    return lseek64(fd, 0, SEEK_END);
334
#    else
335
    return lseek(fd, 0, SEEK_END);
336
#    endif
337
32.2k
}
338
339
std::unique_ptr<GooFile> GooFile::open(const std::string &fileName)
340
7.13k
{
341
7.13k
    int fd = openFileDescriptor(fileName.c_str(), O_RDONLY);
342
343
7.13k
    return GooFile::open(fd);
344
7.13k
}
345
346
std::unique_ptr<GooFile> GooFile::open(int fdA)
347
7.13k
{
348
7.13k
    return fdA < 0 ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(fdA));
349
7.13k
}
350
351
7.13k
GooFile::GooFile(int fdA) : fd(fdA)
352
7.13k
{
353
7.13k
    struct stat statbuf;
354
7.13k
    fstat(fd, &statbuf);
355
7.13k
    modifiedTimeOnOpen = mtim(statbuf);
356
7.13k
}
357
358
bool GooFile::modificationTimeChangedSinceOpen() const
359
0
{
360
0
    struct stat statbuf;
361
0
    fstat(fd, &statbuf);
362
363
0
    return modifiedTimeOnOpen.tv_sec != mtim(statbuf).tv_sec || modifiedTimeOnOpen.tv_nsec != mtim(statbuf).tv_nsec;
364
0
}
365
366
#endif // _WIN32