diff --git a/src/common/rclconfig.cpp b/src/common/rclconfig.cpp index 6f795f31..8c90fb26 100644 --- a/src/common/rclconfig.cpp +++ b/src/common/rclconfig.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -936,12 +937,9 @@ bool RclConfig::getMissingHelperDesc(string& out) const void RclConfig::storeMissingHelperDesc(const string &s) { string fmiss = path_cat(getCacheDir(), "missing"); - FILE *fp = fopen(fmiss.c_str(), "w"); - if (fp) { - if (s.size() > 0 && fwrite(s.c_str(), s.size(), 1, fp) != 1) { - LOGERR("storeMissingHelperDesc: fwrite failed\n"); - } - fclose(fp); + fstream fp = path_open(fmiss, ios::trunc | ios::out); + if (fp.is_open()) { + fp << s; } } @@ -1340,9 +1338,23 @@ string RclConfig::getCachedirPath(const char *varname, const char *dflt) const return path_canon(result); } +// On Windows, try to translate a possibly non-ascii path into the +// shortpath alias. We first create the target, as this only works if +// it exists. Used for xapiandb and aspell as these can't handle +// Unicode paths. +static string maybeshortpath(const std::string& in) +{ +#ifdef _WIN32 + path_makepath(in, 0700); + return path_shortpath(in); +#else + return in; +#endif +} + string RclConfig::getDbDir() const { - return getCachedirPath("dbdir", "xapiandb"); + return maybeshortpath(getCachedirPath("dbdir", "xapiandb")); } string RclConfig::getWebcacheDir() const { @@ -1354,7 +1366,7 @@ string RclConfig::getMboxcacheDir() const } string RclConfig::getAspellcacheDir() const { - return getCachedirPath("aspellDicDir", ""); + return maybeshortpath(getCachedirPath("aspellDicDir", "")); } string RclConfig::getStopfile() const @@ -1730,31 +1742,28 @@ bool RclConfig::initUserConfig() // Use protective 700 mode to create the top configuration // directory: documents can be reconstructed from index data. - if (!path_exists(m_confdir) && - mkdir(m_confdir.c_str(), 0700) < 0) { - m_reason += string("mkdir(") + m_confdir + ") failed: " + - strerror(errno); + if (!path_exists(m_confdir) && !path_makepath(m_confdir, 0700)) { + m_reason += string("mkdir(") + m_confdir + ") failed: "+strerror(errno); return false; } string lang = localelang(); for (int i = 0; i < ncffiles; i++) { string dst = path_cat(m_confdir, string(configfiles[i])); if (!path_exists(dst)) { - FILE *fp = fopen(dst.c_str(), "w"); - if (fp) { - fprintf(fp, "%s\n", blurb); + fstream output = path_open(dst, ios::out); + if (output.is_open()) { + output << blurb << "\n"; if (!strcmp(configfiles[i], "recoll.conf")) { // Add improved unac_except_trans for some languages if (lang == "se" || lang == "dk" || lang == "no" || lang == "fi") { - fprintf(fp, "%s\n", swedish_ex); + output << swedish_ex << "\n"; } else if (lang == "de") { - fprintf(fp, "%s\n", german_ex); + output << german_ex << "\n"; } } - fclose(fp); } else { - m_reason += string("fopen ") + dst + ": " + strerror(errno); + m_reason += string("open ") + dst + ": " + strerror(errno); return false; } } diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index af87896f..72a718d4 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -1071,16 +1072,15 @@ void RclMain::exportSimpleSearchHistory() } string path = qs2utf8s(dialog.selectedFiles().value(0)); LOGDEB("Chosen path: " << path << "\n"); - FILE *fp = fopen(path.c_str(), "wb"); - if (fp == 0) { - QMessageBox::warning(0, "Recoll", - tr("Could not open/create file")); + std::fstream fp = path_open(path, std::ios::out | std::ios::trunc); + if (!fp.is_open()) { + QMessageBox::warning(0, "Recoll", tr("Could not open/create file")); return; } for (int i = 0; i < prefs.ssearchHistory.count(); i++) { - fprintf(fp, "%s\n", qs2utf8s(prefs.ssearchHistory[i]).c_str()); + fp << qs2utf8s(prefs.ssearchHistory[i]) << "\n"; } - fclose(fp); + fp.close(); } // Called when the uiprefs dialog is ok'd diff --git a/src/qtgui/restable.cpp b/src/qtgui/restable.cpp index e90933b0..270a4773 100644 --- a/src/qtgui/restable.cpp +++ b/src/qtgui/restable.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -418,7 +419,7 @@ QVariant RecollModel::data(const QModelIndex& index, int role) const return QString::fromUtf8(lr.front().c_str()); } -void RecollModel::saveAsCSV(FILE *fp) +void RecollModel::saveAsCSV(std::fstream& fp) { if (!m_source) return; @@ -433,7 +434,7 @@ void RecollModel::saveAsCSV(FILE *fp) } string csv; stringsToCSV(tokens, csv); - fprintf(fp, "%s\n", csv.c_str()); + fp << csv << "\n"; tokens.clear(); for (int row = 0; row < rows; row++) { @@ -445,7 +446,7 @@ void RecollModel::saveAsCSV(FILE *fp) tokens.push_back(m_getters[col](m_fields[col], doc)); } stringsToCSV(tokens, csv); - fprintf(fp, "%s\n", csv.c_str()); + fp << csv << "\n"; tokens.clear(); } } @@ -785,22 +786,19 @@ void ResTable::saveAsCSV() LOGDEB("ResTable::saveAsCSV\n"); if (!m_model) return; - QString s = - QFileDialog::getSaveFileName(this, //parent - tr("Save table to CSV file"), - QString::fromLocal8Bit(path_home().c_str()) - ); + QString s = QFileDialog::getSaveFileName( + this, tr("Save table to CSV file"), path2qs(path_home())); if (s.isEmpty()) return; - const char *tofile = s.toLocal8Bit(); - FILE *fp = fopen(tofile, "w"); - if (fp == 0) { + std::string tofile = qs2path(s); + std::fstream fp = path_open(tofile, std::ios::out|std::ios::trunc); + if (!fp.is_open()) { QMessageBox::warning(0, "Recoll", tr("Can't open/create file: ") + s); return; } m_model->saveAsCSV(fp); - fclose(fp); + fp.close(); } // This is called when the sort order is changed from another widget diff --git a/src/qtgui/restable.h b/src/qtgui/restable.h index 2bf814a9..6f7a2b43 100644 --- a/src/qtgui/restable.h +++ b/src/qtgui/restable.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "ui_restable.h" #include "docseq.h" @@ -47,7 +48,7 @@ public: int role = Qt::DisplayRole ) const; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole ) const; - virtual void saveAsCSV(FILE *fp); + virtual void saveAsCSV(std::fstream& fp); virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); // Specific methods virtual void readDocSource(); diff --git a/src/qtgui/winschedtool.cpp b/src/qtgui/winschedtool.cpp index 795f96c1..a8f25314 100644 --- a/src/qtgui/winschedtool.cpp +++ b/src/qtgui/winschedtool.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -42,18 +43,21 @@ void WinSchedToolW::init() connect(startPB, SIGNAL(clicked()), this, SLOT(startWinScheduler())); - // thisexecpath returns the directory + // Use a short path on Windows if possible to avoid issues with + // accented characters + string confdir = path_shortpath(theconfig->getConfDir()); + + // path_thisexecpath() returns the directory string recollindex = path_cat(path_thisexecpath(), "recollindex.exe"); LOGDEB("WinSchedTool: recollindex: " << recollindex << endl); - string batchfile = path_cat(theconfig->getConfDir(), "winsched.bat"); + string batchfile = path_cat(confdir, "winsched.bat"); LOGDEB("WinSchedTool: batch file " << batchfile << endl); if (!path_exists(batchfile)) { - FILE *fp = fopen(batchfile.c_str(), "w"); - fprintf(fp, "\"%s\" -c \"%s\"\n", recollindex.c_str(), - theconfig->getConfDir().c_str()); - fclose(fp); + std::fstream fp = path_open(batchfile, ios::out|ios::trunc); + fp << "\"" << recollindex << "\" -c \"" << confdir << "\"\n"; + fp.close(); } QString blurb = tr( "

