| //======================================================================== |
| // |
| // gfile.cc |
| // |
| // Miscellaneous file and directory name manipulation. |
| // |
| // Copyright 1996-2003 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2006 Takashi Iwai <tiwai@suse.de> |
| // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com> |
| // Copyright (C) 2008 Adam Batkin <adam@batkin.net> |
| // Copyright (C) 2008, 2010, 2012, 2013 Hib Eris <hib@hiberis.nl> |
| // Copyright (C) 2009, 2012, 2014, 2017, 2018, 2021, 2022, 2024 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net> |
| // Copyright (C) 2013, 2018 Adam Reichold <adamreichold@myopera.com> |
| // Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com> |
| // Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de> |
| // Copyright (C) 2013, 2017 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2017 Christoph Cullmann <cullmann@kde.org> |
| // Copyright (C) 2018 Mojca Miklavec <mojca@macports.org> |
| // Copyright (C) 2019, 2021 Christian Persch <chpe@src.gnome.org> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #ifndef _WIN32 |
| # include <sys/types.h> |
| # include <sys/stat.h> |
| # include <fcntl.h> |
| # include <climits> |
| # include <cstring> |
| # include <pwd.h> |
| #endif // _WIN32 |
| #include <cstdio> |
| #include <limits> |
| #include "GooString.h" |
| #include "gfile.h" |
| #include "gdir.h" |
| |
| // Some systems don't define this, so just make it something reasonably |
| // large. |
| #ifndef PATH_MAX |
| # define PATH_MAX 1024 |
| #endif |
| |
| #ifndef _WIN32 |
| |
| using namespace std::string_literals; |
| |
| namespace { |
| |
| template<typename...> |
| struct void_type |
| { |
| using type = void; |
| }; |
| |
| template<typename... Args> |
| using void_t = typename void_type<Args...>::type; |
| |
| template<typename Stat, typename = void_t<>> |
| struct StatMtim |
| { |
| static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtim; } |
| }; |
| |
| // Mac OS X uses a different field name than POSIX and this detects it. |
| template<typename Stat> |
| struct StatMtim<Stat, void_t<decltype(Stat::st_mtimespec)>> |
| { |
| static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtimespec; } |
| }; |
| |
| inline const struct timespec &mtim(const struct stat &stbuf) |
| { |
| return StatMtim<struct stat>::value(stbuf); |
| } |
| |
| } |
| |
| #endif |
| |
| //------------------------------------------------------------------------ |
| |
| GooString *appendToPath(GooString *path, const char *fileName) |
| { |
| #ifdef _WIN32 |
| //---------- Win32 ---------- |
| GooString *tmp; |
| char buf[256]; |
| char *fp; |
| |
| tmp = new GooString(path); |
| tmp->append('/'); |
| tmp->append(fileName); |
| GetFullPathNameA(tmp->c_str(), sizeof(buf), buf, &fp); |
| delete tmp; |
| path->clear(); |
| path->append(buf); |
| return path; |
| |
| #else |
| //---------- Unix ---------- |
| int i; |
| |
| // appending "." does nothing |
| if (!strcmp(fileName, ".")) { |
| return path; |
| } |
| |
| // appending ".." goes up one directory |
| if (!strcmp(fileName, "..")) { |
| for (i = path->getLength() - 2; i >= 0; --i) { |
| if (path->getChar(i) == '/') { |
| break; |
| } |
| } |
| if (i <= 0) { |
| if (path->getChar(0) == '/') { |
| path->del(1, path->getLength() - 1); |
| } else { |
| path->clear(); |
| path->append(".."); |
| } |
| } else { |
| path->del(i, path->getLength() - i); |
| } |
| return path; |
| } |
| |
| // otherwise, append "/" and new path component |
| if (path->getLength() > 0 && path->getChar(path->getLength() - 1) != '/') { |
| path->append('/'); |
| } |
| path->append(fileName); |
| return path; |
| #endif |
| } |
| |
| #ifndef _WIN32 |
| |
| static bool makeFileDescriptorCloexec(int fd) |
| { |
| # ifdef FD_CLOEXEC |
| int flags = fcntl(fd, F_GETFD); |
| if (flags >= 0 && !(flags & FD_CLOEXEC)) { |
| flags = fcntl(fd, F_SETFD, flags | FD_CLOEXEC); |
| } |
| |
| return flags >= 0; |
| # else |
| return true; |
| # endif |
| } |
| |
| int openFileDescriptor(const char *path, int flags) |
| { |
| # ifdef O_CLOEXEC |
| return open(path, flags | O_CLOEXEC); |
| # else |
| int fd = open(path, flags); |
| if (fd == -1) |
| return fd; |
| |
| if (!makeFileDescriptorCloexec(fd)) { |
| close(fd); |
| return -1; |
| } |
| |
| return fd; |
| # endif |
| } |
| |
| #endif |
| |
| FILE *openFile(const char *path, const char *mode) |
| { |
| #ifdef _WIN32 |
| OSVERSIONINFO version; |
| wchar_t wPath[_MAX_PATH + 1]; |
| char nPath[_MAX_PATH + 1]; |
| wchar_t wMode[8]; |
| const char *p; |
| size_t i; |
| |
| // NB: _wfopen is only available in NT |
| version.dwOSVersionInfoSize = sizeof(version); |
| GetVersionEx(&version); |
| if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) { |
| for (p = path, i = 0; *p && i < _MAX_PATH; ++i) { |
| if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) { |
| wPath[i] = (wchar_t)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f)); |
| p += 2; |
| } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) { |
| wPath[i] = (wchar_t)(((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f)); |
| p += 3; |
| } else { |
| wPath[i] = (wchar_t)(p[0] & 0xff); |
| p += 1; |
| } |
| } |
| wPath[i] = (wchar_t)0; |
| for (i = 0; (i < sizeof(mode) - 1) && mode[i]; ++i) { |
| wMode[i] = (wchar_t)(mode[i] & 0xff); |
| } |
| wMode[i] = (wchar_t)0; |
| return _wfopen(wPath, wMode); |
| } else { |
| for (p = path, i = 0; *p && i < _MAX_PATH; ++i) { |
| if ((p[0] & 0xe0) == 0xc0 && p[1] && (p[1] & 0xc0) == 0x80) { |
| nPath[i] = (char)(((p[0] & 0x1f) << 6) | (p[1] & 0x3f)); |
| p += 2; |
| } else if ((p[0] & 0xf0) == 0xe0 && p[1] && (p[1] & 0xc0) == 0x80 && p[2] && (p[2] & 0xc0) == 0x80) { |
| nPath[i] = (char)(((p[1] & 0x3f) << 6) | (p[2] & 0x3f)); |
| p += 3; |
| } else { |
| nPath[i] = p[0]; |
| p += 1; |
| } |
| } |
| nPath[i] = '\0'; |
| return fopen(nPath, mode); |
| } |
| #else |
| // First try to atomically open the file with CLOEXEC |
| const std::string modeStr = mode + "e"s; |
| FILE *file = fopen(path, modeStr.c_str()); |
| if (file != nullptr) { |
| return file; |
| } |
| |
| // Fall back to the provided mode and apply CLOEXEC afterwards |
| file = fopen(path, mode); |
| if (file == nullptr) { |
| return nullptr; |
| } |
| |
| if (!makeFileDescriptorCloexec(fileno(file))) { |
| fclose(file); |
| return nullptr; |
| } |
| |
| return file; |
| #endif |
| } |
| |
| char *getLine(char *buf, int size, FILE *f) |
| { |
| int c, i; |
| |
| i = 0; |
| while (i < size - 1) { |
| if ((c = fgetc(f)) == EOF) { |
| break; |
| } |
| buf[i++] = (char)c; |
| if (c == '\x0a') { |
| break; |
| } |
| if (c == '\x0d') { |
| c = fgetc(f); |
| if (c == '\x0a' && i < size - 1) { |
| buf[i++] = (char)c; |
| } else if (c != EOF) { |
| ungetc(c, f); |
| } |
| break; |
| } |
| } |
| buf[i] = '\0'; |
| if (i == 0) { |
| return nullptr; |
| } |
| return buf; |
| } |
| |
| int Gfseek(FILE *f, Goffset offset, int whence) |
| { |
| #if defined(HAVE_FSEEKO) |
| return fseeko(f, offset, whence); |
| #elif defined(HAVE_FSEEK64) |
| return fseek64(f, offset, whence); |
| #elif defined(__MINGW32__) |
| return fseeko64(f, offset, whence); |
| #elif defined(_WIN32) |
| return _fseeki64(f, offset, whence); |
| #else |
| return fseek(f, offset, whence); |
| #endif |
| } |
| |
| Goffset Gftell(FILE *f) |
| { |
| #if defined(HAVE_FSEEKO) |
| return ftello(f); |
| #elif defined(HAVE_FSEEK64) |
| return ftell64(f); |
| #elif defined(__MINGW32__) |
| return ftello64(f); |
| #elif defined(_WIN32) |
| return _ftelli64(f); |
| #else |
| return ftell(f); |
| #endif |
| } |
| |
| Goffset GoffsetMax() |
| { |
| #if defined(HAVE_FSEEKO) |
| return (std::numeric_limits<off_t>::max)(); |
| #elif defined(HAVE_FSEEK64) || defined(__MINGW32__) |
| return (std::numeric_limits<off64_t>::max)(); |
| #elif defined(_WIN32) |
| return (std::numeric_limits<__int64>::max)(); |
| #else |
| return (std::numeric_limits<long>::max)(); |
| #endif |
| } |
| |
| //------------------------------------------------------------------------ |
| // GooFile |
| //------------------------------------------------------------------------ |
| |
| #ifdef _WIN32 |
| |
| GooFile::GooFile(HANDLE handleA) : handle(handleA) |
| { |
| GetFileTime(handleA, nullptr, nullptr, &modifiedTimeOnOpen); |
| } |
| |
| int GooFile::read(char *buf, int n, Goffset offset) const |
| { |
| DWORD m; |
| |
| LARGE_INTEGER largeInteger = {}; |
| largeInteger.QuadPart = offset; |
| |
| OVERLAPPED overlapped = {}; |
| overlapped.Offset = largeInteger.LowPart; |
| overlapped.OffsetHigh = largeInteger.HighPart; |
| |
| return FALSE == ReadFile(handle, buf, n, &m, &overlapped) ? -1 : m; |
| } |
| |
| Goffset GooFile::size() const |
| { |
| LARGE_INTEGER size = { (DWORD)-1, -1 }; |
| |
| GetFileSizeEx(handle, &size); |
| |
| return size.QuadPart; |
| } |
| |
| std::unique_ptr<GooFile> GooFile::open(const std::string &fileName) |
| { |
| HANDLE handle = CreateFileA(fileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); |
| |
| return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle)); |
| } |
| |
| std::unique_ptr<GooFile> GooFile::open(const wchar_t *fileName) |
| { |
| HANDLE handle = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); |
| |
| return handle == INVALID_HANDLE_VALUE ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(handle)); |
| } |
| |
| bool GooFile::modificationTimeChangedSinceOpen() const |
| { |
| struct _FILETIME lastModified; |
| GetFileTime(handle, nullptr, nullptr, &lastModified); |
| |
| return modifiedTimeOnOpen.dwHighDateTime != lastModified.dwHighDateTime || modifiedTimeOnOpen.dwLowDateTime != lastModified.dwLowDateTime; |
| } |
| |
| #else |
| |
| int GooFile::read(char *buf, int n, Goffset offset) const |
| { |
| # ifdef HAVE_PREAD64 |
| return pread64(fd, buf, n, offset); |
| # else |
| return pread(fd, buf, n, offset); |
| # endif |
| } |
| |
| Goffset GooFile::size() const |
| { |
| # ifdef HAVE_LSEEK64 |
| return lseek64(fd, 0, SEEK_END); |
| # else |
| return lseek(fd, 0, SEEK_END); |
| # endif |
| } |
| |
| std::unique_ptr<GooFile> GooFile::open(const std::string &fileName) |
| { |
| int fd = openFileDescriptor(fileName.c_str(), O_RDONLY); |
| |
| return GooFile::open(fd); |
| } |
| |
| std::unique_ptr<GooFile> GooFile::open(int fdA) |
| { |
| return fdA < 0 ? std::unique_ptr<GooFile>() : std::unique_ptr<GooFile>(new GooFile(fdA)); |
| } |
| |
| GooFile::GooFile(int fdA) : fd(fdA) |
| { |
| struct stat statbuf; |
| fstat(fd, &statbuf); |
| modifiedTimeOnOpen = mtim(statbuf); |
| } |
| |
| bool GooFile::modificationTimeChangedSinceOpen() const |
| { |
| struct stat statbuf; |
| fstat(fd, &statbuf); |
| |
| return modifiedTimeOnOpen.tv_sec != mtim(statbuf).tv_sec || modifiedTimeOnOpen.tv_nsec != mtim(statbuf).tv_nsec; |
| } |
| |
| #endif // _WIN32 |
| |
| //------------------------------------------------------------------------ |
| // GDir and GDirEntry |
| //------------------------------------------------------------------------ |
| |
| GDirEntry::GDirEntry(const char *dirPath, const char *nameA, bool doStat) |
| { |
| #ifdef _WIN32 |
| DWORD fa; |
| #else |
| struct stat st; |
| #endif |
| |
| name = new GooString(nameA); |
| dir = false; |
| fullPath = new GooString(dirPath); |
| appendToPath(fullPath, nameA); |
| if (doStat) { |
| #ifdef _WIN32 |
| fa = GetFileAttributesA(fullPath->c_str()); |
| dir = (fa != 0xFFFFFFFF && (fa & FILE_ATTRIBUTE_DIRECTORY)); |
| #else |
| if (stat(fullPath->c_str(), &st) == 0) { |
| dir = S_ISDIR(st.st_mode); |
| } |
| #endif |
| } |
| } |
| |
| GDirEntry::~GDirEntry() |
| { |
| delete fullPath; |
| delete name; |
| } |
| |
| GDir::GDir(const char *name, bool doStatA) |
| { |
| path = new GooString(name); |
| doStat = doStatA; |
| #ifdef _WIN32 |
| std::unique_ptr<GooString> tmp = path->copy(); |
| tmp->append("/*.*"); |
| hnd = FindFirstFileA(tmp->c_str(), &ffd); |
| #else |
| dir = opendir(name); |
| #endif |
| } |
| |
| GDir::~GDir() |
| { |
| delete path; |
| #ifdef _WIN32 |
| if (hnd != INVALID_HANDLE_VALUE) { |
| FindClose(hnd); |
| hnd = INVALID_HANDLE_VALUE; |
| } |
| #else |
| if (dir) { |
| closedir(dir); |
| } |
| #endif |
| } |
| |
| std::unique_ptr<GDirEntry> GDir::getNextEntry() |
| { |
| #ifdef _WIN32 |
| if (hnd != INVALID_HANDLE_VALUE) { |
| auto e = std::make_unique<GDirEntry>(path->c_str(), ffd.cFileName, doStat); |
| if (!FindNextFileA(hnd, &ffd)) { |
| FindClose(hnd); |
| hnd = INVALID_HANDLE_VALUE; |
| } |
| return e; |
| } |
| #else |
| struct dirent *ent; |
| if (dir) { |
| do { |
| ent = readdir(dir); |
| } while (ent && (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))); |
| if (ent) { |
| return std::make_unique<GDirEntry>(path->c_str(), ent->d_name, doStat); |
| } |
| } |
| #endif |
| |
| return {}; |
| } |
| |
| void GDir::rewind() |
| { |
| #ifdef _WIN32 |
| if (hnd != INVALID_HANDLE_VALUE) |
| FindClose(hnd); |
| std::unique_ptr<GooString> tmp = path->copy(); |
| tmp->append("/*.*"); |
| hnd = FindFirstFileA(tmp->c_str(), &ffd); |
| #else |
| if (dir) { |
| rewinddir(dir); |
| } |
| #endif |
| } |