//======================================================================== // // 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 // Copyright (C) 2006 Kristian Høgsberg // Copyright (C) 2008 Adam Batkin // Copyright (C) 2008, 2010, 2012, 2013 Hib Eris // Copyright (C) 2009, 2012, 2014, 2017, 2018 Albert Astals Cid // Copyright (C) 2009 Kovid Goyal // Copyright (C) 2013, 2018 Adam Reichold // Copyright (C) 2013, 2017 Adrian Johnson // Copyright (C) 2013 Peter Breitenlohner // Copyright (C) 2013, 2017 Thomas Freitag // Copyright (C) 2017 Christoph Cullmann // Copyright (C) 2018 Mojca Miklavec // Copyright (C) 2019 Christian Persch // // 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 #ifndef _WIN32 # include # include # include # include # include # include #endif // _WIN32 #include #include #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 struct void_type { using type = void; }; template using void_t = typename void_type::type; template> 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 struct StatMtim> { static const struct timespec &value(const Stat &stbuf) { return stbuf.st_mtimespec; } }; inline const struct timespec &mtim(const struct stat &stbuf) { return StatMtim::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 } 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 } #ifndef _WIN32 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::max)(); #elif defined(HAVE_FSEEK64) || defined(__MINGW32__) return (std::numeric_limits::max)(); #elif defined(_WIN32) return (std::numeric_limits<__int64>::max)(); #else return (std::numeric_limits::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; } GooFile *GooFile::open(const GooString *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 ? nullptr : new GooFile(handle); } 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 ? nullptr : 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 } GooFile *GooFile::open(const GooString *fileName) { int fd = openFileDescriptor(fileName->c_str(), O_RDONLY); return fd < 0 ? nullptr : new GooFile(fd); } 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 GooString *tmp; tmp = path->copy(); tmp->append("/*.*"); hnd = FindFirstFileA(tmp->c_str(), &ffd); delete tmp; #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 } GDirEntry *GDir::getNextEntry() { GDirEntry *e = nullptr; #ifdef _WIN32 if (hnd != INVALID_HANDLE_VALUE) { e = new GDirEntry(path->c_str(), ffd.cFileName, doStat); if (!FindNextFileA(hnd, &ffd)) { FindClose(hnd); hnd = INVALID_HANDLE_VALUE; } } #else struct dirent *ent; if (dir) { do { ent = readdir(dir); } while (ent && (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))); if (ent) { e = new GDirEntry(path->c_str(), ent->d_name, doStat); } } #endif return e; } void GDir::rewind() { #ifdef _WIN32 GooString *tmp; if (hnd != INVALID_HANDLE_VALUE) FindClose(hnd); tmp = path->copy(); tmp->append("/*.*"); hnd = FindFirstFileA(tmp->c_str(), &ffd); delete tmp; #else if (dir) rewinddir(dir); #endif }