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 | |
61 | using namespace std::string_literals; |
62 | |
63 | namespace { |
64 | |
65 | template<typename...> |
66 | struct void_type |
67 | { |
68 | using type = void; |
69 | }; |
70 | |
71 | template<typename... Args> |
72 | using void_t = typename void_type<Args...>::type; |
73 | |
74 | template<typename Stat, typename = void_t<>> |
75 | struct 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. |
81 | template<typename Stat> |
82 | struct StatMtim<Stat, void_t<decltype(Stat::st_mtimespec)>> |
83 | { |
84 | static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtimespec; } |
85 | }; |
86 | |
87 | inline 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 | |
98 | GooString *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 | |
155 | static 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 | |
169 | int 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 | |
189 | FILE *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 | |
260 | char *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 | |
290 | int 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 | |
305 | Goffset 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 | |
320 | Goffset 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 | |
339 | GooFile::GooFile(HANDLE handleA) : handle(handleA) |
340 | { |
341 | GetFileTime(handleA, nullptr, nullptr, &modifiedTimeOnOpen); |
342 | } |
343 | |
344 | int 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 | |
358 | Goffset GooFile::size() const |
359 | { |
360 | LARGE_INTEGER size = { (DWORD)-1, -1 }; |
361 | |
362 | GetFileSizeEx(handle, &size); |
363 | |
364 | return size.QuadPart; |
365 | } |
366 | |
367 | std::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 | |
374 | std::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 | |
381 | bool 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 | |
391 | int 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 | |
400 | Goffset 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 | |
409 | std::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 | |
416 | std::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 | |
421 | GooFile::GooFile(int fdA) : fd(fdA) |
422 | { |
423 | struct stat statbuf; |
424 | fstat(fd: fd, buf: &statbuf); |
425 | modifiedTimeOnOpen = mtim(stbuf: statbuf); |
426 | } |
427 | |
428 | bool 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 | |
442 | GDirEntry::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 | |
466 | GDirEntry::~GDirEntry() |
467 | { |
468 | delete fullPath; |
469 | delete name; |
470 | } |
471 | |
472 | GDir::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 | |
488 | GDir::~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 | |
503 | std::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 | |
529 | void 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 | |