Recoll indexing batch scheduling

" diff --git a/src/rcldb/rcldb.cpp b/src/rcldb/rcldb.cpp index 2c65ac53..28202a23 100644 --- a/src/rcldb/rcldb.cpp +++ b/src/rcldb/rcldb.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using namespace std; @@ -283,12 +284,12 @@ void Db::Native::openWrite(const string& dir, Db::OpenMode mode) // Force Chert format, don't store the text. string stub = path_cat(m_rcldb->m_config->getConfDir(), "xapian.stub"); - FILE *fp = fopen(stub.c_str(), "w"); - if (nullptr == fp) { + std::fstream fp = path_open(stub, std::ios::out|std::ios::trunc); + if (!fp.is_open()) { throw(string("Can't create ") + stub); } - fprintf(fp, "chert %s\n", dir.c_str()); - fclose(fp); + fp << "chert " << dir << "\n"; + fp.close(); xwdb = Xapian::WritableDatabase(stub, action); m_storetext = false; } diff --git a/src/utils/conftree.cpp b/src/utils/conftree.cpp index 0a70093e..94ce2922 100644 --- a/src/utils/conftree.cpp +++ b/src/utils/conftree.cpp @@ -235,31 +235,27 @@ ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp, m_fmtime(0), m_holdWrites(false) { status = readonly ? STATUS_RO : STATUS_RW; + int mode = readonly ? ios::in : ios::in | ios::out; + if (!readonly && !path_exists(fname)) { + mode |= ios::trunc; + } + fstream input = path_open(fname, mode); + if (!input.is_open()) { + LOGERR("ConfSimple::ConfSimple: fstream(w)("< #include #include +#include #ifdef _WIN32 + #include "safefcntl.h" #include "safeunistd.h" #include "safewindows.h" #include "safesysstat.h" #include "transcode.h" +#include "log.h" #define STAT _wstati64 #define LSTAT _wstati64 @@ -66,8 +69,12 @@ #define READDIR _wreaddir #define DIRENT _wdirent #define DIRHDL _WDIR +#define MKDIR(a,b) _wmkdir(a) +#define OPEN ::_wopen +#define UNLINK _wunlink + +#else /* !_WIN32 -> */ -#else // Not windows -> #include #include #include @@ -86,7 +93,12 @@ #define READDIR readdir #define DIRENT dirent #define DIRHDL DIR -#endif +#define MKDIR(a,b) mkdir(a,b) +#define O_BINARY 0 +#define OPEN ::open +#define UNLINK unlink + +#endif /* !_WIN32 */ #include #include @@ -100,9 +112,11 @@ #include "pathut.h" #include "smallut.h" + using namespace std; #ifdef _WIN32 +#include /// Convert \ separators to / void path_slashize(string& s) { @@ -276,7 +290,25 @@ flock (int fd, int operation) return 0; } -#endif // Win32 only section +std::string path_shortpath(const std::string& path) +{ + SYSPATH(path, syspath); + wchar_t wspath[MAX_PATH]; + int ret = GetShortPathNameW(syspath, wspath, MAX_PATH); + if (ret == 0) { + LOGERR("GetShortPathNameW failed for [" << path << "]\n"); + return path; + } else if (ret >= MAX_PATH) { + LOGERR("GetShortPathNameW [" << path << "] too long " << + path.size() << " MAX_PATH " << MAX_PATH << "\n"); + return path; + } + string shortpath; + wchartoutf8(wspath, shortpath); + return shortpath; +} + +#endif /* _WIN32 */ bool fsocc(const string& path, int *pc, long long *avmbs) { @@ -284,8 +316,8 @@ bool fsocc(const string& path, int *pc, long long *avmbs) #ifdef _WIN32 ULARGE_INTEGER freebytesavail; ULARGE_INTEGER totalbytes; - if (!GetDiskFreeSpaceExA(path.c_str(), &freebytesavail, - &totalbytes, NULL)) { + SYSPATH(path, syspath); + if (!GetDiskFreeSpaceExW(syspath, &freebytesavail, &totalbytes, NULL)) { return false; } if (pc) { @@ -295,7 +327,7 @@ bool fsocc(const string& path, int *pc, long long *avmbs) *avmbs = int(totalbytes.QuadPart / FSOCC_MB); } return true; -#else // not windows -> +#else /* !_WIN32 */ struct statvfs buf; if (statvfs(path.c_str(), &buf) != 0) { @@ -323,7 +355,7 @@ bool fsocc(const string& path, int *pc, long long *avmbs) } } return true; -#endif +#endif /* !_WIN32 */ } @@ -433,21 +465,30 @@ string path_home() { #ifdef _WIN32 string dir; - const char *cp = getenv("USERPROFILE"); + // Using wgetenv does not work well, depending on the + // environment I get wrong values for the accented chars (works + // with recollindex started from msys command window, does not + // work when started from recoll. SHGet... fixes this + //const wchar_t *cp = _wgetenv(L"USERPROFILE"); + wchar_t *cp; + SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &cp); if (cp != 0) { - dir = cp; + wchartoutf8(cp, dir); } if (dir.empty()) { - cp = getenv("HOMEDRIVE"); + cp = _wgetenv(L"HOMEDRIVE"); + wchartoutf8(cp, dir); if (cp != 0) { - const char *cp1 = getenv("HOMEPATH"); + string dir1; + const wchar_t *cp1 = _wgetenv(L"HOMEPATH"); + wchartoutf8(cp1, dir1); if (cp1 != 0) { - dir = string(cp) + string(cp1); + dir = path_cat(dir, dir1); } } } if (dir.empty()) { - dir = "C:\\"; + dir = "C:/"; } dir = path_canon(dir); path_catslash(dir); @@ -475,12 +516,15 @@ string path_home() string path_homedata() { #ifdef _WIN32 - const char *cp = getenv("LOCALAPPDATA"); + wchar_t *cp; + SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &cp); string dir; if (cp != 0) { - dir = path_canon(cp); + wchartoutf8(cp, dir); } - if (dir.empty()) { + if (!dir.empty()) { + dir = path_canon(dir); + } else { dir = path_cat(path_home(), "AppData/Local/"); } return dir; @@ -673,8 +717,11 @@ bool path_makepath(const string& ipath, int mode) path += elem; // Not using path_isdir() here, because this cant grok symlinks // If we hit an existing file, no worry, mkdir will just fail. - if (access(path.c_str(), 0) != 0) { - if (mkdir(path.c_str(), mode) != 0) { + LOGDEB1("path_makepath: testing existence: [" << path << "]\n"); + if (!path_exists(path)) { + LOGDEB1("path_makepath: creating directory [" << path << "]\n"); + SYSPATH(path, syspath); + if (MKDIR(syspath, mode) != 0) { //cerr << "mkdir " << path << " failed, errno " << errno << endl; return false; } @@ -684,6 +731,24 @@ bool path_makepath(const string& ipath, int mode) return true; } +std::fstream path_open(const std::string& path, int mode) +{ +#if defined(_WIN32) && defined (_MSC_VER) + // MSC STL has support for using wide chars in fstream + // constructor. We need this if, e.g. the user name/home directory + // is not ASCII. Actually don't know how to do this with gcc + wchar_t wpath[MAX_PATH + 1]; + utf8towchar(path, wpath, MAX_PATH); + std::fstream ret(wpath, mode); + if (!ret.is_open()) { + LOGERR("path_open("<< path << ", "<< mode <<") errno " << errno <<"\n"); + } + return ret; +#else + return std::fstream(path, mode); +#endif +} + bool path_isdir(const string& path, bool follow) { struct STATBUF st; @@ -1098,7 +1163,8 @@ Pidfile::~Pidfile() int Pidfile::read_pid() { - int fd = ::open(m_path.c_str(), O_RDONLY); + SYSPATH(m_path, syspath); + int fd = OPEN(syspath, O_RDONLY); if (fd == -1) { return -1; } @@ -1120,8 +1186,8 @@ int Pidfile::read_pid() int Pidfile::flopen() { - const char *path = m_path.c_str(); - if ((m_fd = ::open(path, O_RDWR | O_CREAT, 0644)) == -1) { + SYSPATH(m_path, syspath); + if ((m_fd = OPEN(syspath, O_RDWR | O_CREAT, 0644)) == -1) { m_reason = "Open failed: [" + m_path + "]: " + strerror(errno); return -1; } @@ -1198,7 +1264,8 @@ int Pidfile::close() int Pidfile::remove() { - return unlink(m_path.c_str()); + SYSPATH(m_path, syspath); + return UNLINK(syspath); } // Call funcs that need static init (not initially reentrant) diff --git a/src/utils/pathut.h b/src/utils/pathut.h index a31e06fa..e0716748 100644 --- a/src/utils/pathut.h +++ b/src/utils/pathut.h @@ -21,6 +21,7 @@ #include #include #include +#include // Must be called in main thread before starting other threads extern void pathut_init_mt(); @@ -129,6 +130,22 @@ bool fsocc(const std::string& path, int *pc, long long *avmbs = 0); /// mkdir -p extern bool path_makepath(const std::string& path, int mode); +/* Open file, trying to do the right thing with non-ASCII paths on + * Windows, where it only works with MSVC at the moment if the path is + * not ASCII, because it uses fstream(wchar_t*), which is an MSVC + * extension. On other OSes, just builds the fstream. We'd need to + * find a way to make this work with g++. It would be easier in this + * case to use a FILE (_openw(), then fdopen()), but conftree really + * depends on std::iostream. One possible workaround for g++ would be + * to use shortpaths (which we already use to pass file names to + * xapian and aspell). Most of the problems are caused by the home + * directory name being non-ASCII, so returning a short path in + * path_home() would probably solve everything (but not pretty). + * + * @param path an utf-8 file path. + * @param mode is an std::fstream mode (ios::in etc.) */ +extern std::fstream path_open(const std::string& path, int mode); + /// Where we create the user data subdirs extern std::string path_homedata(); /// Test if path is absolute @@ -164,6 +181,9 @@ public: /// Convert \ separators to / void path_slashize(std::string& s); void path_backslashize(std::string& s); +extern std::string path_shortpath(const std::string& path); +#else +#define path_shortpath(path) (path) #endif /// Lock/pid file class. This is quite close to the pidfile_xxx diff --git a/src/utils/rclutil.cpp b/src/utils/rclutil.cpp index 953a9af9..3e22cdea 100644 --- a/src/utils/rclutil.cpp +++ b/src/utils/rclutil.cpp @@ -118,6 +118,10 @@ static const string& path_wingetrcltmpdir() LOGSYSERR("path_wingettempfilename", "path_makepath", tdir); } } + // Try to use a short path to avoid issues in case the user + // login name is not ascii, especially with command line + // parameter passing + tdir = path_shortpath(tdir); } return tdir; }