libreoffice-online/common/FileUtil.cpp
Tor Lillqvist 75438baa70 More mobile app stuff, very much early state of work in progress
Re-think Linux vs mobile ifdefs a bit. Use #ifdef __linux only to
surround code that actually is Linux-specific. Use #ifdef MOBILEAPP
for code that is for a mobile version (with no separste wsd, forkit,
and kit processes, and with no WebSocket protocol used).

Bypass UnitFoo for mobile. Possibly we do want the UnitFoo stuff after
all on mobile, to run in some special testing mode? Hard to say, let's
skipt it for now.
2018-09-10 15:13:43 +03:00

282 lines
8.2 KiB
C++

/* -*- 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/.
*/
#include <config.h>
#include "FileUtil.hpp"
#include <ftw.h>
#include <sys/stat.h>
#ifdef __linux
#include <sys/vfs.h>
#elif defined IOS
#import <Foundation/Foundation.h>
#endif
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <mutex>
#include <string>
#include <Poco/TemporaryFile.h>
#include "Log.hpp"
#include "Util.hpp"
#include "Unit.hpp"
namespace
{
void alertAllUsersAndLog(const std::string& message, const std::string& cmd, const std::string& kind)
{
LOG_ERR(message);
Util::alertAllUsers(cmd, kind);
}
}
namespace FileUtil
{
std::string createRandomDir(const std::string& path)
{
const std::string name = Util::rng::getFilename(64);
Poco::File(Poco::Path(path, name)).createDirectories();
return name;
}
std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename, const std::string& dstFilenamePrefix)
{
const std::string srcPath = srcDir + '/' + srcFilename;
const std::string dstFilename = dstFilenamePrefix + Util::encodeId(Util::rng::getNext()) + '_' + srcFilename;
const std::string dstPath = Poco::Path(Poco::Path::temp(), dstFilename).toString();
Poco::File(srcPath).copyTo(dstPath);
Poco::TemporaryFile::registerForDeletion(dstPath);
return dstPath;
}
std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename)
{
return getTempFilePath(srcDir, srcFilename, "");
}
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)
{
LOG_DBG("Renaming " << tempFileName << " to " << fileName << " OK.");
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;
}
}
}
}
}
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.
}
}
} // namespace FileUtil
namespace
{
struct fs
{
fs(const std::string& p, dev_t d)
: path(p), dev(d)
{
}
std::string path;
dev_t dev;
};
struct fsComparator
{
bool operator() (const fs& lhs, const fs& rhs) const
{
return (lhs.dev < rhs.dev);
}
};
static std::mutex fsmutex;
static std::set<fs, fsComparator> filesystems;
} // anonymous namespace
namespace FileUtil
{
void registerFileSystemForDiskSpaceChecks(const std::string& path)
{
const std::string::size_type lastSlash = path.rfind('/');
assert(path.empty() || lastSlash != std::string::npos);
if (lastSlash != std::string::npos)
{
const std::string dirPath = path.substr(0, lastSlash + 1) + '.';
LOG_INF("Registering filesystem for space checks: [" << dirPath << "]");
std::lock_guard<std::mutex> lock(fsmutex);
struct stat s;
if (stat(dirPath.c_str(), &s) == 0)
{
filesystems.insert(fs(dirPath, s.st_dev));
}
}
}
std::string checkDiskSpaceOnRegisteredFileSystems(const bool cacheLastCheck)
{
static std::chrono::steady_clock::time_point lastCheck;
std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now());
std::lock_guard<std::mutex> lock(fsmutex);
// Don't check more often than once a minute
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck).count() < 60)
return std::string();
if (cacheLastCheck)
lastCheck = now;
for (const auto& i: filesystems)
{
if (!checkDiskSpace(i.path))
{
return i.path;
}
}
return std::string();
}
bool checkDiskSpace(const std::string& path)
{
assert(!path.empty());
#ifndef MOBILEAPP
bool hookResult;
if (UnitBase::get().filterCheckDiskSpace(path, hookResult))
return hookResult;
#endif
// we should be able to run just OK with 5GB
constexpr int64_t ENOUGH_SPACE = int64_t(5)*1024*1024*1024;
#ifdef __linux
struct statfs sfs;
if (statfs(path.c_str(), &sfs) == -1)
return true;
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)
return true;
if (static_cast<double>(sfs.f_bavail) / sfs.f_blocks <= 0.05)
return false;
#elif defined IOS
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
return true;
}
} // namespace FileUtil
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */