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 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//
32// To see a description of the changes please see the Changelog file that
33// came with your tarball or type make ChangeLog if you are building from git
34//
35//========================================================================
36
37#include <config.h>
38
39#ifndef _WIN32
40# include <sys/types.h>
41# include <sys/stat.h>
42# include <fcntl.h>
43# include <climits>
44# include <cstring>
45# include <pwd.h>
46#endif // _WIN32
47#include <cstdio>
48#include <limits>
49#include "GooString.h"
50#include "gfile.h"
51#include "gdir.h"
52
53// Some systems don't define this, so just make it something reasonably
54// large.
55#ifndef PATH_MAX
56# define PATH_MAX 1024
57#endif
58
59#ifndef _WIN32
60
61using namespace std::string_literals;
62
63namespace {
64
65template<typename...>
66struct void_type
67{
68 using type = void;
69};
70
71template<typename... Args>
72using void_t = typename void_type<Args...>::type;
73
74template<typename Stat, typename = void_t<>>
75struct StatMtim
76{
77 static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtim; }
78};
79
80// Mac OS X uses a different field name than POSIX and this detects it.
81template<typename Stat>
82struct StatMtim<Stat, void_t<decltype(Stat::st_mtimespec)>>
83{
84 static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtimespec; }
85};
86
87inline const struct timespec &mtim(const struct stat &stbuf)
88{
89 return StatMtim<struct stat>::value(stbuf);
90}
91
92}
93
94#endif
95
96//------------------------------------------------------------------------
97
98GooString *appendToPath(GooString *path, const char *fileName)
99{
100#ifdef _WIN32
101 //---------- Win32 ----------
102 GooString *tmp;
103 char buf[256];
104 char *fp;
105
106 tmp = new GooString(path);
107 tmp->append('/');
108 tmp->append(fileName);
109 GetFullPathNameA(tmp->c_str(), sizeof(buf), buf, &fp);
110 delete tmp;
111 path->clear();
112 path->append(buf);
113 return path;
114
115#else
116 //---------- Unix ----------
117 int i;
118
119 // appending "." does nothing
120 if (!strcmp(s1: fileName, s2: ".")) {
121 return path;
122 }
123
124 // appending ".." goes up one directory
125 if (!strcmp(s1: fileName, s2: "..")) {
126 for (i = path->getLength() - 2; i >= 0; --i) {
127 if (path->getChar(i) == '/') {
128 break;
129 }
130 }
131 if (i <= 0) {
132 if (path->getChar(i: 0) == '/') {
133 path->del(i: 1, n: path->getLength() - 1);
134 } else {
135 path->clear();
136 path->append(str: "..");
137 }
138 } else {
139 path->del(i, n: path->getLength() - i);
140 }
141 return path;
142 }
143
144 // otherwise, append "/" and new path component
145 if (path->getLength() > 0 && path->getChar(i: path->getLength() - 1) != '/') {
146 path->append(c: '/');
147 }
148 path->append(str: fileName);
149 return path;
150#endif
151}
152
153#ifndef _WIN32
154
155static bool makeFileDescriptorCloexec(int fd)
156{
157# ifdef FD_CLOEXEC
158 int flags = fcntl(fd: fd, F_GETFD);
159 if (flags >= 0 && !(flags & FD_CLOEXEC)) {
160 flags = fcntl(fd: fd, F_SETFD, flags | FD_CLOEXEC);
161 }
162
163 return flags >= 0;
164# else
165 return true;
166# endif
167}
168
169int openFileDescriptor(const char *path, int flags)
170{
171# ifdef O_CLOEXEC
172 return open(file: path, oflag: flags | O_CLOEXEC);
173# else
174 int fd = open(path, flags);
175 if (fd == -1)
176 return fd;
177
178 if (!makeFileDescriptorCloexec(fd)) {
179 close(fd);
180 return -1;
181 }
182
183 return fd;
184# endif
185}
186
187#endif
188
189FILE *openFile(const char *path, const char *mode)
190{
191#ifdef _WIN32
192 OSVERSIONINFO version;
193 wchar_t wPath[_MAX_PATH + 1];
194 char nPath[_MAX_PATH + 1];
195 wchar_t wMode[8];
196 const char *p;
197 size_t i;
198
199 // NB: _wfopen is only available in NT
200 version.dwOSVersionInfoSize = sizeof(version);
201 GetVersionEx(&version);
202 if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
203 for (p = path, i = 0; *p && i < _MAX_PATH; ++i) {
204 if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) {
205 wPath[i] = (wchar_t)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f));
206 p += 2;
207 } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) {
208 wPath[i] = (wchar_t)(((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f));
209 p += 3;
210 } else {
211 wPath[i] = (wchar_t)(p[0] & 0xff);
212 p += 1;
213 }
214 }
215 wPath[i] = (wchar_t)0;
216 for (i = 0; (i < sizeof(mode) - 1) && mode[i]; ++i) {
217 wMode[i] = (wchar_t)(mode[i] & 0xff);
218 }
219 wMode[i] = (wchar_t)0;
220 return _wfopen(wPath, wMode);
221 } else {
222 for (p = path, i = 0; *p && i < _MAX_PATH; ++i) {
223 if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) {
224 nPath[i] = (char)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f));
225 p += 2;
226 } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) {
227 nPath[i] = (char)(((p[1] & 0x3f) << 6) | (p[2] & 0x3f));
228 p += 3;
229 } else {
230 nPath[i] = p[0];
231 p += 1;
232 }
233 }
234 nPath[i] = '\0';
235 return fopen(nPath, mode);
236 }
237#else
238 // First try to atomically open the file with CLOEXEC
239 const std::string modeStr = mode + "e"s;
240 FILE *file = fopen(filename: path, modes: modeStr.c_str());
241 if (file != nullptr) {
242 return file;
243 }
244
245 // Fall back to the provided mode and apply CLOEXEC afterwards
246 file = fopen(filename: path, modes: mode);
247 if (file == nullptr) {
248 return nullptr;
249 }
250
251 if (!makeFileDescriptorCloexec(fd: fileno(stream: file))) {
252 fclose(stream: file);
253 return nullptr;
254 }
255
256 return file;
257#endif
258}
259
260char *getLine(char *buf, int size, FILE *f)
261{
262 int c, i;
263
264 i = 0;
265 while (i < size - 1) {
266 if ((c = fgetc(stream: f)) == EOF) {
267 break;
268 }
269 buf[i++] = (char)c;
270 if (c == '\x0a') {
271 break;
272 }
273 if (c == '\x0d') {
274 c = fgetc(stream: f);
275 if (c == '\x0a' && i < size - 1) {
276 buf[i++] = (char)c;
277 } else if (c != EOF) {
278 ungetc(c: c, stream: f);
279 }
280 break;
281 }
282 }
283 buf[i] = '\0';
284 if (i == 0) {
285 return nullptr;
286 }
287 return buf;
288}
289
290int Gfseek(FILE *f, Goffset offset, int whence)
291{
292#if defined(HAVE_FSEEKO)
293 return fseeko(stream: f, off: offset, whence: whence);
294#elif defined(HAVE_FSEEK64)
295 return fseek64(f, offset, whence);
296#elif defined(__MINGW32__)
297 return fseeko64(f, offset, whence);
298#elif defined(_WIN32)
299 return _fseeki64(f, offset, whence);
300#else
301 return fseek(f, offset, whence);
302#endif
303}
304
305Goffset Gftell(FILE *f)
306{
307#if defined(HAVE_FSEEKO)
308 return ftello(stream: f);
309#elif defined(HAVE_FSEEK64)
310 return ftell64(f);
311#elif defined(__MINGW32__)
312 return ftello64(f);
313#elif defined(_WIN32)
314 return _ftelli64(f);
315#else
316 return ftell(f);
317#endif
318}
319
320Goffset GoffsetMax()
321{
322#if defined(HAVE_FSEEKO)
323 return (std::numeric_limits<off_t>::max)();
324#elif defined(HAVE_FSEEK64) || defined(__MINGW32__)
325 return (std::numeric_limits<off64_t>::max)();
326#elif defined(_WIN32)
327 return (std::numeric_limits<__int64>::max)();
328#else
329 return (std::numeric_limits<long>::max)();
330#endif
331}
332
333//------------------------------------------------------------------------
334// GooFile
335//------------------------------------------------------------------------
336
337#ifdef _WIN32
338
339GooFile::GooFile(HANDLE handleA) : handle(handleA)
340{
341 GetFileTime(handleA, nullptr, nullptr, &modifiedTimeOnOpen);
342}
343
344int GooFile::read(char *buf, int n, Goffset offset) const
345{
346 DWORD m;
347
348 LARGE_INTEGER largeInteger = {};
349 largeInteger.QuadPart = offset;
350
351 OVERLAPPED overlapped = {};
352 overlapped.Offset = largeInteger.LowPart;
353 overlapped.OffsetHigh = largeInteger.HighPart;
354
355 return FALSE == ReadFile(handle, buf, n, &m, &overlapped) ? -1 : m;
356}
357
358Goffset GooFile::size() const
359{
360 LARGE_INTEGER size = { (DWORD)-1, -1 };
361
362 GetFileSizeEx(handle, &size);
363
364 return size.QuadPart;
365}
366
367std::unique_ptr<GooFile> GooFile::open(const std::string &fileName)
368{
369 HANDLE handle = CreateFileA(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
370
371 return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle));
372}
373
374std::unique_ptr<GooFile> GooFile::open(const wchar_t *fileName)
375{
376 HANDLE handle = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
377
378 return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle));
379}
380
381bool GooFile::modificationTimeChangedSinceOpen() const
382{
383 struct _FILETIME lastModified;
384 GetFileTime(handle, nullptr, nullptr, &lastModified);
385
386 return modifiedTimeOnOpen.dwHighDateTime != lastModified.dwHighDateTime || modifiedTimeOnOpen.dwLowDateTime != lastModified.dwLowDateTime;
387}
388
389#else
390
391int GooFile::read(char *buf, int n, Goffset offset) const
392{
393# ifdef HAVE_PREAD64
394 return pread64(fd: fd, buf: buf, nbytes: n, offset: offset);
395# else
396 return pread(fd, buf, n, offset);
397# endif
398}
399
400Goffset GooFile::size() const
401{
402# ifdef HAVE_LSEEK64
403 return lseek64(fd: fd, offset: 0, SEEK_END);
404# else
405 return lseek(fd, 0, SEEK_END);
406# endif
407}
408
409std::unique_ptr<GooFile> GooFile::open(const std::string &fileName)
410{
411 int fd = openFileDescriptor(path: fileName.c_str(), O_RDONLY);
412
413 return GooFile::open(fdA: fd);
414}
415
416std::unique_ptr<GooFile> GooFile::open(int fdA)
417{
418 return fdA < 0 ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(fdA));
419}
420
421GooFile::GooFile(int fdA) : fd(fdA)
422{
423 struct stat statbuf;
424 fstat(fd: fd, buf: &statbuf);
425 modifiedTimeOnOpen = mtim(stbuf: statbuf);
426}
427
428bool GooFile::modificationTimeChangedSinceOpen() const
429{
430 struct stat statbuf;
431 fstat(fd: fd, buf: &statbuf);
432
433 return modifiedTimeOnOpen.tv_sec != mtim(stbuf: statbuf).tv_sec || modifiedTimeOnOpen.tv_nsec != mtim(stbuf: statbuf).tv_nsec;
434}
435
436#endif // _WIN32
437
438//------------------------------------------------------------------------
439// GDir and GDirEntry
440//------------------------------------------------------------------------
441
442GDirEntry::GDirEntry(const char *dirPath, const char *nameA, bool doStat)
443{
444#ifdef _WIN32
445 DWORD fa;
446#else
447 struct stat st;
448#endif
449
450 name = new GooString(nameA);
451 dir = false;
452 fullPath = new GooString(dirPath);
453 appendToPath(path: fullPath, fileName: nameA);
454 if (doStat) {
455#ifdef _WIN32
456 fa = GetFileAttributesA(fullPath->c_str());
457 dir = (fa != 0xFFFFFFFF && (fa & FILE_ATTRIBUTE_DIRECTORY));
458#else
459 if (stat(file: fullPath->c_str(), buf: &st) == 0) {
460 dir = S_ISDIR(st.st_mode);
461 }
462#endif
463 }
464}
465
466GDirEntry::~GDirEntry()
467{
468 delete fullPath;
469 delete name;
470}
471
472GDir::GDir(const char *name, bool doStatA)
473{
474 path = new GooString(name);
475 doStat = doStatA;
476#ifdef _WIN32
477 GooString *tmp;
478
479 tmp = path->copy();
480 tmp->append("/*.*");
481 hnd = FindFirstFileA(tmp->c_str(), &ffd);
482 delete tmp;
483#else
484 dir = opendir(name: name);
485#endif
486}
487
488GDir::~GDir()
489{
490 delete path;
491#ifdef _WIN32
492 if (hnd != INVALID_HANDLE_VALUE) {
493 FindClose(hnd);
494 hnd = INVALID_HANDLE_VALUE;
495 }
496#else
497 if (dir) {
498 closedir(dirp: dir);
499 }
500#endif
501}
502
503std::unique_ptr<GDirEntry> GDir::getNextEntry()
504{
505#ifdef _WIN32
506 if (hnd != INVALID_HANDLE_VALUE) {
507 auto e = std::make_unique<GDirEntry>(path->c_str(), ffd.cFileName, doStat);
508 if (!FindNextFileA(hnd, &ffd)) {
509 FindClose(hnd);
510 hnd = INVALID_HANDLE_VALUE;
511 }
512 return e;
513 }
514#else
515 struct dirent *ent;
516 if (dir) {
517 do {
518 ent = readdir(dirp: dir);
519 } while (ent && (!strcmp(s1: ent->d_name, s2: ".") || !strcmp(s1: ent->d_name, s2: "..")));
520 if (ent) {
521 return std::make_unique<GDirEntry>(args: path->c_str(), args&: ent->d_name, args&: doStat);
522 }
523 }
524#endif
525
526 return {};
527}
528
529void GDir::rewind()
530{
531#ifdef _WIN32
532 GooString *tmp;
533
534 if (hnd != INVALID_HANDLE_VALUE)
535 FindClose(hnd);
536 tmp = path->copy();
537 tmp->append("/*.*");
538 hnd = FindFirstFileA(tmp->c_str(), &ffd);
539 delete tmp;
540#else
541 if (dir) {
542 rewinddir(dirp: dir);
543 }
544#endif
545}
546

source code of poppler/goo/gfile.cc