/* Copyright (C) 2016-2019 J.F.Dockes * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "autoconfig.h" #include #include #include #include "safefcntl.h" #include "safeunistd.h" #include "dirent.h" #include "cstr.h" #ifdef _WIN32 #include "safewindows.h" #else #include #include #include #endif #include #include #include #include "safesysstat.h" #include #include #include #include #include "rclutil.h" #include "pathut.h" #include "wipedir.h" #include "transcode.h" #include "md5ut.h" #include "log.h" #include "smallut.h" using namespace std; template void map_ss_cp_noshr(T s, T *d) { for (const auto& ent : s) { d->insert( pair(string(ent.first.begin(), ent.first.end()), string(ent.second.begin(), ent.second.end()))); } } template void map_ss_cp_noshr >( map s, map*d); template void map_ss_cp_noshr >( unordered_map s, unordered_map*d); #ifdef _WIN32 static bool path_hasdrive(const string& s) { if (s.size() >= 2 && isalpha(s[0]) && s[1] == ':') { return true; } return false; } static bool path_isdriveabs(const string& s) { if (s.size() >= 3 && isalpha(s[0]) && s[1] == ':' && s[2] == '/') { return true; } return false; } #include #pragma comment(lib, "shlwapi.lib") string path_thisexecpath() { wchar_t text[MAX_PATH]; GetModuleFileNameW(NULL, text, MAX_PATH); #ifdef NTDDI_WIN8_future PathCchRemoveFileSpec(text, MAX_PATH); #else PathRemoveFileSpecW(text); #endif string path; wchartoutf8(text, path); if (path.empty()) { path = "c:/"; } return path; } string path_wingettempfilename(wchar_t *pref) { // Use a subdirectory named "rcltmp" inside the windows temp // location. wchar_t dbuf[MAX_PATH + 1]; GetTempPathW(MAX_PATH + 1, dbuf); string tdir; wchartoutf8(dbuf, tdir); tdir = path_cat(tdir, "rcltmp");; if (!path_exists(tdir)) { if (path_makepath(tdir, 0700)) { LOGSYSERR("path_wingettempfilename", "path_makepath", tdir); } } utf8towchar(tdir, dbuf, MAX_PATH); wchar_t buf[MAX_PATH + 1]; GetTempFileNameW(dbuf, pref, 0, buf); // Windows will have created a temp file, we delete it. if (!DeleteFileW(buf)) { LOGSYSERR("path_wingettempfilename", "DeleteFileW", filename); } else { LOGDEB1("path_wingettempfilename: DeleteFile " << filename << " Ok\n"); } string filename; wchartoutf8(buf, filename); path_slashize(filename); return filename; } #endif // _WIN32 // Check if path is either non-existing or an empty directory. bool path_empty(const string& path) { if (path_isdir(path)) { string reason; std::set entries; if (!readdir(path, reason, entries) || entries.empty()) { return true; } return false; } else { return !path_exists(path); } } string path_defaultrecollconfsubdir() { #ifdef _WIN32 return "Recoll"; #else return ".recoll"; #endif } // Location for sample config, filters, etc. (e.g. /usr/share/recoll/) const string& path_pkgdatadir() { static string datadir; if (datadir.empty()) { #ifdef _WIN32 datadir = path_cat(path_thisexecpath(), "Share"); #else const char *cdatadir = getenv("RECOLL_DATADIR"); if (cdatadir == 0) { // If not in environment, use the compiled-in constant. datadir = RECOLL_DATADIR; } else { datadir = cdatadir; } #endif } return datadir; } // Printable url: this is used to transcode from the system charset // into either utf-8 if transcoding succeeds, or url-encoded bool printableUrl(const string& fcharset, const string& in, string& out) { #ifdef _WIN32 // On windows our paths are always utf-8 out = in; #else int ecnt = 0; if (!transcode(in, out, fcharset, "UTF-8", &ecnt) || ecnt) { out = url_encode(in, 7); } #endif return true; } string url_gpathS(const string& url) { #ifdef _WIN32 string u = url_gpath(url); string nu; if (path_hasdrive(u)) { nu.append(1, '/'); nu.append(1, u[0]); if (path_isdriveabs(u)) { nu.append(u.substr(2)); } else { // This should be an error really nu.append(1, '/'); nu.append(u.substr(2)); } } return nu; #else return url_gpath(url); #endif } const string& tmplocation() { static string stmpdir; if (stmpdir.empty()) { const char *tmpdir = getenv("RECOLL_TMPDIR"); if (tmpdir == 0) { tmpdir = getenv("TMPDIR"); } if (tmpdir == 0) { tmpdir = getenv("TMP"); } if (tmpdir == 0) { tmpdir = getenv("TEMP"); } if (tmpdir == 0) { #ifdef _WIN32 wchar_t bufw[MAX_PATH + 1]; GetTempPathW(MAX_PATH + 1, bufw); wchartoutf8(bufw, stmpdir); #else stmpdir = "/tmp"; #endif } else { stmpdir = tmpdir; } stmpdir = path_canon(stmpdir); } return stmpdir; } bool maketmpdir(string& tdir, string& reason) { #ifndef _WIN32 tdir = path_cat(tmplocation(), "rcltmpXXXXXX"); char *cp = strdup(tdir.c_str()); if (!cp) { reason = "maketmpdir: out of memory (for file name !)\n"; tdir.erase(); return false; } // There is a race condition between name computation and // mkdir. try to make sure that we at least don't shoot ourselves // in the foot #if !defined(HAVE_MKDTEMP) || defined(_WIN32) static std::mutex mmutex; std::unique_lock lock(mmutex); #endif if (! #ifdef HAVE_MKDTEMP mkdtemp(cp) #else mktemp(cp) #endif // HAVE_MKDTEMP ) { free(cp); reason = "maketmpdir: mktemp failed for [" + tdir + "] : " + strerror(errno); tdir.erase(); return false; } tdir = cp; free(cp); #else // _WIN32 // There is a race condition between name computation and // mkdir. try to make sure that we at least don't shoot ourselves // in the foot static std::mutex mmutex; std::unique_lock lock(mmutex); static wchar_t tmpbasename[]{L"rcltmp"}; tdir = path_wingettempfilename(tmpbasename); #endif // At this point the directory does not exist yet except if we used // mkdtemp #if !defined(HAVE_MKDTEMP) || defined(_WIN32) if (mkdir(tdir.c_str(), 0700) < 0) { reason = string("maketmpdir: mkdir ") + tdir + " failed"; tdir.erase(); return false; } #endif return true; } class TempFile::Internal { public: Internal(const std::string& suffix); ~Internal(); friend class TempFile; private: std::string m_filename; std::string m_reason; bool m_noremove{false}; }; TempFile::TempFile(const string& suffix) : m(new Internal(suffix)) { } TempFile::TempFile() { m = std::shared_ptr(); } const char *TempFile::filename() const { return m ? m->m_filename.c_str() : ""; } const std::string& TempFile::getreason() const { static string fatal{"fatal error"}; return m ? m->m_reason : fatal; } void TempFile::setnoremove(bool onoff) { if (m) m->m_noremove = onoff; } bool TempFile::ok() const { return m ? !m->m_filename.empty() : false; } TempFile::Internal::Internal(const string& suffix) { // Because we need a specific suffix, can't use mkstemp // well. There is a race condition between name computation and // file creation. try to make sure that we at least don't shoot // our own selves in the foot. maybe we'll use mkstemps one day. static std::mutex mmutex; std::unique_lock lock(mmutex); #ifndef _WIN32 string filename = path_cat(tmplocation(), "rcltmpfXXXXXX"); char *cp = strdup(filename.c_str()); if (!cp) { m_reason = "Out of memory (for file name !)\n"; return; } // Using mkstemp this way is awful (bot the suffix adding and // using mkstemp() instead of mktemp just to avoid the warnings) int fd; if ((fd = mkstemp(cp)) < 0) { free(cp); m_reason = "TempFileInternal: mkstemp failed\n"; return; } close(fd); unlink(cp); filename = cp; free(cp); #else static wchar_t tmpbasename[]{L"rcl"}; string filename = path_wingettempfilename(tmpbasename); #endif m_filename = filename + suffix; LOGDEB1("TempFile: filename: " << m_filename << endl); int fd1 = open(m_filename.c_str(), O_CREAT | O_EXCL, 0600); if (fd1 < 0) { m_reason = string("Open/create error. errno : ") + lltodecstr(errno) + " file name: " + m_filename; m_filename.erase(); } else { close(fd1); } } #ifdef _WIN32 static list remainingTempFileNames; static std::mutex remTmpFNMutex; #endif TempFile::Internal::~Internal() { if (!m_filename.empty() && !m_noremove) { LOGDEB1("TempFile:~: unlinking " << m_filename << endl); if (unlink(m_filename.c_str()) != 0) { LOGSYSERR("TempFile:~", "unlink", m_filename); #ifdef _WIN32 { std::unique_lock lock(remTmpFNMutex); remainingTempFileNames.push_back(m_filename); } #endif } else { LOGDEB1("TempFile:~: unlink " << m_filename << " Ok\n"); } } } // On Windows we sometimes fail to remove temporary files because // they are open. It's difficult to make sure this does not // happen, so we add a cleaning pass after clearing the input // handlers cache (which should kill subprocesses etc.) void TempFile::tryRemoveAgain() { #ifdef _WIN32 LOGDEB1("TempFile::tryRemoveAgain. List size: " << remainingTempFileNames.size() << endl); std::unique_lock lock(remTmpFNMutex); std::list::iterator pos = remainingTempFileNames.begin(); while (pos != remainingTempFileNames.end()) { if (unlink(pos->c_str()) != 0) { LOGSYSERR("TempFile::tryRemoveAgain", "unlink", *pos); pos++; } else { pos = remainingTempFileNames.erase(pos); } } #endif } TempDir::TempDir() { if (!maketmpdir(m_dirname, m_reason)) { m_dirname.erase(); return; } LOGDEB("TempDir::TempDir: -> " << m_dirname << endl); } TempDir::~TempDir() { if (!m_dirname.empty()) { LOGDEB("TempDir::~TempDir: erasing " << m_dirname << endl); (void)wipedir(m_dirname, true, true); m_dirname.erase(); } } bool TempDir::wipe() { if (m_dirname.empty()) { m_reason = "TempDir::wipe: no directory !\n"; return false; } if (wipedir(m_dirname, false, true)) { m_reason = "TempDir::wipe: wipedir failed\n"; return false; } return true; } // Freedesktop standard paths for cache directory (thumbnails are now in there) static const string& xdgcachedir() { static string xdgcache; if (xdgcache.empty()) { const char *cp = getenv("XDG_CACHE_HOME"); if (cp == 0) { xdgcache = path_cat(path_home(), ".cache"); } else { xdgcache = string(cp); } } return xdgcache; } static const string& thumbnailsdir() { static string thumbnailsd; if (thumbnailsd.empty()) { thumbnailsd = path_cat(xdgcachedir(), "thumbnails"); if (access(thumbnailsd.c_str(), 0) != 0) { thumbnailsd = path_cat(path_home(), ".thumbnails"); } } return thumbnailsd; } // Place for 256x256 files static const string thmbdirlarge = "large"; // 128x128 static const string thmbdirnormal = "normal"; static void thumbname(const string& url, string& name) { string digest; string l_url = url_encode(url); MD5String(l_url, digest); MD5HexPrint(digest, name); name += ".png"; } bool thumbPathForUrl(const string& url, int size, string& path) { string name; thumbname(url, name); if (size <= 128) { path = path_cat(thumbnailsdir(), thmbdirnormal); path = path_cat(path, name); if (access(path.c_str(), R_OK) == 0) { return true; } } path = path_cat(thumbnailsdir(), thmbdirlarge); path = path_cat(path, name); if (access(path.c_str(), R_OK) == 0) { return true; } // File does not exist. Path corresponds to the large version at this point, // fix it if needed. if (size <= 128) { path = path_cat(path_home(), thmbdirnormal); path = path_cat(path, name); } return false; } void rclutil_init_mt() { path_pkgdatadir(); tmplocation(); thumbnailsdir(); }