/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2022 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 .*
************************************************************************/
/************************************************************************
* @file XattrLock.hh *
* @brief Class to handle locks in extended attributes *
************************************************************************/
#pragma once
#include
#include
#include
#include "mgm/Namespace.hh"
#include "common/Constants.hh"
#include "namespace/Prefetcher.hh"
EOSMGMNAMESPACE_BEGIN
struct XattrLock {
public:
XattrLock() : valid(false), expires(0), isshared(false), isfuseopen(false) {}
XattrLock(eos::IFileMD::XAttrMap& attr) : xattr(attr), valid(false), expires(0), isshared(false), isfuseopen(false) {
auto l = xattr.find(eos::common::EOS_APP_LOCK_ATTR);
if (l != xattr.end()) {
Parse(l->second.c_str());
}
// check fuse commit state
std::string fs = attr["sys.fusex.state"];
if (!fs.empty()) {
isfuseopen = fs.back() != '|';
}
}
virtual ~XattrLock() {}
void Parse(const char* l)
{
valid = false;
std::map kvmap;
if (eos::common::StringConversion::GetKeyValueMap(l,
kvmap)) {
if (!kvmap.count("expires") ||
!kvmap.count("type") ||
!kvmap.count("owner")) {
valid = false;
} else {
expires = atoi(kvmap["expires"].c_str());
isshared = (kvmap["type"] == "shared");
owner = (kvmap["owner"]);
valid = true;
}
}
}
bool foreignLock(eos::common::VirtualIdentity& vid, bool isrw)
{
time_t now = time(NULL);
if (!valid) {
return false;
}
ssize_t lifetime = expires - now;
if (isfuseopen) {
// file is in open state
return false;
}
if (lifetime > 0) {
// check the lock is still within its lifetime
if (lifetime > 604800) {
// that is an illegal attribute and we ignore it
return false;
}
if (!isrw && isshared) {
// if this is read access on a shared lock, we let it pass
return false;
}
// we can have full match or a wildcard ownership for the user or app name, both with wildcard does not make sense
std::string lockowner = std::string(vid.uid_string.c_str()) + std::string(":")
+ std::string(vid.app.c_str());
std::string lockownerwn = std::string("*") + std::string(":") + std::string(
vid.app.c_str());
std::string lockownerwa = std::string(vid.uid_string.c_str()) + std::string(":")
+ std::string("*");
if ((lockowner != owner) &&
(lockownerwn != owner) &&
(lockownerwa != owner)) {
// this is not us - this is the only case we prevent access
return true;
}
}
return false;
}
bool Lock(const char* path, bool shrd, time_t lifetime,
eos::common::VirtualIdentity& vid, bool userwildcard, bool appwildcard)
{
errno = 0;
XrdOucErrInfo error;
XrdOucString value;
eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);
eos::IFileMD::IFileMDWriteLockerPtr fdLock;
try {
fdLock = gOFS->eosView->getFileWriteLocked(path);
} catch (eos::MDException &e) {
errno = e.getErrno();
eos_static_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(),
e.getMessage().str().c_str());
return false;
}
if (!gOFS->_attr_get(path, error, vid, "", eos::common::EOS_APP_LOCK_ATTR,
value)) {
// parse the lock
Parse(value.c_str());
if (foreignLock(vid, true)) {
errno = EBUSY;
// we cannot lock this
return false;
}
}
if (lifetime > (604800)) {
// application locks no longer than a week
errno = EINVAL;
return false;
}
if (userwildcard && appwildcard) {
// cannot have both with wildcard
errno = EINVAL;
return false;
}
// define expires
expires = time(NULL) + lifetime;
// define the owner
owner = (userwildcard ? std::string("*") : std::string(vid.uid_string.c_str()))
+ std::string(":") + (appwildcard ? std::string("*") : std::string(
vid.app.c_str()));
// define shared
isshared = shrd;
// store the lock attribute
if (gOFS->_attr_set(path, error, vid, "", eos::common::EOS_APP_LOCK_ATTR,
Value().c_str())) {
return false;
}
return true;
}
bool Unlock(const char* path, eos::common::VirtualIdentity& vid)
{
errno = 0;
XrdOucErrInfo error;
XrdOucString value;
eos::Prefetcher::prefetchFileMDAndWait(gOFS->eosView, path);
eos::IFileMD::IFileMDWriteLockerPtr fdLock;
try {
fdLock = gOFS->eosView->getFileWriteLocked(path);
} catch (eos::MDException &e) {
errno = e.getErrno();
eos_static_debug("msg=\"exception\" ec=%d emsg=\"%s\"\n", e.getErrno(),
e.getMessage().str().c_str());
return false;
}
if (!gOFS->_attr_get(path, error, vid, "", eos::common::EOS_APP_LOCK_ATTR,
value)) {
// parse the lock
Parse(value.c_str());
if (foreignLock(vid, true)) {
errno = EBUSY;
// we cannot unlock this
return false;
}
}
// remove the lock attribute
if (gOFS->_attr_rem(path, error, vid, "", eos::common::EOS_APP_LOCK_ATTR)) {
return false;
}
return true;
}
std::string Dump()
{
std::ostringstream ss;
ss << "valid:" << valid << " expires:" << expires << " shared:" << isshared <<
std::endl;
return ss.str();
}
std::string Value()
{
std::ostringstream ss;
ss << "expires:" << expires;
if (isshared) {
ss << ",type:" << "shared";
} else {
ss << ",type:" << "exclusive";
}
ss << ",owner:" << owner;
return ss.str();
}
private:
eos::IFileMD::XAttrMap xattr;
bool valid;
bool isfuseopen;
bool isshared;
time_t expires;
std::string owner;
};
EOSMGMNAMESPACE_END