2016-11-12 15:38:13 -06:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* This file is part of the LibreOffice project.
|
|
|
|
*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
|
2017-12-20 07:06:26 -06:00
|
|
|
#include <config.h>
|
2016-11-12 15:38:13 -06:00
|
|
|
|
2017-03-08 10:38:22 -06:00
|
|
|
#include "FileUtil.hpp"
|
|
|
|
|
2017-01-29 23:04:10 -06:00
|
|
|
#include <ftw.h>
|
2016-11-12 15:38:13 -06:00
|
|
|
#include <sys/stat.h>
|
2018-08-29 10:23:22 -05:00
|
|
|
#ifdef __linux
|
2016-11-12 15:38:13 -06:00
|
|
|
#include <sys/vfs.h>
|
2018-09-05 07:11:05 -05:00
|
|
|
#elif defined IOS
|
2018-08-29 10:23:22 -05:00
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#endif
|
2016-11-12 15:38:13 -06:00
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <fstream>
|
|
|
|
#include <mutex>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <Poco/TemporaryFile.h>
|
|
|
|
|
|
|
|
#include "Log.hpp"
|
|
|
|
#include "Util.hpp"
|
2017-03-31 11:18:41 -05:00
|
|
|
#include "Unit.hpp"
|
2016-11-12 15:38:13 -06:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
void alertAllUsersAndLog(const std::string& message, const std::string& cmd, const std::string& kind)
|
|
|
|
{
|
2016-12-19 17:28:26 -06:00
|
|
|
LOG_ERR(message);
|
2016-11-12 15:38:13 -06:00
|
|
|
Util::alertAllUsers(cmd, kind);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace FileUtil
|
|
|
|
{
|
|
|
|
std::string createRandomDir(const std::string& path)
|
|
|
|
{
|
2018-02-07 03:17:19 -06:00
|
|
|
const std::string name = Util::rng::getFilename(64);
|
2016-11-12 15:38:13 -06:00
|
|
|
Poco::File(Poco::Path(path, name)).createDirectories();
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2017-01-05 21:46:00 -06:00
|
|
|
std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename, const std::string& dstFilenamePrefix)
|
2016-11-12 15:38:13 -06:00
|
|
|
{
|
|
|
|
const std::string srcPath = srcDir + '/' + srcFilename;
|
2017-01-05 21:46:00 -06:00
|
|
|
const std::string dstFilename = dstFilenamePrefix + Util::encodeId(Util::rng::getNext()) + '_' + srcFilename;
|
|
|
|
const std::string dstPath = Poco::Path(Poco::Path::temp(), dstFilename).toString();
|
2016-11-12 15:38:13 -06:00
|
|
|
Poco::File(srcPath).copyTo(dstPath);
|
|
|
|
Poco::TemporaryFile::registerForDeletion(dstPath);
|
|
|
|
return dstPath;
|
|
|
|
}
|
|
|
|
|
2017-01-05 21:46:00 -06:00
|
|
|
std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename)
|
|
|
|
{
|
|
|
|
return getTempFilePath(srcDir, srcFilename, "");
|
|
|
|
}
|
|
|
|
|
2016-11-12 15:38:13 -06:00
|
|
|
bool saveDataToFileSafely(const std::string& fileName, const char *data, size_t size)
|
|
|
|
{
|
|
|
|
const auto tempFileName = fileName + ".temp";
|
|
|
|
std::fstream outStream(tempFileName, std::ios::out);
|
|
|
|
|
|
|
|
// If we can't create the file properly, just remove it
|
|
|
|
if (!outStream.good())
|
|
|
|
{
|
|
|
|
alertAllUsersAndLog("Creating " + tempFileName + " failed, disk full?", "internal", "diskfull");
|
|
|
|
// Try removing both just in case
|
|
|
|
std::remove(tempFileName.c_str());
|
|
|
|
std::remove(fileName.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
outStream.write(data, size);
|
|
|
|
if (!outStream.good())
|
|
|
|
{
|
|
|
|
alertAllUsersAndLog("Writing to " + tempFileName + " failed, disk full?", "internal", "diskfull");
|
|
|
|
outStream.close();
|
|
|
|
std::remove(tempFileName.c_str());
|
|
|
|
std::remove(fileName.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
outStream.close();
|
|
|
|
if (!outStream.good())
|
|
|
|
{
|
|
|
|
alertAllUsersAndLog("Closing " + tempFileName + " failed, disk full?", "internal", "diskfull");
|
|
|
|
std::remove(tempFileName.c_str());
|
|
|
|
std::remove(fileName.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Everything OK, rename the file to its proper name
|
|
|
|
if (std::rename(tempFileName.c_str(), fileName.c_str()) == 0)
|
|
|
|
{
|
2016-12-19 17:28:26 -06:00
|
|
|
LOG_DBG("Renaming " << tempFileName << " to " << fileName << " OK.");
|
2016-11-12 15:38:13 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
alertAllUsersAndLog("Renaming " + tempFileName + " to " + fileName + " failed, disk full?", "internal", "diskfull");
|
|
|
|
std::remove(tempFileName.c_str());
|
|
|
|
std::remove(fileName.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-29 23:04:10 -06:00
|
|
|
static int nftw_cb(const char *fpath, const struct stat*, int type, struct FTW*)
|
|
|
|
{
|
|
|
|
if (type == FTW_DP)
|
|
|
|
{
|
|
|
|
rmdir(fpath);
|
|
|
|
}
|
|
|
|
else if (type == FTW_F || type == FTW_SL)
|
|
|
|
{
|
|
|
|
unlink(fpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always continue even when things go wrong.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void removeFile(const std::string& path, const bool recursive)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
struct stat sb;
|
|
|
|
if (!recursive || stat(path.c_str(), &sb) == -1 || S_ISREG(sb.st_mode))
|
|
|
|
{
|
|
|
|
// Non-recursive directories, and files.
|
|
|
|
Poco::File(path).remove(recursive);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Directories only.
|
|
|
|
nftw(path.c_str(), nftw_cb, 128, FTW_DEPTH | FTW_PHYS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (const std::exception&)
|
|
|
|
{
|
|
|
|
// Already removed or we don't care about failures.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-12 15:38:13 -06:00
|
|
|
} // namespace FileUtil
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
struct fs
|
|
|
|
{
|
2018-12-10 01:55:24 -06:00
|
|
|
fs(const std::string& path, dev_t dev)
|
|
|
|
: _path(path), _dev(dev)
|
2016-11-12 15:38:13 -06:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-12-10 01:55:24 -06:00
|
|
|
const std::string& getPath() const { return _path; }
|
|
|
|
|
|
|
|
dev_t getDev() const { return _dev; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string _path;
|
|
|
|
dev_t _dev;
|
2016-11-12 15:38:13 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
struct fsComparator
|
|
|
|
{
|
|
|
|
bool operator() (const fs& lhs, const fs& rhs) const
|
|
|
|
{
|
2018-12-10 01:55:24 -06:00
|
|
|
return (lhs.getDev() < rhs.getDev());
|
2016-11-12 15:38:13 -06:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static std::mutex fsmutex;
|
|
|
|
static std::set<fs, fsComparator> filesystems;
|
|
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
namespace FileUtil
|
|
|
|
{
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2016-11-12 15:38:13 -06:00
|
|
|
void registerFileSystemForDiskSpaceChecks(const std::string& path)
|
|
|
|
{
|
2018-02-23 18:14:49 -06:00
|
|
|
const std::string::size_type lastSlash = path.rfind('/');
|
|
|
|
assert(path.empty() || lastSlash != std::string::npos);
|
|
|
|
if (lastSlash != std::string::npos)
|
2016-11-12 15:38:13 -06:00
|
|
|
{
|
2018-02-23 18:14:49 -06:00
|
|
|
const std::string dirPath = path.substr(0, lastSlash + 1) + '.';
|
|
|
|
LOG_INF("Registering filesystem for space checks: [" << dirPath << "]");
|
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(fsmutex);
|
2016-11-12 15:38:13 -06:00
|
|
|
|
|
|
|
struct stat s;
|
2016-11-22 21:06:05 -06:00
|
|
|
if (stat(dirPath.c_str(), &s) == 0)
|
|
|
|
{
|
|
|
|
filesystems.insert(fs(dirPath, s.st_dev));
|
|
|
|
}
|
2016-11-12 15:38:13 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-04 00:29:45 -06:00
|
|
|
std::string checkDiskSpaceOnRegisteredFileSystems(const bool cacheLastCheck)
|
2016-11-12 15:38:13 -06:00
|
|
|
{
|
|
|
|
static std::chrono::steady_clock::time_point lastCheck;
|
|
|
|
std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now());
|
|
|
|
|
2018-02-23 18:14:49 -06:00
|
|
|
std::lock_guard<std::mutex> lock(fsmutex);
|
|
|
|
|
|
|
|
// Don't check more often than once a minute
|
2016-11-12 15:38:13 -06:00
|
|
|
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck).count() < 60)
|
2016-11-22 21:06:50 -06:00
|
|
|
return std::string();
|
2016-11-12 15:38:13 -06:00
|
|
|
|
2017-01-04 00:29:45 -06:00
|
|
|
if (cacheLastCheck)
|
|
|
|
lastCheck = now;
|
2016-11-12 15:38:13 -06:00
|
|
|
|
2018-02-23 18:14:49 -06:00
|
|
|
for (const auto& i: filesystems)
|
2016-11-12 15:38:13 -06:00
|
|
|
{
|
2018-12-10 01:55:24 -06:00
|
|
|
if (!checkDiskSpace(i.getPath()))
|
2016-11-12 15:38:13 -06:00
|
|
|
{
|
2018-12-10 01:55:24 -06:00
|
|
|
return i.getPath();
|
2016-11-12 15:38:13 -06:00
|
|
|
}
|
|
|
|
}
|
2016-11-22 21:06:50 -06:00
|
|
|
|
|
|
|
return std::string();
|
2016-11-12 15:38:13 -06:00
|
|
|
}
|
2018-09-13 11:16:00 -05:00
|
|
|
#endif
|
2016-11-12 15:38:13 -06:00
|
|
|
|
|
|
|
bool checkDiskSpace(const std::string& path)
|
|
|
|
{
|
2016-11-22 21:06:05 -06:00
|
|
|
assert(!path.empty());
|
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
#if !MOBILEAPP
|
2017-03-31 11:18:41 -05:00
|
|
|
bool hookResult;
|
|
|
|
if (UnitBase::get().filterCheckDiskSpace(path, hookResult))
|
|
|
|
return hookResult;
|
2018-09-05 07:11:05 -05:00
|
|
|
#endif
|
2017-03-31 11:18:41 -05:00
|
|
|
|
2019-05-23 04:28:37 -05:00
|
|
|
// we should be able to run just OK with 5GB for production or 1GB for development
|
|
|
|
#if ENABLE_DEBUG
|
|
|
|
const int64_t gb(1);
|
|
|
|
#else
|
|
|
|
const int64_t gb(5);
|
|
|
|
#endif
|
|
|
|
constexpr int64_t ENOUGH_SPACE = gb*1024*1024*1024;
|
2018-08-29 10:23:22 -05:00
|
|
|
|
|
|
|
#ifdef __linux
|
2016-11-12 15:38:13 -06:00
|
|
|
struct statfs sfs;
|
|
|
|
if (statfs(path.c_str(), &sfs) == -1)
|
|
|
|
return true;
|
|
|
|
|
2018-02-23 18:14:49 -06:00
|
|
|
const int64_t freeBytes = static_cast<int64_t>(sfs.f_bavail) * sfs.f_bsize;
|
|
|
|
|
|
|
|
LOG_INF("Filesystem [" << path << "] has " << (freeBytes / 1024 / 1024) <<
|
|
|
|
" MB free (" << (sfs.f_bavail * 100. / sfs.f_blocks) << "%).");
|
|
|
|
|
|
|
|
if (freeBytes > ENOUGH_SPACE)
|
2017-02-21 12:55:04 -06:00
|
|
|
return true;
|
|
|
|
|
2016-11-12 15:38:13 -06:00
|
|
|
if (static_cast<double>(sfs.f_bavail) / sfs.f_blocks <= 0.05)
|
|
|
|
return false;
|
2018-09-05 07:11:05 -05:00
|
|
|
#elif defined IOS
|
2018-08-29 10:23:22 -05:00
|
|
|
NSDictionary *atDict = [[NSFileManager defaultManager] attributesOfFileSystemForPath:@"/" error:NULL];
|
|
|
|
long long freeSpace = [[atDict objectForKey:NSFileSystemFreeSize] longLongValue];
|
|
|
|
long long totalSpace = [[atDict objectForKey:NSFileSystemSize] longLongValue];
|
|
|
|
|
|
|
|
if (freeSpace > ENOUGH_SPACE)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (static_cast<double>(freeSpace) / totalSpace <= 0.05)
|
|
|
|
return false;
|
|
|
|
#endif
|
2017-02-21 12:55:04 -06:00
|
|
|
|
2016-11-12 15:38:13 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace FileUtil
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|