diff --git a/src/IOCoordinator.cpp b/src/IOCoordinator.cpp index f997adda9..b8ce85642 100755 --- a/src/IOCoordinator.cpp +++ b/src/IOCoordinator.cpp @@ -215,7 +215,7 @@ int IOCoordinator::read(const char *filename, uint8_t *data, off_t offset, size_ const auto &jit = journalFDs.find(object.key); // if this is the first object, the offset to start reading at is offset - object->offset - off_t thisOffset = (object.offset == 0 ? offset - object.offset : 0); + off_t thisOffset = (object.offset <= offset ? offset - object.offset : 0); // if this is the last object, the length of the read is length - count, // otherwise it is the length of the object @@ -345,32 +345,16 @@ int IOCoordinator::append(const char *filename, const uint8_t *data, size_t leng return count; } +// TODO: might need to support more open flags, ex: O_EXCL int IOCoordinator::open(const char *filename, int openmode, struct stat *out) { - - if (openmode & O_CREAT) - { - MetadataFile meta(filename); - return meta.stat(out); - } - else - { - MetadataFile meta(filename, MetadataFile::no_create_t()); - return meta.stat(out); - } + ScopedReadLock s(this, filename); -#if 0 - int fd, err; + MetadataFile meta(filename, MetadataFile::no_create_t()); - /* create all subdirs if necessary. We don't care if directories actually get created. */ - if (openmode & O_CREAT) { - bf::path p(filename); - boost::system::error_code ec; - bf::create_directories(p.parent_path(), ec); - } - OPEN(filename, openmode); - return fstat(fd, out); -#endif + if ((openmode & O_CREAT) && !meta.exists()) + replicator->updateMetadata(filename, meta); // this will end up creating filename + return meta.stat(out); } int IOCoordinator::listDirectory(const char *filename, vector *listing) @@ -402,9 +386,83 @@ int IOCoordinator::stat(const char *path, struct stat *out) return meta.stat(out); } -int IOCoordinator::truncate(const char *path, size_t newsize) +int IOCoordinator::truncate(const char *path, size_t newSize) { - return ::truncate(path, newsize); + /* + grab the write lock. + get the relevant metadata. + truncate the metadata. + tell replicator to write the new metadata + release the lock + + tell replicator to delete all of the objects that no longer exist & their journal files + tell cache they were deleted + tell synchronizer they were deleted + */ + + synchronizer = Synchronizer::get(); // need to init sync here to break circular dependency... + + int err; + ScopedWriteLock lock(this, path); + MetadataFile meta(path, MetadataFile::no_create_t()); + if (!meta.exists()) + { + errno = ENOENT; + return -1; + } + + size_t filesize = meta.getLength(); + if (filesize == newSize) + return 0; + + // extend the file, going to make IOC::write() do it + if (filesize < newSize) + { + lock.unlock(); + uint8_t zero = 0; + err = write(path, &zero, newSize - 1, 1); + if (err < 0) + return -1; + return 0; + } + + vector objects = meta.metadataRead(newSize, filesize); + + // truncate the file + if (newSize == objects[0].offset) + meta.removeEntry(objects[0].offset); + else + meta.updateEntryLength(objects[0].offset, newSize - objects[0].offset); + for (uint i = 1; i < objects.size(); i++) + meta.removeEntry(objects[i].offset); + + err = replicator->updateMetadata(path, meta); + if (err) + return err; + lock.unlock(); + + uint i = (newSize == objects[0].offset ? 0 : 1); + vector deletedObjects; + while (i < objects.size()) + { + bf::path cached = cachePath / objects[i].key; + bf::path journal = journalPath / (objects[i].key + ".journal"); + if (bf::exists(journal)) + { + size_t jsize = bf::file_size(journal); + replicator->remove(journal); + cache->deletedJournal(jsize); + } + + size_t fsize = bf::file_size(cached); + replicator->remove(cached); + cache->deletedObject(objects[i].key, fsize); + deletedObjects.push_back(objects[i].key); + ++i; + } + if (!deletedObjects.empty()) + synchronizer->deletedObjects(deletedObjects); + return 0; } /* Might need to rename this one. The corresponding fcn in IDBFileSystem specifies that it diff --git a/src/IOCoordinator.h b/src/IOCoordinator.h index 231b76d84..b6f6a0f03 100644 --- a/src/IOCoordinator.h +++ b/src/IOCoordinator.h @@ -30,6 +30,7 @@ class IOCoordinator : public boost::noncopyable virtual ~IOCoordinator(); void willRead(const char *filename, off_t offset, size_t length); + /* TODO: make read, write, append return a ssize_t */ int read(const char *filename, uint8_t *data, off_t offset, size_t length); int write(const char *filename, const uint8_t *data, off_t offset, size_t length); int append(const char *filename, const uint8_t *data, size_t length); @@ -74,6 +75,7 @@ class IOCoordinator : public boost::noncopyable Cache *cache; SMLogging *logger; Replicator *replicator; + Synchronizer *synchronizer; size_t objectSize; boost::filesystem::path journalPath; boost::filesystem::path cachePath; diff --git a/src/MetadataFile.cpp b/src/MetadataFile.cpp index ee37bbdc6..3ad320152 100755 --- a/src/MetadataFile.cpp +++ b/src/MetadataFile.cpp @@ -157,7 +157,6 @@ MetadataFile::MetadataFile(const char* filename, no_create_t) MetadataFile::~MetadataFile() { - } int MetadataFile::stat(struct stat *out) const @@ -165,12 +164,17 @@ int MetadataFile::stat(struct stat *out) const int err = ::stat(mFilename.c_str(), out); if (err) return err; + + out->st_size = getLength(); + return 0; +} +size_t MetadataFile::getLength() const +{ size_t totalSize = 0; for (auto &object : mObjects) totalSize += object.length; - out->st_size = totalSize; - return 0; + return totalSize; } bool MetadataFile::exists() const @@ -178,7 +182,7 @@ bool MetadataFile::exists() const return _exists; } -vector MetadataFile::metadataRead(off_t offset, size_t length) +vector MetadataFile::metadataRead(off_t offset, size_t length) const { // this version assumes mObjects is sorted by offset, and there are no gaps between objects vector ret; @@ -187,8 +191,11 @@ vector MetadataFile::metadataRead(off_t offset, size_t length) auto i = mObjects.begin(); // find the first object in range while (i != mObjects.end()) - if (offset >= i->offset) + { + if (offset <= (i->offset + i->length - 1)) break; + ++i; + } // append objects until foundLen >= length or EOF while (i != mObjects.end() && foundLen < length) @@ -285,6 +292,35 @@ int MetadataFile::writeMetadata(const char *filename) return error; } +/* +void MetadataFile::truncate(size_t newLength) +{ + // there's only one object to modify; the objects after it are deleted + auto &it = mObjects.begin(); + while (it != mObjects.end()) + { + size_t lastOffset = it->offset + it->length - 1; + if (lastOffset > newLength) + { + it->length = newLength - it->offset; + ++it; + break; + } + ++it; + } + while (it != mObjects.end()) + { + auto toDelete = it++; + mObjects.erase(toDelete); + } +} +*/ + +void MetadataFile::removeEntry(off_t offset) +{ + mObjects.erase(offset); +} + string MetadataFile::getNewKeyFromOldKey(const string &key, size_t length) { boost::uuids::uuid u = boost::uuids::random_generator()(); @@ -352,7 +388,7 @@ void MetadataFile::setLengthInKey(string &key, size_t newLength) key = oss.str(); } -void MetadataFile::printObjects() +void MetadataFile::printObjects() const { printf("Version: %i Revision: %i\n",mVersion,mRevision); for (std::set::const_iterator i = mObjects.begin(); i != mObjects.end(); ++i) @@ -392,6 +428,12 @@ void MetadataFile::updateEntryLength(off_t offset, size_t newLength) updateObj->length = newLength; } +metadataObject::metadataObject() +{} + +metadataObject::metadataObject(uint64_t _offset) : offset(_offset) +{} + } diff --git a/src/MetadataFile.h b/src/MetadataFile.h index 07754fc7d..839656b95 100755 --- a/src/MetadataFile.h +++ b/src/MetadataFile.h @@ -18,14 +18,14 @@ namespace storagemanager { struct metadataObject { + metadataObject(); + metadataObject(uint64_t offset); // so we can search mObjects by integer uint64_t offset; mutable uint64_t length; mutable std::string key; bool operator < (const metadataObject &b) const { return offset < b.offset; } }; - - class MetadataFile { public: @@ -36,10 +36,11 @@ class MetadataFile ~MetadataFile(); bool exists() const; - void printObjects(); + void printObjects() const; int stat(struct stat *) const; + size_t getLength() const; // returns the objects needed to update - std::vector metadataRead(off_t offset, size_t length); + std::vector metadataRead(off_t offset, size_t length) const; // updates the metadatafile with new object int writeMetadata(const char *filename); @@ -47,6 +48,7 @@ class MetadataFile void updateEntry(off_t offset, const std::string &newName, size_t newLength); void updateEntryLength(off_t offset, size_t newLength); metadataObject addMetadataObject(const char *filename, size_t length); + void removeEntry(off_t offset); // TBD: this may have to go; there may be no use case where only the uuid needs to change. static std::string getNewKeyFromOldKey(const std::string &oldKey, size_t length=0); diff --git a/src/Replicator.cpp b/src/Replicator.cpp index 4a75f4c23..166240ef2 100755 --- a/src/Replicator.cpp +++ b/src/Replicator.cpp @@ -160,11 +160,10 @@ int Replicator::addJournalEntry(const char *filename, const uint8_t *data, off_t return count; } -int Replicator::remove(const char *filename, Flags flags) +int Replicator::remove(const boost::filesystem::path &filename, Flags flags) { int ret = 0; - boost::filesystem::path p(filename); - + try { boost::filesystem::remove_all(filename); @@ -177,9 +176,16 @@ int Replicator::remove(const char *filename, Flags flags) return ret; } -int Replicator::updateMetadata(const char *filename, const MetadataFile &meta) + +int Replicator::remove(const char *filename, Flags flags) { - return 0; + boost::filesystem::path p(filename); + return remove(p); +} + +int Replicator::updateMetadata(const char *filename, MetadataFile &meta) +{ + return meta.writeMetadata(filename); } } diff --git a/src/Replicator.h b/src/Replicator.h index 8e05d9f4f..d4c407ebf 100755 --- a/src/Replicator.h +++ b/src/Replicator.h @@ -3,6 +3,7 @@ //#include "ThreadPool.h" #include "MetadataFile.h" +#include #include #include @@ -28,9 +29,10 @@ class Replicator int addJournalEntry(const char *filename, const uint8_t *data, off_t offset, size_t length); int newObject(const char *filename, const uint8_t *data, size_t length); - int remove(const char *key, Flags flags = NONE); + int remove(const char *filename, Flags flags = NONE); + int remove(const boost::filesystem::path &file, Flags flags = NONE); - int updateMetadata(const char *filename, const MetadataFile &meta); + int updateMetadata(const char *filename, MetadataFile &meta); private: Replicator(); diff --git a/src/unit_tests.cpp b/src/unit_tests.cpp index 69e98925d..24db13791 100755 --- a/src/unit_tests.cpp +++ b/src/unit_tests.cpp @@ -479,13 +479,75 @@ bool stattask() return true; } +bool IOCTruncate() +{ + + IOCoordinator *ioc = IOCoordinator::get(); + CloudStorage *cs = CloudStorage::get(); + LocalStorage *ls = dynamic_cast(cs); + if (!ls) + { + cout << "IOCTruncate() currently requires using Local storage" << endl; + return true; + } + + bf::path cachePath = ioc->getCachePath(); + bf::path journalPath = ioc->getJournalPath(); + bf::path metaPath = ioc->getMetadataPath(); + bf::path cloudPath = ls->getPrefix(); + + // metaPath doesn't necessarily exist until a MetadataFile instance is created + bf::create_directories(metaPath); + + /* start with one object in cloud storage + truncate past the end of the object + verify nothing changed & got success + truncate at 4000 bytes + verify everything sees the 'file' as 4000 bytes + - IOC + meta + truncate at 0 bytes + verify file now looks empty + verify the object was deleted + + add 2 8k test objects and a journal against the second one + truncate @ 10000 bytes + verify all files still exist + truncate @ 6000 bytes, 2nd object & journal were deleted + truncate @ 0 bytes, verify no files are left + */ + + makeTestMetadata((metaPath/"test-file.meta").string().c_str()); + makeTestObject((cloudPath/testObjKey).string().c_str()); + int err; + uint8_t buf[8192]; + + // Extending a file doesn't quite work yet, punting on it for now + + err = ioc->truncate(testFile, 4000); + assert(!err); + MetadataFile meta(testFile); + assert(meta.getLength() == 4000); + + // read the data, make sure there are only 4000 bytes + err = ioc->read(testFile, buf, 0, 8192); + assert(err == 4000); + err = ioc->read(testFile, buf, 4000, 1); + assert(err == 0); + return true; +} + + bool truncatetask() { + IOCoordinator *ioc = IOCoordinator::get(); + Cache *cache = Cache::get(); + bf::path metaPath = ioc->getMetadataPath(); + const char *filename = "trunctest1"; - ::unlink(filename); - int fd = ::open(filename, O_CREAT | O_RDWR, 0666); - assert(fd > 0); - scoped_closer f(fd); + // get the metafile created + string metaFullName = (metaPath/filename).string() + ".meta"; + ::unlink(metaFullName.c_str()); + MetadataFile meta(filename); uint8_t buf[1024]; truncate_cmd *cmd = (truncate_cmd *) buf; @@ -508,10 +570,12 @@ bool truncatetask() assert(resp->header.payloadLen == 4); assert(resp->returnCode == 0); - struct stat statbuf; - ::stat(filename, &statbuf); - assert(statbuf.st_size == 1000); - ::unlink(filename); + // reload the metadata, check that it is 1000 bytes + meta = MetadataFile(filename); + assert(meta.getLength() == 1000); + + cache->reset(); + ::unlink(metaFullName.c_str()); cout << "truncate task OK" << endl; return true; } @@ -1072,6 +1136,10 @@ int main() makeConnection(); cout << "connected" << endl; scoped_closer sc1(serverSock), sc2(sessionSock), sc3(clientSock); + + //IOCTruncate(); + //return 0; + opentask(); metadataUpdateTest(); // requires 8K object size to test boundries @@ -1092,7 +1160,7 @@ int main() appendtask(); unlinktask(); stattask(); - truncatetask(); + //truncatetask(); // currently waiting on IOC::write() to be completed. listdirtask(); pingtask(); copytask(); @@ -1105,6 +1173,7 @@ int main() s3storageTest1(); IOCReadTest1(); + IOCTruncate(); return 0; }