// ----------------------------------------------------------------------
// File: Version.cc
// Author: Andreas-Joachim Peters - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2011 CERN/Switzerland *
* *
* 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 3 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, see .*
************************************************************************/
// -----------------------------------------------------------------------
// This file is included source code in XrdMgmOfs.cc to make the code more
// transparent without slowing down the compilation time.
// -----------------------------------------------------------------------
/*----------------------------------------------------------------------------*/
/*
* @brief handles file versioning for fid
*
* @param fid id of the file to version
* @param error object
* @param vid virtual identity of the caller
* @param max_versions the maximum number of version to keep
* @param versionedpath return variable for the full path to the latest version file
* @return SFS_OK if successfully send otherwise SFS_ERROR
*
* Versions are kept in a hidden directory ./owned by root
*/
/*----------------------------------------------------------------------------*/
int
XrdMgmOfs::Version(eos::common::FileId::fileid_t fid,
XrdOucErrInfo& error,
eos::common::VirtualIdentity& vid,
int max_versions,
XrdOucString* versionedpath,
bool simulate)
{
static const char* epname = "version";
EXEC_TIMING_BEGIN("Version");
gOFS->MgmStats.Add("Versioning", vid.uid, vid.gid, 1);
std::shared_ptr fmd;
std::string path;
std::string vpath;
std::string bname;
std::string versionpath;
eos::common::VirtualIdentity fidvid = vid;
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
time_t filectime = 0;
{
eos::common::RWMutexReadLock lock(gOFS->eosViewRWMutex);
try {
fmd = gOFS->eosFileService->getFileMD(fid);
path = gOFS->eosView->getUri(fmd.get()).c_str();
eos::common::Path cPath(path.c_str());
bool noversion;
cPath.DecodeAtomicPath(noversion);
vpath = cPath.GetParentPath();
bname = cPath.GetName();
fidvid.uid = fmd->getCUid();
fidvid.gid = fmd->getCGid();
fidvid.allowed_gids.insert(fidvid.gid);
eos::IFileMD::ctime_t ctime;
fmd->getCTime(ctime);
filectime = (time_t) ctime.tv_sec;
} catch (eos::MDException& e) {
eos_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(),
e.getMessage().str().c_str());
errno = e.getErrno();
std::string errmsg = "translate file id - ";
errmsg = +e.getMessage().str().c_str();
return Emsg(epname, error, errno, errmsg.c_str(), path.c_str());
}
}
if ((fidvid.uid != vid.uid) && (vid.uid)) {
return Emsg(epname, error, EPERM,
"create version - you are not the owner of this file", path.c_str());
}
vpath += ".sys.v#.";
vpath += bname;
versionpath = vpath;
versionpath += "/";
{
char vci[128];
snprintf(vci, sizeof(vci) - 1, "%llu.%08llx", (unsigned long long) filectime,
(unsigned long long) fid);
versionpath += vci;
// return the latest version name
if (versionedpath) {
*versionedpath = versionpath.c_str();
}
}
// Check if .version directory exists, if not create it
struct stat buf;
if (gOFS->_stat(vpath.c_str(), &buf, error, fidvid, 0, 0)) {
eos_info("msg=\"creating version directory\" version-directory=\"%s\"",
vpath.c_str());
if (gOFS->_mkdir(vpath.c_str(), 0, error, fidvid, (const char*) 0, nullptr, true)) {
return Emsg(epname, error, errno, "create version directory", vpath.c_str());
}
if (gOFS->_stat(vpath.c_str(), &buf, error, fidvid, 0, 0)) {
return Emsg(epname, error, errno, "stat version directory", vpath.c_str());
}
// make sure, the owner can write into the version directory
XrdSfsMode chmod_mode = buf.st_mode | S_IRWXU;
if (gOFS->_chmod(vpath.c_str(), chmod_mode, error, rootvid, (const char*) 0)) {
return Emsg(epname, error, errno, "chmod version directory", vpath.c_str());
}
}
// Rename to the version directory target
if ((!gOFS->_stat(vpath.c_str(), &buf, error, fidvid, 0, 0)) && (!simulate) &&
gOFS->_rename(path.c_str(), versionpath.c_str(), error, fidvid, 0, 0, false,
false)) {
return Emsg(epname, error, errno, "version file", path.c_str());
}
// Purge versions according to policy
if (max_versions > 0) {
if (gOFS->PurgeVersion(vpath.c_str(), error, max_versions)) {
return Emsg(epname, error, errno, "purge versions", path.c_str());
}
}
if (!simulate) {
eos_info("msg=\"new version created\" previous-path=\"%s\" version-path=\"%s\"",
path.c_str(), versionpath.c_str());
} else {
eos_info("msg=\"new version simulated\" previous-path=\"%s\" version-path=\"%s\"",
path.c_str(), versionpath.c_str());
}
EXEC_TIMING_END("Versioning");
return SFS_OK;
}
/*----------------------------------------------------------------------------*/
/*
* @brief purge oldest versions exceeding max_versions
*
* @param versiondir directory where versions live
* @param max_versions maximum number of versions to keep
*
* @return SFS_OK if success otherwise SFS_ERROR and might set errno
*
* If max_versions=0 it will remove all versions and the version directory!
* If max_versions=-1 it will read the attribute sys.versioning of the parent
* directory and apply the setting.
* If max_versions=-2 it will read the attribute sys.versioning of the parent
* directory and apply the setting-1 .
*
* The caller needs to have the quota mutex read locked (gQuoatMutex).
*/
/*----------------------------------------------------------------------------*/
int
XrdMgmOfs::PurgeVersion(const char* versiondir,
XrdOucErrInfo& error,
int max_versions)
{
eos::common::VirtualIdentity rootvid = eos::common::VirtualIdentity::Root();
eos_info("version-dir=%s max-versions=%d", versiondir, max_versions);
if (!versiondir) {
errno = EINVAL;
return SFS_ERROR;
}
std::string path = versiondir;
if (max_versions < 0) {
// Indicates that we should read the max version depth from the parent attributes
eos::common::Path cPath(versiondir);
// Get the attributes and call the verify function
eos::IContainerMD::XAttrMap map;
if (gOFS->_attr_ls(cPath.GetParentPath(), error, rootvid, (const char*) 0,
map)) {
return SFS_ERROR;
}
if (map.count("sys.versioning")) {
max_versions = atoi(map["sys.versioning"].c_str());
} else {
return SFS_OK;
}
}
XrdMgmOfsDirectory directory;
int listrc = directory.open(versiondir, rootvid, (const char*) 0);
eos_info("listrc=%d max-version=%d", listrc, max_versions);
if (!listrc && !max_versions) {
// We use the rm -r proc function to do the clean-up to have the recycle
// functionality involved for version directories
ProcCommand Cmd;
//info=eos.rgid=0&eos.ruid=0&mgm.cmd=rm&mgm.option=r&mgm.path=
XrdOucString info = "mgm.cmd=rm&mgm.option=r&mgm.path=";
info += path.c_str();
Cmd.open("/proc/user", info.c_str(), rootvid, &error);
Cmd.close();
if (Cmd.GetRetc()) {
return SFS_ERROR;
} else {
return SFS_OK;
}
}
int success = 0;
std::map version_by_age;
std::vector versions;
if (!listrc) {
const char* val = 0;
time_t now = time(NULL);
while ((val = directory.nextEntry())) {
std::string entryname = val;
if ((entryname == ".") || (entryname == "..")) {
continue;
}
versions.push_back(entryname);
// get age information for this entry
std::string mtime, inode;
eos::common::StringConversion::SplitKeyValue(entryname, mtime, inode, ".");
uint64_t stmtime = strtoull(mtime.c_str(), 0, 10);
ssize_t age = (ssize_t)(now) - stmtime;
if (age > 0) {
version_by_age[age] = entryname;
}
}
const uint64_t age_bins[12] = { 0, 86400 * 1, 86400 * 2, 86400 * 3, 86400 * 4, 86400 * 5, 86400 * 6, 86400 * 7,
86400 * 14, 86400 * 21, 86400 * 28, 0xffffffffffffffff
};
std::map age_map;
std::set keep_set;
for (auto x = version_by_age.rbegin(); x != version_by_age.rend(); x++) {
for (size_t i = 0; i < 12 ; ++i) {
if (EOS_LOGS_DEBUG) {
eos_static_debug("bin %llu", age_bins[i]);
}
if ((x->first >= age_bins[i]) &&
(x->first < age_bins[i + 1])) {
if (EOS_LOGS_DEBUG) {
eos_static_info("map %lu %lu", x->first, i);
}
if (age_map[i] == 0) {
// mark the olderst version in a bin to keep
keep_set.insert(x->second);
}
age_map[i]++;
}
}
}
if (EOS_LOGS_DEBUG) {
for (size_t i = 0 ; i < 12 - 1 ; ++i) {
eos_static_info("age: < %lu days : %lu", age_bins[i + 1] / 86400, age_map[i]);
}
}
// if we have more versions than defined, clean the ones, which can be cleaned
if ((int) versions.size() > max_versions) {
for (size_t i = 0; i < (versions.size() - max_versions); ++i) {
if (keep_set.count(versions[i])) {
continue;
}
std::string deletionpath = path;
deletionpath += "/";
deletionpath += versions[i];
success |= gOFS->_rem(deletionpath.c_str(), error, rootvid, (const char*) 0,
false, false);
}
}
if (success == SFS_OK) {
eos_info("dir=\"%s\" msg=\"purging ok\" old-versions=%d new-versions=%d",
versiondir, versions.size(), max_versions);
} else {
eos_err("dir=\"%s\" msg=\"purging failed\" versions=%d", versiondir,
versions.size());
}
} else {
success = SFS_ERROR;
}
return success;
}