/**************************************************************************/ /* */ /* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */ /* Copyright (c) 2008-2017 Oleksandr Shneyder */ /* Copyright (c) 2014-2022 Ulrich Sibiller */ /* Copyright (c) 2014-2019 Mihai Moldovan */ /* Copyright (c) 2011-2022 Mike Gabriel */ /* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */ /* */ /* NXCOMP, NX protocol compression and NX extensions to this software */ /* are copyright of the aforementioned persons and companies. */ /* */ /* Redistribution and use of the present software is allowed according */ /* to terms specified in the file LICENSE.nxcomp which comes in the */ /* source distribution. */ /* */ /* All rights reserved. */ /* */ /* NOTE: This software has received contributions from various other */ /* contributors, only the core maintainers and supporters are listed as */ /* copyright holders. Please contact us, if you feel you should be listed */ /* as copyright holder, as well. */ /* */ /**************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include "Keeper.h" // // Set the verbosity level. // #define PANIC #define WARNING #undef TEST #undef DEBUG // // Remove the directory if it's empty // since more than 30 * 24 h. // #define EMPTY_DIR_TIME 2592000 // // Sleep once any ONCE entries. // #define ONCE 2 // // Define this to trace messages while // they are allocated and deallocated. // #undef REFERENCES // // This is used for reference count. // #ifdef REFERENCES int File::references_ = 0; #endif bool T_older::operator () (File *a, File *b) const { return a -> compare(b); } File::File() { name_ = NULL; size_ = 0; time_ = 0; #ifdef REFERENCES references_++; *logofs << "Keeper: Created new File at " << this << " out of " << references_ << " allocated references.\n" << logofs_flush; #endif } // // TODO: This class can leak industrial amounts of memory. // I'm 100% sure that the destructor is called and that // also the string pointed in the File structure is dele- // ted. Everything is logged, but still the memory is not // freed. This is less a problem on Windows, where the me- // mory occupation remains almost constant. Obviously the // problem lies in the STL allocators of the GNU libstc++. // File::~File() { #ifdef TEST *logofs << "Keeper: Deleting name for File at " << this << ".\n" << logofs_flush; #endif delete [] name_; #ifdef REFERENCES *logofs << "Keeper: Deleted File at " << this << " out of " << references_ << " allocated references.\n" << logofs_flush; references_--; #endif } bool File::compare(File *b) const { if (this -> time_ == b -> time_) { return (this -> size_ < b -> size_); } return (this -> time_ < b -> time_); } Keeper::Keeper(int caches, int images, const char *root, int sleep, int parent) { caches_ = caches; images_ = images; sleep_ = sleep; parent_ = parent; root_ = new char[strlen(root) + 1]; strcpy(root_, root); total_ = 0; signal_ = 0; files_ = new T_files; } Keeper::~Keeper() { empty(); delete files_; delete [] root_; } int Keeper::cleanupCaches() { #ifdef TEST *logofs << "Keeper: Looking for cache directories in '" << root_ << "'.\n" << logofs_flush; #endif DIR *rootDir = opendir(root_); if (rootDir != NULL) { dirent *dirEntry; struct stat fileStat; int baseSize = strlen(root_); int s = 0; while (((dirEntry = readdir(rootDir)) != NULL)) { if (s++ % ONCE == 0) usleep(sleep_ * 1000); if (signal_ != 0) break; if (strcmp(dirEntry -> d_name, "cache") == 0 || strncmp(dirEntry -> d_name, "cache-", 6) == 0) { char *dirName = new char[baseSize + strlen(dirEntry -> d_name) + 2]; if (dirName == NULL) { #ifdef WARNING *logofs << "Keeper: WARNING! Can't check directory entry '" << dirEntry -> d_name << "'.\n" << logofs_flush; #endif delete [] dirName; continue; } strcpy(dirName, root_); strcpy(dirName + baseSize, "/"); strcpy(dirName + baseSize + 1, dirEntry -> d_name); #ifdef TEST *logofs << "Keeper: Checking directory '" << dirName << "'.\n" << logofs_flush; #endif if (stat(dirName, &fileStat) == 0 && S_ISDIR(fileStat.st_mode) != 0) { // // Add to repository all the "C-" and // "S-" files in the given directory. // collect(dirName); } delete [] dirName; } } closedir(rootDir); } else { #ifdef WARNING *logofs << "Keeper: WARNING! Can't open NX root directory '" << root_ << "'. Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Can't open NX root directory '" << root_ << "'. Error is " << EGET() << " '" << ESTR() << "'.\n"; } // // Remove older files. // cleanup(caches_); // // Empty the repository. // empty(); return 1; } int Keeper::cleanupImages() { #ifdef TEST *logofs << "Keeper: Looking for image directory in '" << root_ << "'.\n" << logofs_flush; #endif char *imagesPath = new char[strlen(root_) + strlen("/images") + 1]; if (imagesPath == NULL) { return -1; } strcpy(imagesPath, root_); strcat(imagesPath, "/images"); // // Check if the cache directory does exist. // struct stat dirStat; if (stat(imagesPath, &dirStat) == -1) { #ifdef WARNING *logofs << "Keeper: WARNING! Can't stat NX images cache directory '" << imagesPath << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Can't stat NX images cache directory '" << imagesPath << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; delete [] imagesPath; return -1; } // // Check any of the 16 directories in the // images root path. // char *digitPath = new char[strlen(imagesPath) + 5]; strcpy(digitPath, imagesPath); for (char digit = 0; digit < 16; digit++) { // // Give up if we received a signal or // our parent is gone. // if (signal_ != 0) { #ifdef TEST *logofs << "Keeper: Signal detected. Aborting.\n" << logofs_flush; #endif goto KeeperCleanupImagesAbort; } else if (parent_ != getppid() || parent_ == 1) { #ifdef WARNING *logofs << "Keeper: WARNING! Parent process appears " << "to be dead. Returning.\n" << logofs_flush; #endif goto KeeperCleanupImagesAbort; return 0; } sprintf(digitPath + strlen(imagesPath), "/I-%01X", digit); // // Add to the repository all the files // in the given directory. // collect(digitPath); } delete [] imagesPath; delete [] digitPath; // // Remove the oldest files. // cleanup(images_); // // Empty the repository. // empty(); return 1; KeeperCleanupImagesAbort: delete [] imagesPath; delete [] digitPath; empty(); return 0; } int Keeper::collect(const char *path) { #ifdef TEST *logofs << "Keeper: Looking for files in directory '" << path << "'.\n" << logofs_flush; #endif DIR *cacheDir = opendir(path); if (cacheDir != NULL) { File *file; dirent *dirEntry; struct stat fileStat; int baseSize = strlen(path); int fileSize = baseSize + 3 + MD5_LENGTH * 2 + 1; int n = 0; int s = 0; while (((dirEntry = readdir(cacheDir)) != NULL)) { if (s++ % ONCE == 0) usleep(sleep_ * 1000); if (signal_ != 0) break; if (strcmp(dirEntry -> d_name, ".") == 0 || strcmp(dirEntry -> d_name, "..") == 0) { continue; } n++; if (strlen(dirEntry -> d_name) == (MD5_LENGTH * 2 + 2) && (strncmp(dirEntry -> d_name, "I-", 2) == 0 || strncmp(dirEntry -> d_name, "S-", 2) == 0 || strncmp(dirEntry -> d_name, "C-", 2) == 0)) { file = new File(); char *fileName = new char[fileSize]; if (file == NULL || fileName == NULL) { #ifdef WARNING *logofs << "Keeper: WARNING! Can't add file '" << dirEntry -> d_name << "' to repository.\n" << logofs_flush; #endif delete [] fileName; delete file; continue; } strcpy(fileName, path); strcpy(fileName + baseSize, "/"); strcpy(fileName + baseSize + 1, dirEntry -> d_name); file -> name_ = fileName; #ifdef DEBUG *logofs << "Keeper: Adding file '" << file -> name_ << "'.\n" << logofs_flush; #endif if (stat(file -> name_, &fileStat) == -1) { #ifdef WARNING *logofs << "Keeper: WARNING! Can't stat NX file '" << file -> name_ << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif delete file; continue; } file -> size_ = fileStat.st_size; file -> time_ = fileStat.st_mtime; files_ -> insert(T_files::value_type(file)); total_ += file -> size_; } } closedir(cacheDir); if (n == 0) { time_t now = time(NULL); if (now > 0 && stat(path, &fileStat) == 0) { #ifdef TEST *logofs << "Keeper: Empty NX subdirectory '" << path << "' unused since " << now - fileStat.st_mtime << " S.\n" << logofs_flush; #endif if (now - fileStat.st_mtime > EMPTY_DIR_TIME) { #ifdef TEST *logofs << "Keeper: Removing empty NX subdirectory '" << path << "'.\n" << logofs_flush; #endif rmdir(path); } } } } else { #ifdef WARNING *logofs << "Keeper: WARNING! Can't open NX subdirectory '" << path << ". Error is " << EGET() << " '" << ESTR() << "'.\n" << logofs_flush; #endif cerr << "Warning" << ": Can't open NX subdirectory '" << path << ". Error is " << EGET() << " '" << ESTR() << "'.\n"; } return 1; } int Keeper::cleanup(int threshold) { #ifdef TEST *logofs << "Keeper: Deleting the oldest files with " << files_ -> size() << " elements threshold " << threshold << " and size " << total_ << ".\n" << logofs_flush; #endif // // At least some versions of the standard // library don't allow erasing an element // while looping. This is not the most ef- // ficient way to release the objects, but // it's better than making a copy of the // container. // while (total_ > threshold && files_ -> size() > 0) { T_files::iterator i = files_ -> begin(); File *file = *i; #ifdef TEST *logofs << "Keeper: Removing '" << file -> name_ << "' with time " << file -> time_ << " and size " << file -> size_ << ".\n" << logofs_flush; #endif unlink(file -> name_); total_ -= file -> size_; #ifdef DEBUG *logofs << "Keeper: Going to delete the file at " << file << " while cleaning up.\n" << logofs_flush; #endif delete file; #ifdef DEBUG *logofs << "Keeper: Going to erase the element " << "while cleaning up.\n" << logofs_flush; #endif files_ -> erase(i); } #ifdef TEST *logofs << "Keeper: Bytes in repository are " << total_ << ".\n" << logofs_flush; #endif return 1; } void Keeper::empty() { #ifdef TEST *logofs << "Keeper: Getting rid of files in repository.\n" << logofs_flush; #endif while (files_ -> size() > 0) { T_files::iterator i = files_ -> begin(); File *file = *i; #ifdef DEBUG *logofs << "Keeper: Going to delete the file at " << file << " while emptying.\n" << logofs_flush; #endif delete file; #ifdef DEBUG *logofs << "Keeper: Going to erase the element " << "while emptying.\n" << logofs_flush; #endif files_ -> erase(i); } total_ = 0; #ifdef TEST *logofs << "Keeper: Bytes in repository are " << total_ << ".\n" << logofs_flush; #endif }