2021-10-21 14:29:15 -05:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* 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/.
|
|
|
|
*/
|
|
|
|
|
2023-05-11 06:47:10 -05:00
|
|
|
#include <config.h>
|
|
|
|
|
2021-10-21 14:29:15 -05:00
|
|
|
#include "QuarantineUtil.hpp"
|
|
|
|
|
|
|
|
#include <Poco/Path.h>
|
|
|
|
#include <Poco/URI.h>
|
|
|
|
#include "ClientSession.hpp"
|
2021-11-18 06:08:14 -06:00
|
|
|
#include "COOLWSD.hpp"
|
2021-10-21 14:29:15 -05:00
|
|
|
#include "DocumentBroker.hpp"
|
2023-05-10 07:19:59 -05:00
|
|
|
#include "FileUtil.hpp"
|
2023-05-13 06:28:20 -05:00
|
|
|
#include "Util.hpp"
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-11 05:48:40 -05:00
|
|
|
#include <chrono>
|
2021-10-21 14:29:15 -05:00
|
|
|
#include <common/Common.hpp>
|
|
|
|
#include <common/StringVector.hpp>
|
|
|
|
#include <common/Log.hpp>
|
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
std::string Quarantine::QuarantinePath;
|
2023-05-11 06:47:33 -05:00
|
|
|
std::unordered_map<std::string, std::vector<std::string>> Quarantine::QuarantineMap;
|
2023-05-08 06:57:30 -05:00
|
|
|
|
2023-05-13 12:30:41 -05:00
|
|
|
Quarantine::Quarantine(DocumentBroker& docBroker, const std::string& docName)
|
2023-05-11 05:58:15 -05:00
|
|
|
: _docKey(docBroker.getDocKey())
|
2023-05-13 12:30:41 -05:00
|
|
|
, _docName(docName)
|
2023-05-14 09:22:26 -05:00
|
|
|
, _quarantinedFilename('_' + std::to_string(docBroker.getPid()) + '_' + docBroker.getDocKey() +
|
|
|
|
'_' + _docName)
|
2023-05-11 06:00:39 -05:00
|
|
|
, _maxSizeBytes(COOLWSD::getConfigValue<std::size_t>("quarantine_files.limit_dir_size_mb", 0) *
|
|
|
|
1024 * 1024)
|
2023-05-11 06:07:16 -05:00
|
|
|
, _maxAgeSecs(COOLWSD::getConfigValue<std::size_t>("quarantine_files.expiry_min", 30) * 60)
|
2023-05-11 06:36:12 -05:00
|
|
|
, _maxVersions(std::max(
|
|
|
|
COOLWSD::getConfigValue<std::size_t>("quarantine_files.max_versions_to_maintain", 2),
|
|
|
|
1UL))
|
2023-05-11 05:48:40 -05:00
|
|
|
{
|
2023-05-13 12:30:41 -05:00
|
|
|
LOG_DBG("Quarantine ctor for [" << _docKey << ']');
|
2023-05-11 05:48:40 -05:00
|
|
|
}
|
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
void Quarantine::initialize(const std::string& path)
|
2021-10-21 14:29:15 -05:00
|
|
|
{
|
2023-05-08 07:11:01 -05:00
|
|
|
if (!COOLWSD::getConfigValue<bool>("quarantine_files[@enable]", false) ||
|
|
|
|
!QuarantinePath.empty())
|
2023-05-08 06:57:30 -05:00
|
|
|
return;
|
2023-05-07 17:07:48 -05:00
|
|
|
|
2023-05-11 06:47:33 -05:00
|
|
|
QuarantineMap.clear();
|
2021-11-03 02:08:22 -05:00
|
|
|
|
2023-05-11 07:34:50 -05:00
|
|
|
std::vector<std::string> files;
|
|
|
|
Poco::File(path).list(files);
|
2021-11-03 02:08:22 -05:00
|
|
|
|
2023-05-11 07:34:50 -05:00
|
|
|
//FIXME: This is lexicographical and won't sort timestamps correctly.
|
2023-05-08 06:57:30 -05:00
|
|
|
std::sort(files.begin(), files.end());
|
2023-05-11 07:34:50 -05:00
|
|
|
|
|
|
|
std::vector<StringToken> tokens;
|
2023-05-08 06:57:30 -05:00
|
|
|
for (const auto& file : files)
|
|
|
|
{
|
|
|
|
StringVector::tokenize(file.c_str(), file.size(), '_', tokens);
|
2023-05-11 07:34:50 -05:00
|
|
|
if (tokens.size() > 2)
|
|
|
|
{
|
|
|
|
QuarantineMap[file.substr(tokens[2]._index)].emplace_back(path + file);
|
|
|
|
}
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
tokens.clear();
|
|
|
|
}
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
QuarantinePath = path;
|
|
|
|
}
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
void Quarantine::removeQuarantine()
|
|
|
|
{
|
|
|
|
if (!isQuarantineEnabled())
|
|
|
|
return;
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
FileUtil::removeFile(QuarantinePath, true);
|
|
|
|
}
|
2023-05-07 17:07:48 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
// returns quarantine directory size in bytes
|
|
|
|
// files with hardlink count of more than 1 is not counted
|
|
|
|
// because they are originally stored in jails
|
|
|
|
std::size_t Quarantine::quarantineSize()
|
|
|
|
{
|
|
|
|
if (!isQuarantineEnabled())
|
|
|
|
return 0;
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
std::vector<std::string> files;
|
|
|
|
Poco::File(QuarantinePath).list(files);
|
|
|
|
std::size_t size = 0;
|
|
|
|
for (const auto& file : files)
|
2021-10-21 14:29:15 -05:00
|
|
|
{
|
2023-05-08 06:57:30 -05:00
|
|
|
FileUtil::Stat f(QuarantinePath + file);
|
2021-11-03 02:08:22 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
if (f.hardLinkCount() == 1)
|
|
|
|
size += f.size();
|
2021-10-21 14:29:15 -05:00
|
|
|
}
|
2023-05-08 06:57:30 -05:00
|
|
|
return size;
|
|
|
|
}
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
void Quarantine::makeQuarantineSpace()
|
|
|
|
{
|
|
|
|
if (!isQuarantineEnabled())
|
|
|
|
return;
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
std::vector<std::string> files;
|
|
|
|
Poco::File(QuarantinePath).list(files);
|
2021-11-03 02:08:22 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
std::sort(files.begin(), files.end());
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
const auto timeNow = std::chrono::system_clock::now();
|
|
|
|
const auto ts =
|
|
|
|
std::chrono::duration_cast<std::chrono::seconds>(timeNow.time_since_epoch()).count();
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
std::size_t currentSize = quarantineSize();
|
|
|
|
auto index = files.begin();
|
|
|
|
while (index != files.end() && !files.empty())
|
|
|
|
{
|
2023-05-13 06:28:20 -05:00
|
|
|
bool purge = currentSize >= _maxSizeBytes;
|
|
|
|
if (!purge)
|
|
|
|
{
|
|
|
|
const auto pair = Util::u64FromString(Util::split(*index, '_').first);
|
|
|
|
const auto age = (ts - pair.first);
|
|
|
|
if (!pair.second || age > _maxAgeSecs)
|
|
|
|
{
|
|
|
|
LOG_TRC("Will remove quarantined file [" << *index << "] which is " << age
|
|
|
|
<< " secs old (max " << _maxAgeSecs
|
|
|
|
<< " secs)");
|
|
|
|
purge = true;
|
|
|
|
}
|
|
|
|
}
|
2021-11-10 14:24:01 -06:00
|
|
|
|
2023-05-13 06:28:20 -05:00
|
|
|
if (purge)
|
2021-10-21 14:29:15 -05:00
|
|
|
{
|
2023-05-13 06:28:20 -05:00
|
|
|
FileUtil::Stat file(QuarantinePath + *index);
|
|
|
|
LOG_TRC("Removing quarantined file ["
|
|
|
|
<< *index << "] (" << file.size() << " bytes). Current quarantine size: "
|
|
|
|
<< currentSize << " (max " << _maxSizeBytes << " bytes)");
|
2023-05-08 06:57:30 -05:00
|
|
|
currentSize -= file.size();
|
|
|
|
FileUtil::removeFile(QuarantinePath + *index, true);
|
|
|
|
files.erase(index);
|
2021-10-21 14:29:15 -05:00
|
|
|
}
|
2023-05-08 06:57:30 -05:00
|
|
|
else
|
|
|
|
index++;
|
2021-10-21 14:29:15 -05:00
|
|
|
}
|
2023-05-08 06:57:30 -05:00
|
|
|
}
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-11 05:46:16 -05:00
|
|
|
void Quarantine::clearOldQuarantineVersions()
|
2023-05-08 06:57:30 -05:00
|
|
|
{
|
|
|
|
if (!isQuarantineEnabled())
|
|
|
|
return;
|
|
|
|
|
2023-05-13 06:43:24 -05:00
|
|
|
auto& container = QuarantineMap[_docKey];
|
|
|
|
if (container.size() > _maxVersions)
|
2021-10-21 14:29:15 -05:00
|
|
|
{
|
2023-05-13 06:43:24 -05:00
|
|
|
const std::size_t excess = container.size() - _maxVersions;
|
|
|
|
LOG_TRC("Removing " << excess << " excess quarantined file versions for [" << _docKey
|
|
|
|
<< ']');
|
|
|
|
for (std::size_t i = 0; i < excess; ++i)
|
|
|
|
{
|
|
|
|
const std::string& path = container[i];
|
|
|
|
LOG_TRC("Removing excess quarantined file version #" << (i + 1) << " [" << path
|
|
|
|
<< "] for [" << _docKey << ']');
|
|
|
|
|
|
|
|
FileUtil::removeFile(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// And remove them from the container.
|
|
|
|
container.erase(container.begin(), container.begin() + excess);
|
2021-10-21 14:29:15 -05:00
|
|
|
}
|
2023-05-08 06:57:30 -05:00
|
|
|
}
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-10 07:19:59 -05:00
|
|
|
bool Quarantine::quarantineFile(const std::string& docPath)
|
2023-05-08 06:57:30 -05:00
|
|
|
{
|
|
|
|
if (!isQuarantineEnabled())
|
|
|
|
return false;
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
const auto timeNow = std::chrono::system_clock::now();
|
2023-05-11 05:57:31 -05:00
|
|
|
const auto ts =
|
|
|
|
std::chrono::duration_cast<std::chrono::seconds>(timeNow.time_since_epoch()).count();
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-14 09:22:26 -05:00
|
|
|
const std::string linkedFilePath = QuarantinePath + std::to_string(ts) + _quarantinedFilename;
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-11 06:47:33 -05:00
|
|
|
auto& fileList = QuarantineMap[_docKey];
|
2023-05-08 06:57:30 -05:00
|
|
|
if (!fileList.empty())
|
|
|
|
{
|
2023-05-10 07:19:59 -05:00
|
|
|
FileUtil::Stat sourceStat(docPath);
|
2023-05-08 06:57:30 -05:00
|
|
|
FileUtil::Stat lastFileStat(fileList[fileList.size() - 1]);
|
2021-11-23 08:13:59 -06:00
|
|
|
|
2023-05-10 07:19:59 -05:00
|
|
|
if (lastFileStat.isIdenticalTo(sourceStat))
|
2023-05-08 06:57:30 -05:00
|
|
|
{
|
2023-05-10 07:19:59 -05:00
|
|
|
LOG_INF("Quarantining of file ["
|
|
|
|
<< docPath << "] to [" << linkedFilePath
|
|
|
|
<< "] is skipped because this file version is already quarantined");
|
2023-05-08 06:57:30 -05:00
|
|
|
return false;
|
2021-11-23 08:13:59 -06:00
|
|
|
}
|
2023-05-08 06:57:30 -05:00
|
|
|
}
|
2021-11-23 08:13:59 -06:00
|
|
|
|
2023-05-08 06:57:30 -05:00
|
|
|
makeQuarantineSpace();
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-10 07:19:59 -05:00
|
|
|
if (FileUtil::linkOrCopyFile(docPath, linkedFilePath))
|
2023-05-08 06:57:30 -05:00
|
|
|
{
|
|
|
|
fileList.emplace_back(linkedFilePath);
|
2023-05-11 05:46:16 -05:00
|
|
|
clearOldQuarantineVersions();
|
2023-05-08 06:57:30 -05:00
|
|
|
makeQuarantineSpace();
|
2021-10-21 14:29:15 -05:00
|
|
|
|
2023-05-10 07:19:59 -05:00
|
|
|
LOG_INF("Quarantined [" << docPath << "] to [" << linkedFilePath << ']');
|
2023-05-08 06:57:30 -05:00
|
|
|
return true;
|
|
|
|
}
|
2023-04-21 18:28:23 -05:00
|
|
|
|
2023-05-10 07:19:59 -05:00
|
|
|
LOG_ERR("Quarantining of file [" << docPath << "] to [" << linkedFilePath << "] failed");
|
2023-05-08 06:57:30 -05:00
|
|
|
return false;
|
|
|
|
}
|
2023-04-21 18:28:23 -05:00
|
|
|
|
2023-05-08 07:11:01 -05:00
|
|
|
void Quarantine::removeQuarantinedFiles()
|
2023-05-08 06:57:30 -05:00
|
|
|
{
|
2023-05-13 08:19:32 -05:00
|
|
|
LOG_DBG("Removing all quarantined files for [" << _docKey << ']');
|
2023-05-11 06:47:33 -05:00
|
|
|
for (const auto& file : QuarantineMap[_docKey])
|
2023-05-08 06:57:30 -05:00
|
|
|
{
|
2023-05-13 08:19:32 -05:00
|
|
|
LOG_TRC("Removing quarantined file [" << file << "] for [" << _docKey << ']');
|
2023-05-08 06:57:30 -05:00
|
|
|
FileUtil::removeFile(file);
|
2023-04-21 18:28:23 -05:00
|
|
|
}
|
2023-05-08 06:57:30 -05:00
|
|
|
|
2023-05-11 06:47:33 -05:00
|
|
|
QuarantineMap.erase(_docKey);
|
2021-11-04 08:09:27 -05:00
|
|
|
}
|