/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2016 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 .*
************************************************************************/
#include
#include
#include "common/Logging.hh"
#include "common/StacktraceHere.hh"
#include "common/StringConversion.hh"
#include "namespace/ns_quarkdb/FileMD.hh"
#include "namespace/ns_quarkdb/persistency/Serialization.hh"
#include "namespace/interface/IContainerMD.hh"
#include "namespace/interface/IFileMDSvc.hh"
#include "namespace/utils/DataHelper.hh"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include
#define DBG(message) std::cerr << __FILE__ << ":" << __LINE__ << " -- " << #message << " = " << message << std::endl
EOSNSNAMESPACE_BEGIN
//------------------------------------------------------------------------------
// Empty constructor
//------------------------------------------------------------------------------
QuarkFileMD::QuarkFileMD()
{
pFileMDSvc = nullptr;
}
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
QuarkFileMD::QuarkFileMD(IFileMD::id_t id, IFileMDSvc* fileMDSvc):
pFileMDSvc(fileMDSvc)
{
mFile.set_id(id);
mClock = std::chrono::high_resolution_clock::now().time_since_epoch().count();
}
//------------------------------------------------------------------------------
// Virtual copy constructor
//------------------------------------------------------------------------------
QuarkFileMD*
QuarkFileMD::clone() const
{
return runWriteOp([this]() { return new QuarkFileMD(*this); });
}
//------------------------------------------------------------------------------
// Copy constructor
//------------------------------------------------------------------------------
QuarkFileMD::QuarkFileMD(const QuarkFileMD& other)
{
*this = other;
}
//------------------------------------------------------------------------------
// Assignment operator
//------------------------------------------------------------------------------
QuarkFileMD&
QuarkFileMD::operator = (const QuarkFileMD& other)
{
return runWriteOp([this, &other]() -> QuarkFileMD& {
mFile = other.mFile;
mClock = other.mClock;
pFileMDSvc = 0;
return *this;
});
}
//------------------------------------------------------------------------------
// Set name
//------------------------------------------------------------------------------
void QuarkFileMD::setName(const std::string& name)
{
if (name.find('/') != std::string::npos) {
eos_static_crit("Detected slashes in filename: %s",
eos::common::getStacktrace().c_str());
throw_mdexception(EINVAL, "Bug, detected slashes in file name: " << name);
}
runWriteOp([this, &name]() { mFile.set_name(name); });
}
//------------------------------------------------------------------------------
// Add location
//------------------------------------------------------------------------------
void
QuarkFileMD::addLocation(location_t location)
{
this->runWriteOp([this, location]() {
if (hasLocationNoLock(location)) {
return;
}
mFile.add_locations(location);
});
IFileMDChangeListener::Event e(this, IFileMDChangeListener::LocationAdded,
location);
pFileMDSvc->notifyListeners(&e);
}
//------------------------------------------------------------------------------
// Remove location that was previously unlinked
//------------------------------------------------------------------------------
void
QuarkFileMD::removeLocation(location_t location)
{
bool locationRemoved = false;
{
this->runWriteOp([this, &locationRemoved, location]() {
for (auto it = mFile.mutable_unlink_locations()->cbegin();
it != mFile.mutable_unlink_locations()->cend(); ++it) {
if (*it == location) {
it = mFile.mutable_unlink_locations()->erase(it);
locationRemoved = true;
break;
}
}
});
}
if(locationRemoved){
IFileMDChangeListener::Event
e(this, IFileMDChangeListener::LocationRemoved, location);
pFileMDSvc->notifyListeners(&e);
}
}
//------------------------------------------------------------------------------
// Remove all locations that were previously unlinked
//------------------------------------------------------------------------------
void
QuarkFileMD::removeAllLocations()
{
bool stop = false;
while (!stop) {
std::optional location;
{
stop = runReadOp([this, &location]() {
auto it = mFile.unlink_locations().cbegin();
if (it == mFile.unlink_locations().cend()) {
return true;
}
location = *it;
return false;
});
}
if(location) {
removeLocation(*location);
}
}
}
//------------------------------------------------------------------------------
// Unlink location
//------------------------------------------------------------------------------
void
QuarkFileMD::unlinkLocation(location_t location)
{
{
this->runWriteOp([this, location]() {
for (auto it = mFile.mutable_locations()->cbegin();
it != mFile.mutable_locations()->cend(); ++it) {
if (*it == location) {
// If location is already unlink, skip adding it
if (!hasUnlinkedLocationNoLock(location)) {
mFile.add_unlink_locations(*it);
}
it = mFile.mutable_locations()->erase(it);
break;
}
}
});
}
IFileMDChangeListener::Event
e(this, IFileMDChangeListener::LocationUnlinked, location);
pFileMDSvc->notifyListeners(&e);
}
//------------------------------------------------------------------------------
// Unlink all locations
//------------------------------------------------------------------------------
void
QuarkFileMD::unlinkAllLocations()
{
while (true) {
std::optional location;
this->runWriteOp([this, &location]() {
auto it = mFile.locations().cbegin();
if (it == mFile.locations().cend()) {
return;
}
location = *it;
});
if(location) {
unlinkLocation(*location);
} else {
return;
}
}
}
//------------------------------------------------------------------------
// Env Representation
//------------------------------------------------------------------------
void
QuarkFileMD::getEnv(std::string& env, bool escapeAnd)
{
runReadOp([this, &env, escapeAnd] {
env = "";
std::ostringstream oss;
std::string saveName = mFile.name();
if (escapeAnd) {
if (!saveName.empty()) {
saveName = eos::common::StringConversion::SealXrdPath(saveName);
}
}
ctime_t ctime;
ctime_t mtime;
(void)getCTimeNoLock(ctime);
(void)getMTimeNoLock(mtime);
oss << "name=" << saveName << "&id=" << mFile.id()
<< "&ctime=" << ctime.tv_sec << "&ctime_ns=" << ctime.tv_nsec
<< "&mtime=" << mtime.tv_sec << "&mtime_ns=" << mtime.tv_nsec
<< "&size=" << mFile.size() << "&cid=" << mFile.cont_id()
<< "&uid=" << mFile.uid() << "&gid=" << mFile.gid()
<< "&lid=" << mFile.layout_id() << "&flags=" << mFile.flags()
<< "&link=" << mFile.link_name();
env += oss.str();
env += "&location=";
char locs[16];
for (const auto& elem : mFile.locations()) {
snprintf(static_cast(locs), sizeof(locs), "%u", elem);
env += static_cast(locs);
env += ",";
}
for (const auto& elem : mFile.unlink_locations()) {
snprintf(static_cast(locs), sizeof(locs), "!%u", elem);
env += static_cast(locs);
env += ",";
}
env += "&checksum=";
uint8_t size = mFile.checksum().size();
for (uint8_t i = 0; i < size; i++) {
char hx[3];
hx[0] = 0;
snprintf(static_cast(hx), sizeof(hx), "%02x",
*(unsigned char*)(mFile.checksum().data() + i));
env += static_cast(hx);
}
});
}
//------------------------------------------------------------------------------
// Serialize the object to a std::string buffer
//------------------------------------------------------------------------------
void
QuarkFileMD::serialize(eos::Buffer& buffer)
{
runReadOp([this, &buffer]() {
// Increase clock to mark that metadata file has suffered updates
mClock =
std::chrono::high_resolution_clock::now().time_since_epoch().count();
// Align the buffer to 4 bytes to efficiently compute the checksum
#if GOOGLE_PROTOBUF_VERSION < 3004000
size_t obj_size = mFile.ByteSize();
#else
size_t obj_size = mFile.ByteSizeLong();
#endif
uint32_t align_size = (obj_size + 3) >> 2 << 2;
size_t sz = sizeof(align_size);
size_t msg_size = align_size + 2 * sz;
buffer.setSize(msg_size);
// Write the checksum value, size of the raw protobuf object and then the
// actual protobuf object serialized
const char* ptr = buffer.getDataPtr() + 2 * sz;
google::protobuf::io::ArrayOutputStream aos((void*)ptr, align_size);
if (!mFile.SerializeToZeroCopyStream(&aos)) {
MDException ex(EIO);
ex.getMessage() << "Failed while serializing buffer";
throw ex;
}
// Compute the CRC32C checksum
uint32_t cksum = DataHelper::computeCRC32C((void*)ptr, align_size);
cksum = DataHelper::finalizeCRC32C(cksum);
// Point to the beginning to fill in the checksum and size of useful data
ptr = buffer.getDataPtr();
(void)memcpy((void*)ptr, &cksum, sz);
ptr += sz;
(void)memcpy((void*)ptr, &obj_size, sz);
});
}
//------------------------------------------------------------------------------
// Initialize from protobuf contents
//------------------------------------------------------------------------------
void
QuarkFileMD::initialize(eos::ns::FileMdProto&& proto)
{
runWriteOp(
[this, prot = std::move(proto)]() mutable { mFile = std::move(prot); });
}
//------------------------------------------------------------------------------
// Deserialize from buffer
//------------------------------------------------------------------------------
void
QuarkFileMD::deserialize(const eos::Buffer& buffer)
{
runWriteOp(
[this, &buffer]() { Serialization::deserializeFile(buffer, mFile); });
}
//----------------------------------------------------------------------------
// Get reference to underlying protobuf object
//----------------------------------------------------------------------------
const eos::ns::FileMdProto&
QuarkFileMD::getProto() const
{
return mFile;
}
//------------------------------------------------------------------------------
// Set size - 48 bytes will be used
//------------------------------------------------------------------------------
void
QuarkFileMD::setSize(uint64_t size)
{
int64_t sizeChange = 0;
this->runWriteOp([this, size, &sizeChange]() {
sizeChange = (size & 0x0000ffffffffffff) - mFile.size();
mFile.set_size(size & 0x0000ffffffffffff);
});
IFileMDChangeListener::Event e(this, IFileMDChangeListener::SizeChange, 0,
sizeChange);
pFileMDSvc->notifyListeners(&e);
}
//------------------------------------------------------------------------------
// Get creation time, no lock
//------------------------------------------------------------------------------
void
QuarkFileMD::getCTimeNoLock(ctime_t& ctime) const
{
if (mFile.ctime().length() == sizeof(ctime_t)) {
(void) memcpy(&ctime, mFile.ctime().data(), sizeof(ctime_t));
} else {
(void) memset(&ctime, 0, sizeof(ctime_t));
}
}
//------------------------------------------------------------------------------
// Get creation time
//------------------------------------------------------------------------------
void
QuarkFileMD::getCTime(ctime_t& ctime) const
{
return runReadOp([this, &ctime]() { return getCTimeNoLock(ctime); });
}
//------------------------------------------------------------------------------
// Set creation time
//------------------------------------------------------------------------------
void
QuarkFileMD::setCTime(ctime_t ctime)
{
runWriteOp([this, ctime]() { mFile.set_ctime(&ctime, sizeof(ctime)); });
}
//----------------------------------------------------------------------------
// Set creation time to now
//----------------------------------------------------------------------------
void
QuarkFileMD::setCTimeNow()
{
struct timespec tnow;
#ifdef __APPLE__
struct timeval tv;
gettimeofday(&tv, 0);
tnow.tv_sec = tv.tv_sec;
tnow.tv_nsec = tv.tv_usec * 1000;
#else
clock_gettime(CLOCK_REALTIME, &tnow);
#endif
setCTime(tnow);
}
//------------------------------------------------------------------------------
// Get modification time, no locks
//------------------------------------------------------------------------------
void
QuarkFileMD::getMTimeNoLock(ctime_t& mtime) const
{
if (mFile.mtime().length() == sizeof(ctime_t)) {
(void) memcpy(&mtime, mFile.mtime().data(), sizeof(ctime_t));
} else {
(void) memset(&mtime, 0, sizeof(ctime_t));
}
}
//------------------------------------------------------------------------------
// Get modification time
//------------------------------------------------------------------------------
void
QuarkFileMD::getMTime(ctime_t& mtime) const
{
return runReadOp([this, &mtime]() { return getMTimeNoLock(mtime); });
}
//------------------------------------------------------------------------------
// Set modification time
//------------------------------------------------------------------------------
void
QuarkFileMD::setMTime(ctime_t mtime)
{
runWriteOp([this, mtime]() { mFile.set_mtime(&mtime, sizeof(mtime)); });
}
//------------------------------------------------------------------------------
// Set modification time to now
//------------------------------------------------------------------------------
void
QuarkFileMD::setMTimeNow()
{
struct timespec tnow;
#ifdef __APPLE__
struct timeval tv;
gettimeofday(&tv, 0);
tnow.tv_sec = tv.tv_sec;
tnow.tv_nsec = tv.tv_usec * 1000;
#else
clock_gettime(CLOCK_REALTIME, &tnow);
#endif
setMTime(tnow);
struct timespec default_ts = {0, 0};
setSyncTime(default_ts);
}
//------------------------------------------------------------------------------
// Get access time, no locks
//------------------------------------------------------------------------------
void
QuarkFileMD::getATimeNoLock(ctime_t& atime) const
{
if (mFile.atime().length() == sizeof(ctime_t)) {
(void) memcpy(&atime, mFile.atime().data(), sizeof(ctime_t));
} else {
(void) memset(&atime, 0, sizeof(ctime_t));
}
}
//------------------------------------------------------------------------------
// Get access time
//------------------------------------------------------------------------------
void
QuarkFileMD::getATime(ctime_t& atime) const
{
runReadOp([this,&atime] {
getATimeNoLock(atime);
});
}
//------------------------------------------------------------------------------
// Set access time
//------------------------------------------------------------------------------
void
QuarkFileMD::setATime(ctime_t atime)
{
runWriteOp([this,atime](){
mFile.set_atime(&atime, sizeof(atime));
});
}
//------------------------------------------------------------------------------
// Set access time to now
//------------------------------------------------------------------------------
bool
QuarkFileMD::setATimeNow(uint64_t olderthan)
{
struct timespec tnow;
struct timespec atime;
#ifdef __APPLE__
struct timeval tv;
gettimeofday(&tv, 0);
tnow.tv_sec = tv.tv_sec;
tnow.tv_nsec = tv.tv_usec * 1000;
#else
clock_gettime(CLOCK_REALTIME, &tnow);
#endif
getATime(atime);
// only set the atime if it is older than olderthan
if (((tnow.tv_sec - atime.tv_sec) >= (time_t) olderthan)) {
setATime(tnow);
return true;
} else {
return false;
}
}
/* SyncTime: whenever the file is changed, SyncTime becomes MTime. It is only
* when mtime is set explicitely that the two diverge. Hence. the logic
* here is that if SyncTime is 0, use MTime. And reset SyncTime to
* zero when MTime is set to now (thus logically setting them both).
*/
//------------------------------------------------------------------------------
// Get sync time, no lock
//------------------------------------------------------------------------------
void
QuarkFileMD::getSyncTimeNoLock(ctime_t& stime) const
{
(void) memcpy(&stime, mFile.stime().data(), sizeof(stime));
if (stime.tv_sec == 0) { /* fall back to mtime if default */
(void) memcpy(&stime, mFile.mtime().data(), sizeof(stime));
}
}
//------------------------------------------------------------------------------
// Get sync time
//------------------------------------------------------------------------------
void
QuarkFileMD::getSyncTime(ctime_t& stime) const
{
runReadOp([this, &stime]() { getSyncTimeNoLock(stime); });
}
//------------------------------------------------------------------------------
// Set sync time
//------------------------------------------------------------------------------
void
QuarkFileMD::setSyncTime(ctime_t stime)
{
runWriteOp([this, stime]() { mFile.set_stime(&stime, sizeof(stime)); });
}
//------------------------------------------------------------------------------
// Set sync time to now
//------------------------------------------------------------------------------
void
QuarkFileMD::setSyncTimeNow()
{
struct timespec tnow;
clock_gettime(CLOCK_REALTIME, &tnow);
setSyncTime(tnow);
}
//------------------------------------------------------------------------------
// Get map copy of the extended attributes
//------------------------------------------------------------------------------
eos::IFileMD::XAttrMap
QuarkFileMD::getAttributes() const
{
return runReadOp([this]() {
std::map xattrs;
for (const auto& elem : mFile.xattrs()) {
xattrs.insert(elem);
}
return xattrs;
});
}
//------------------------------------------------------------------------------
// Test the unlinked location
//------------------------------------------------------------------------------
bool QuarkFileMD::hasUnlinkedLocation(IFileMD::location_t location)
{
return this->runReadOp(
[this, location]() { return hasUnlinkedLocationNoLock(location); });
}
//------------------------------------------------------------------------------
// Test the unlinked location, no locks
//------------------------------------------------------------------------------
bool QuarkFileMD::hasUnlinkedLocationNoLock(location_t location) const
{
for (int i = 0; i < mFile.unlink_locations_size(); ++i) {
if (mFile.unlink_locations()[i] == location) {
return true;
}
}
return false;
}
EOSNSNAMESPACE_END