bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
BESFileLockingCache.cc
1// This file was originally part of bes, A C++ back-end server
2// implementation framework for the OPeNDAP Data Access Protocol.
3// Copied to libdap. This is used to cache responses built from
4// functional CE expressions.
5
6// Moved back to the BES. 6/11/13 jhrg
7
8// Copyright (c) 2012 OPeNDAP, Inc
9// Author: James Gallagher <jgallagher@opendap.org>
10// Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
11//
12// This library is free software; you can redistribute it and/or
13// modify it under the terms of the GNU Lesser General Public
14// License as published by the Free Software Foundation; either
15// version 2.1 of the License, or (at your option) any later version.
16//
17// This library is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20// Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public
23// License along with this library; if not, write to the Free Software
24// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25//
26// You can contact University Corporation for Atmospheric Research at
27// 3080 Center Green Drive, Boulder, CO 80301
28
29#include "config.h"
30
31#include <sys/stat.h>
32#include <unistd.h>
33#include <dirent.h>
34#include <fcntl.h>
35#include <sys/stat.h>
36
37#include <string>
38#include <sstream>
39#include <vector>
40
41#include <cstring>
42#include <cerrno>
43
44#include "BESInternalError.h"
45
46#include "BESUtil.h"
47#include "BESDebug.h"
48#include "BESLog.h"
49
50#include "BESFileLockingCache.h"
51
52// Symbols used with BESDEBUG.
53#define CACHE "cache"
54#define LOCK "cache-lock"
55#define LOCK_STATUS "cache-lock-status"
56
57#define CACHE_CONTROL "cache_control"
58
59#define prolog std::string("BESFileLockingCache::").append(__func__).append("() - ")
60
61using namespace std;
62
63// conversion factor
64static const unsigned long long BYTES_PER_MEG = 1048576ULL;
65
66// Max cache size in megs, so we can check the user input and warn.
67// 2^64 / 2^20 == 2^44
68static const unsigned long long MAX_CACHE_SIZE_IN_MEGABYTES = (1ULL << 44);
69
70static inline string get_errno() {
71 const char *s_err = strerror(errno);
72 return s_err ? s_err : "unknown error";
73}
74
75// Build a lock of a certain type.
76//
77// Using whence == SEEK_SET with start and len set to zero means lock the whole file.
78// jhrg 9/8/18
79static inline struct flock *advisory_lock(int type)
80{
81 static struct flock lock;
82 lock.l_type = type;
83 lock.l_whence = SEEK_SET;
84 lock.l_start = 0;
85 lock.l_len = 0;
86 lock.l_pid = getpid();
87
88 return &lock;
89}
90
92class AdvisoryLockGuard {
93 int d_fd;
94
95public:
96 AdvisoryLockGuard() = delete;
97 AdvisoryLockGuard(const AdvisoryLockGuard &) = delete;
98 AdvisoryLockGuard &operator=(const AdvisoryLockGuard &) = delete;
99
101 AdvisoryLockGuard(int fd, int type) : d_fd(fd) {
102 struct flock *l = advisory_lock(type);
103 if (fcntl(d_fd, F_SETLKW, l) == -1) {
104 ERROR_LOG("Could not lock the advisory file lock (fcntl: " + get_errno() + ").");
105 }
106 }
107
108 ~AdvisoryLockGuard() {
109 struct flock *l = advisory_lock(F_UNLCK);
110 if (fcntl(d_fd, F_SETLKW, l) == -1) {
111 ERROR_LOG("Could not unlock the advisory file lock (fcntl: " + get_errno() + ").");
112 }
113 }
114};
115
137BESFileLockingCache::BESFileLockingCache(string cache_dir, string prefix, unsigned long long size) :
138 d_cache_dir(std::move(cache_dir)), d_prefix(std::move(prefix)), d_max_cache_size_in_bytes(size)
139{
140 m_initialize_cache_info();
141}
142
152static bool createLockedFile(const string &file_name, int &ref_fd)
153{
154 BESDEBUG(LOCK, prolog << "BEGIN file: " << file_name <<endl);
155
156 int fd;
157 if ((fd = open(file_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666)) < 0) {
158 switch (errno) {
159 case EEXIST:
160 return false;
161
162 default:
163 throw BESInternalError(file_name + ": " + get_errno(), __FILE__, __LINE__);
164 }
165 }
166
167 struct flock *l = advisory_lock(F_WRLCK);
168 // F_SETLKW == set lock, blocking
169 if (fcntl(fd, F_SETLKW, l) == -1) {
170 close(fd);
171 ostringstream oss;
172 oss << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
173 throw BESInternalError(oss.str(), __FILE__, __LINE__);
174 }
175
176 BESDEBUG(LOCK, prolog << "END file: " << file_name <<endl);
177
178 // Success
179 ref_fd = fd;
180 return true;
181}
182
192bool BESFileLockingCache::m_initialize_cache_info()
193{
194 BESDEBUG(CACHE, prolog << "BEGIN" << endl);
195
196 // The value set in configuration files, etc., is the size in megabytes. The private
197 // variable holds the size in bytes (converted below).
198 d_max_cache_size_in_bytes = min(d_max_cache_size_in_bytes, MAX_CACHE_SIZE_IN_MEGABYTES);
199 d_max_cache_size_in_bytes *= BYTES_PER_MEG;
200 d_target_size = d_max_cache_size_in_bytes * 0.8;
201
202 BESDEBUG(CACHE, prolog << "d_max_cache_size_in_bytes: "
203 << d_max_cache_size_in_bytes << " d_target_size: "<<d_target_size<< endl);
204
205 bool status = m_check_ctor_params(); // Throws BESError on error; otherwise sets the cache_enabled() property
206 if (status) {
207 d_cache_info = BESUtil::assemblePath(d_cache_dir, d_prefix + CACHE_CONTROL, true);
208
209 BESDEBUG(CACHE, prolog << "d_cache_info: " << d_cache_info << endl);
210
211 // See if we can create it. If so, that means it doesn't exist. So make it and
212 // set the cache initial size to zero.
213 if (createLockedFile(d_cache_info, d_cache_info_fd)) {
214 // initialize the cache size to zero
215 unsigned long long size = 0;
216 if (write(d_cache_info_fd, &size, sizeof(unsigned long long)) != sizeof(unsigned long long))
217 throw BESInternalError(prolog + "Could not write size info to the cache info file `" + d_cache_info + "`",
218 __FILE__,
219 __LINE__);
220
221 // This leaves the d_cache_info_fd file descriptor open
222#if 0
223 unlock_cache();
224#endif
225 if (fcntl(d_cache_info_fd, F_SETLK, advisory_lock(F_UNLCK)) == -1) {
226 throw BESInternalError(prolog +"An error occurred trying to unlock the cache-control file" + get_errno(), __FILE__,
227 __LINE__);
228 }
229 }
230 else {
231 if ((d_cache_info_fd = open(d_cache_info.c_str(), O_RDWR)) == -1) {
232 throw BESInternalError(prolog + "Failed to open cache info file: " + d_cache_info + " errno: " + get_errno(), __FILE__, __LINE__);
233 }
234 }
235
236 BESDEBUG(CACHE, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
237 }
238
239 BESDEBUG(CACHE, prolog << "END [" << "CACHE IS " << (cache_enabled()?"ENABLED]":"DISABLED]") << endl);
240
241 return status;
242}
243
259void BESFileLockingCache::initialize(const string &cache_dir, const string &prefix, unsigned long long size)
260{
261 d_cache_dir = cache_dir;
262 d_prefix = prefix;
263 d_max_cache_size_in_bytes = size; // converted later on to bytes
264
265 m_initialize_cache_info();
266}
267
268
269
270inline void BESFileLockingCache::m_record_descriptor(const string &file, int fd)
271{
272 BESDEBUG(LOCK, prolog << "Recording descriptor: " << file << ", " << fd << endl);
273
274 d_locks.insert(std::pair<string, int>(file, fd));
275}
276
277inline int BESFileLockingCache::m_remove_descriptor(const string &file)
278{
279 BESDEBUG(LOCK, prolog << "d_locks size: " << d_locks.size() << endl);
280
281 FilesAndLockDescriptors::iterator i = d_locks.find(file);
282 if (i == d_locks.end()) return -1;
283
284 int fd = i->second;
285 d_locks.erase(i);
286
287 BESDEBUG(LOCK, prolog << "Found file descriptor [" << fd << "] for file: " << file << endl);
288
289 return fd;
290}
291
292#if USE_GET_SHARED_LOCK
293inline int BESFileLockingCache::m_find_descriptor(const string &file)
294{
295 BESDEBUG(LOCK, prolog << "d_locks size: " << d_locks.size() << endl);
296
297 FilesAndLockDescriptors::iterator i = d_locks.find(file);
298 if (i == d_locks.end()) return -1;
299
300 BESDEBUG(LOCK, prolog << "Found file descriptor [" << i->second << "] for file: " << file << endl);
301
302 return i->second; // return the file descriptor bound to 'file'
303}
304#endif
305
311static string lockStatus(const int fd)
312{
313 struct flock lock_query;
314
315 lock_query.l_type = F_WRLCK; /* Test for any lock on any part of a file. */
316 lock_query.l_start = 0;
317 lock_query.l_whence = SEEK_SET;
318 lock_query.l_len = 0;
319 lock_query.l_pid = 0;
320
321 int ret = fcntl(fd, F_GETLK, &lock_query);
322
323 stringstream ss;
324 ss << endl;
325 if (ret == -1) {
326 ss << "fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << " errno[" << errno << "]: "
327 << strerror(errno) << endl;
328 }
329 else {
330 ss << "fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << endl;
331 }
332
333 ss << "lock_info.l_len: " << lock_query.l_len << endl;
334 ss << "lock_info.l_pid: " << lock_query.l_pid << endl;
335 ss << "lock_info.l_start: " << lock_query.l_start << endl;
336
337 string type;
338 switch (lock_query.l_type) {
339 case F_RDLCK:
340 type = "F_RDLCK";
341 break;
342 case F_WRLCK:
343 type = "F_WRLCK";
344 break;
345 case F_UNLCK:
346 type = "F_UNLCK";
347 break;
348
349 }
350
351 ss << "lock_info.l_type: " << type << endl;
352 ss << "lock_info.l_whence: " << lock_query.l_whence << endl;
353
354 return ss.str();
355}
356
362static void unlock(int fd)
363{
364 if (fcntl(fd, F_SETLK, advisory_lock(F_UNLCK)) == -1) {
365 throw BESInternalError(prolog + "An error occurred trying to unlock the file: " + get_errno(), __FILE__, __LINE__);
366 }
367
368 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(fd) << endl);
369
370 if (close(fd) == -1) throw BESInternalError(prolog + "Could not close the (just) unlocked file.", __FILE__, __LINE__);
371
372 BESDEBUG(LOCK, prolog << "File Closed. fd: " << fd << endl);
373}
374
394bool BESFileLockingCache::m_check_ctor_params()
395{
396 // Should this really be a fatal error? What about just not
397 // using the cache in this case or writing out a warning message
398 // to the log. jhrg 10/23/15
399 //
400 // Yes, leave this as a fatal error and handle the case when cache_dir is
401 // empty in code that specializes this class. Those child classes are
402 // all singletons and their get_instance() methods need to return null
403 // when caching is turned off. You cannot do that here without throwing
404 // and we don't want to throw an exception for every call to a child's
405 // get_instance() method just because someone doesn't want to use a cache.
406 // jhrg 9/27/16
407 BESDEBUG(CACHE, prolog << "BEGIN" << endl);
408
409 if (d_cache_dir.empty()) {
410 BESDEBUG(CACHE, prolog << "The cache directory was not specified. CACHE IS DISABLED." << endl);
411
412 disable();
413 return false;
414 }
415
416
417 int status = mkdir(d_cache_dir.c_str(), 0775);
418 // If there is an error and it's not that the dir already exists,
419 // throw an exception.
420 if (status == -1 && errno != EEXIST) {
421 string err = prolog + "The cache directory " + d_cache_dir + " could not be created: " + strerror(errno);
422 throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
423 }
424
425 if (d_prefix.empty()) {
426 string err = prolog + "The cache file prefix was not specified, must not be empty";
427 throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
428 }
429
430 // I changed this from '<=' to '<' since the code now uses a cache size
431 // of zero to indicate that the cache will never be purged. The other
432 // size-related methods all still work. Since the field is unsigned,
433 // testing for '< 0' is pointless. Later on in this code the value is capped
434 // at MAX_CACHE_SIZE_IN_MEGABYTES (set in this file), which is 2^44.
435 // jhrg 2.28.18
436#if 0
437 if (d_max_cache_size_in_bytes < 0) {
438 string err = "The cache size was not specified, must be greater than zero";
439 throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
440 }
441#endif
442
443 BESDEBUG(CACHE,
444 "BESFileLockingCache::" << __func__ << "() -" <<
445 " d_cache_dir: " << d_cache_dir <<
446 " d_prefix: " << d_prefix <<
447 " d_max_cache_size_in_bytes: " << d_max_cache_size_in_bytes << endl);
448
449 enable();
450 return true;
451}
452
453
454static const string chars_excluded_from_filenames = R"(<>=,/()\"':? []()$)";
455
472string BESFileLockingCache::get_cache_file_name(const string &src, bool mangle)
473{
474 // Old way of building String, retired 10/02/2015 - ndp
475 // Return d_cache_dir + "/" + d_prefix + BESFileLockingCache::DAP_CACHE_CHAR + target;
476 BESDEBUG(CACHE, prolog << "src: '" << src << "' mangle: "<< mangle << endl);
477
478 string target = get_cache_file_prefix() + src;
479
480 if (mangle) {
481 string::size_type pos = target.find_first_of(chars_excluded_from_filenames);
482 while (pos != string::npos) {
483 target.replace(pos, 1, "#", 1);
484 pos = target.find_first_of(chars_excluded_from_filenames);
485 }
486 }
487
488 if (target.size() > 254) {
489 ostringstream msg;
490 msg << prolog << "Cache filename is longer than 254 characters (name length: ";
491 msg << target.size() << ", name: " << target;
492 throw BESInternalError(msg.str(), __FILE__, __LINE__);
493 }
494
495 target = BESUtil::assemblePath(get_cache_directory(), target, true);
496
497 BESDEBUG(CACHE, prolog << "target: '" << target << "'" << endl);
498
499 return target;
500}
501
502#if USE_GET_SHARED_LOCK
516static bool getSharedLock(const string &file_name, int &ref_fd)
517{
518 BESDEBUG(LOCK, prolog << "Acquiring cache read lock for " << file_name <<endl);
519
520 int fd;
521 if ((fd = open(file_name.c_str(), O_RDONLY)) < 0) {
522 switch (errno) {
523 case ENOENT:
524 return false;
525
526 default:
527 throw BESInternalError(get_errno(), __FILE__, __LINE__);
528 }
529 }
530
531 struct flock *l = advisory_lock(F_RDLCK);
532 if (fcntl(fd, F_SETLKW, l) == -1) {
533 close(fd);
534 ostringstream oss;
535 oss << prolog << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
536 throw BESInternalError(oss.str(), __FILE__, __LINE__);
537 }
538
539 BESDEBUG(LOCK, prolog << "SUCCESS Read Lock Acquired For " << file_name <<endl);
540
541 // Success
542 ref_fd = fd;
543 return true;
544}
545#endif
546
565bool BESFileLockingCache::get_read_lock(const string &target, int &fd)
566{
567 AdvisoryLockGuard read_alg(d_cache_info_fd, F_RDLCK);
568#if 0
569 lock_cache_read();
570#endif
571
572 bool status = true;
573
574#if USE_GET_SHARED_LOCK
575 status = getSharedLock(target, fd);
576
577 if (status) m_record_descriptor(target, fd);
578#else
579 fd = m_find_descriptor(target);
580 // fd == -1 --> The file is not currently open
581 if ((fd == -1) && (fd = open(target.c_str(), O_RDONLY)) < 0) {
582 switch (errno) {
583 case ENOENT:
584 return false; // The file does not exist
585
586 default:
587 throw BESInternalError(get_errno(), __FILE__, __LINE__);
588 }
589 }
590
591 // The file might be open for writing, so setting a read lock is
592 // not possible.
593 struct flock *l = advisory_lock(F_RDLCK);
594 if (fcntl(fd, F_SETLKW, l) == -1) {
595 return false; // cannot get the lock
596 }
597
598 m_record_descriptor(target, fd);
599#endif
600
601#if 0
602 unlock_cache();
603#endif
604
605 return status;
606}
607
625bool BESFileLockingCache::create_and_lock(const string &target, int &fd)
626{
627 AdvisoryLockGuard write_alg(d_cache_info_fd, F_WRLCK);
628#if 0
629 lock_cache_write();
630#endif
631
632 bool status = createLockedFile(target, fd);
633
634 BESDEBUG(LOCK, prolog << "target: " << target << " (status: " << status << ", fd: " << fd << ")" << endl);
635
636 if (status) m_record_descriptor(target, fd);
637
638#if 0
639 unlock_cache();
640#endif
641
642 return status;
643}
644
661{
662 struct flock lock;
663 lock.l_type = F_RDLCK;
664 lock.l_whence = SEEK_SET;
665 lock.l_start = 0;
666 lock.l_len = 0;
667 lock.l_pid = getpid();
668
669 if (fcntl(fd, F_SETLKW, &lock) == -1) {
670 throw BESInternalError(get_errno(), __FILE__, __LINE__);
671 }
672
673 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(fd) << endl);
674}
675
684#if 0
685void BESFileLockingCache::lock_cache_write()
686{
687 BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
688
689 if (fcntl(d_cache_info_fd, F_SETLKW, advisory_lock(F_WRLCK)) == -1) {
690 throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
691 __LINE__);
692 }
693
694 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
695}
696
700void BESFileLockingCache::lock_cache_read()
701{
702 BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
703
704 if (fcntl(d_cache_info_fd, F_SETLKW, advisory_lock(F_RDLCK)) == -1) {
705 throw BESInternalError(prolog + "An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
706 __LINE__);
707 }
708
709 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
710}
711
717void BESFileLockingCache::unlock_cache()
718{
719 BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
720
721 if (fcntl(d_cache_info_fd, F_SETLK, advisory_lock(F_UNLCK)) == -1) {
722 throw BESInternalError(prolog +"An error occurred trying to unlock the cache-control file" + get_errno(), __FILE__,
723 __LINE__);
724 }
725
726 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
727}
728#endif
729
745void BESFileLockingCache::unlock_and_close(const string &file_name)
746{
747 BESDEBUG(LOCK, prolog << "BEGIN file: " << file_name << endl);
748
749 int fd = m_remove_descriptor(file_name); // returns -1 when no more files desp. remain
750 while (fd != -1) {
751 unlock(fd);
752 fd = m_remove_descriptor(file_name);
753 }
754
755 BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
756 BESDEBUG(LOCK, prolog << "END file: "<< file_name<< endl);
757}
758
769unsigned long long BESFileLockingCache::update_cache_info(const string &target)
770{
771 AdvisoryLockGuard write_alg(d_cache_info_fd, F_WRLCK);
772 unsigned long long current_size;
773#if 0
774 try {
775 lock_cache_write();
776#endif
777
778 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
779 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
780
781 // read the size from the cache info file
782 if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
783 throw BESInternalError(prolog + "Could not get read size info from the cache info file!", __FILE__, __LINE__);
784
785 struct stat buf;
786 int statret = stat(target.c_str(), &buf);
787 if (statret == 0)
788 current_size += buf.st_size;
789 else
790 throw BESInternalError(prolog + "Could not read the size of the new file: " + target + " : " + get_errno(), __FILE__,
791 __LINE__);
792
793 BESDEBUG(CACHE, prolog << "cache size updated to: " << current_size << endl);
794
795 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
796 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
797
798 if (write(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
799 throw BESInternalError(prolog + "Could not write size info from the cache info file!", __FILE__, __LINE__);
800
801#if 0
802 unlock_cache();
803 }
804 catch (...) {
805 unlock_cache();
806 throw;
807 }
808#endif
809
810 return current_size;
811}
812
817bool BESFileLockingCache::cache_too_big(unsigned long long current_size) const
818{
819 return current_size > d_max_cache_size_in_bytes;
820}
821
830{
831 AdvisoryLockGuard read_alg(d_cache_info_fd, F_RDLCK);
832 unsigned long long current_size;
833#if 0
834 try {
835 lock_cache_read();
836#endif
837
838 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
839 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
840 // read the size from the cache info file
841 if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
842 throw BESInternalError(prolog + "Could not get read size info from the cache info file!", __FILE__, __LINE__);
843
844#if 0
845 unlock_cache();
846 }
847 catch (...) {
848 unlock_cache();
849 throw;
850 }
851#endif
852
853 return current_size;
854}
855
856static bool entry_op(cache_entry &e1, cache_entry &e2)
857{
858 return e1.time < e2.time;
859}
860
862unsigned long long BESFileLockingCache::m_collect_cache_dir_info(CacheFiles &contents)
863{
864 DIR *dip = opendir(d_cache_dir.c_str());
865 if (!dip) throw BESInternalError(prolog + "Unable to open cache directory " + d_cache_dir, __FILE__, __LINE__);
866
867 struct dirent *dit = nullptr;
868 vector<string> files;
869 // go through the cache directory and collect all the files that
870 // start with the matching prefix
871 while ((dit = readdir(dip)) != NULL) {
872 string dirEntry = dit->d_name;
873 if (dirEntry.compare(0, d_prefix.size(), d_prefix) == 0 && dirEntry != d_cache_info) {
874 files.push_back(d_cache_dir + "/" + dirEntry);
875 }
876 }
877
878 closedir(dip);
879
880 unsigned long long current_size = 0;
881 struct stat buf;
882 for (vector<string>::iterator file = files.begin(); file != files.end(); ++file) {
883 if (stat(file->c_str(), &buf) == 0) {
884 current_size += buf.st_size;
885 cache_entry entry;
886 entry.name = *file;
887 entry.size = buf.st_size;
888 entry.time = buf.st_atime;
889 // Sanity check; Removed after initial testing since some files might be zero bytes
890#if 0
891 if (entry.size == 0)
892 throw BESInternalError("Zero-byte file found in cache. " + *file, __FILE__, __LINE__);
893#endif
894 contents.push_back(entry);
895 }
896 }
897
898 // Sort so smaller (older) times are first.
899 contents.sort(entry_op);
900
901 return current_size;
902}
903
920bool BESFileLockingCache::get_exclusive_lock_nb(const string &file_name, int &ref_fd)
921{
922 BESDEBUG(LOCK, prolog << "BEGIN filename: " << file_name <<endl);
923
924 int fd;
925 if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
926 switch (errno) {
927 case ENOENT:
928 return false;
929
930 default:
931 throw BESInternalError(prolog + "errno: " + get_errno(), __FILE__, __LINE__);
932 }
933 }
934
935 struct flock *l = advisory_lock(F_WRLCK);
936 if (fcntl(fd, F_SETLK, l) == -1) {
937 switch (errno) {
938 case EAGAIN:
939 case EACCES:
940 BESDEBUG(LOCK,prolog << "exit (false): " << file_name << " by: " << l->l_pid << endl);
941 close(fd);
942 return false;
943
944 default: {
945 close(fd);
946 ostringstream oss;
947 oss << prolog << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
948 throw BESInternalError(oss.str(), __FILE__, __LINE__);
949 }
950 }
951 }
952
953
954 // Success
955 m_record_descriptor(file_name,fd);
956 ref_fd = fd;
957 BESDEBUG(LOCK, prolog << "END filename: " << file_name <<endl);
958 return true;
959}
960
961
977void BESFileLockingCache::update_and_purge(const string &new_file)
978{
979 BESDEBUG(CACHE, prolog << "Starting the purge" << endl);
980
981 if (is_unlimited()) {
982 BESDEBUG(CACHE, prolog << "Size is set to unlimited, so no need to purge." << endl);
983 return;
984 }
985
986 AdvisoryLockGuard write_alg(d_cache_info_fd, F_WRLCK);
987#if 0
988 try {
989 lock_cache_write();
990#endif
991
992 CacheFiles contents;
993 unsigned long long computed_size = m_collect_cache_dir_info(contents);
994#if 0
995 if (BESISDEBUG( "cache_contents" )) {
996 BESDEBUG(CACHE, "BEFORE Purge " << computed_size/BYTES_PER_MEG << endl );
997 CacheFiles::iterator ti = contents.begin();
998 CacheFiles::iterator te = contents.end();
999 for (; ti != te; ti++) {
1000 BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
1001 }
1002 }
1003#endif
1004 BESDEBUG(CACHE, prolog << "Current and target size (in MB) "
1005 << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
1006
1007 // This deletes files and updates computed_size
1008 if (cache_too_big(computed_size)) {
1009
1010 // d_target_size is 80% of the maximum cache size.
1011 // Grab the first which is the oldest in terms of access time.
1012 CacheFiles::iterator i = contents.begin();
1013 while (i != contents.end() && computed_size > d_target_size) {
1014 // Grab an exclusive lock but do not block - if another process has the file locked
1015 // just move on to the next file. Also test to see if the current file is the file
1016 // this process just added to the cache - don't purge that!
1017 int cfile_fd;
1018 if (i->name != new_file && get_exclusive_lock_nb(i->name, cfile_fd)) {
1019 BESDEBUG(CACHE, prolog << "purge: " << i->name << " removed." << endl);
1020
1021 if (unlink(i->name.c_str()) != 0)
1022 throw BESInternalError(
1023 prolog + "Unable to purge the file " + i->name + " from the cache: " + get_errno(), __FILE__,
1024 __LINE__);
1025
1026 unlock(cfile_fd);
1027 computed_size -= i->size;
1028 }
1029 ++i;
1030
1031 BESDEBUG(CACHE,prolog << "Current and target size (in MB) "
1032 << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
1033 }
1034 }
1035
1036 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
1037 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
1038
1039 if (write(d_cache_info_fd, &computed_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
1040 throw BESInternalError(prolog + "Could not write size info to the cache info file!", __FILE__, __LINE__);
1041#if 0
1042 if (BESISDEBUG( "cache_contents" )) {
1043 contents.clear();
1044 computed_size = m_collect_cache_dir_info(contents);
1045 BESDEBUG(CACHE, "AFTER Purge " << computed_size/BYTES_PER_MEG << endl );
1046 CacheFiles::iterator ti = contents.begin();
1047 CacheFiles::iterator te = contents.end();
1048 for (; ti != te; ti++) {
1049 BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
1050 }
1051 }
1052#endif
1053#if 0
1054 unlock_cache();
1055 }
1056 catch (...) {
1057 unlock_cache();
1058 throw;
1059 }
1060#endif
1061}
1062
1079bool BESFileLockingCache::get_exclusive_lock(const string &file_name, int &ref_fd)
1080{
1081 BESDEBUG(LOCK, prolog << "BEGIN filename: " << file_name <<endl);
1082
1083 int fd;
1084 if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
1085 switch (errno) {
1086 case ENOENT:
1087 return false;
1088
1089 default:
1090 {
1091 stringstream msg;
1092 msg << prolog << "FAILED to open file: " << file_name << " errno: " << get_errno();
1093 BESDEBUG(LOCK, msg.str() << endl);
1094 throw BESInternalError(msg.str() , __FILE__, __LINE__);
1095 }
1096 }
1097 }
1098
1099 struct flock *l = advisory_lock(F_WRLCK);
1100 if (fcntl(fd, F_SETLKW, l) == -1) { // F_SETLKW == blocking lock
1101 close(fd);
1102 ostringstream oss;
1103 oss << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
1104 throw BESInternalError(oss.str(), __FILE__, __LINE__);
1105 }
1106
1107 // Success
1108 m_record_descriptor(file_name,fd);
1109 ref_fd = fd;
1110
1111 BESDEBUG(LOCK, prolog << "END filename: " << file_name <<endl);
1112 return true;
1113}
1114
1125void BESFileLockingCache::purge_file(const string &file)
1126{
1127 AdvisoryLockGuard write_alg(d_cache_info_fd, F_WRLCK);
1128 BESDEBUG(CACHE, prolog << "Starting the purge" << endl);
1129
1130#if 0
1131 try {
1132 lock_cache_write();
1133#endif
1134
1135 // Grab an exclusive lock on the file. SonarScan will complain about nested trys... jhrg 11/16/22
1136 int cfile_fd;
1137 try {
1138 if (get_exclusive_lock(file, cfile_fd)) {
1139 // Get the file's size
1140 unsigned long long size = 0;
1141 struct stat buf;
1142 if (stat(file.c_str(), &buf) == 0) {
1143 size = buf.st_size;
1144 }
1145
1146 BESDEBUG(CACHE, prolog << "file: " << file << " removed." << endl);
1147
1148 if (unlink(file.c_str()) != 0)
1149 throw BESInternalError(
1150 prolog + "Unable to purge the file " + file + " from the cache: " + get_errno(), __FILE__,
1151 __LINE__);
1152
1153 // FIXME The exception above could result in a leak. jhrg 11/16/22
1154 unlock(cfile_fd);
1155 cfile_fd = -1;
1156 unsigned long long cache_size = get_cache_size() - size;
1157
1158 if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
1159 throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__,
1160 __LINE__);
1161
1162 if (write(d_cache_info_fd, &cache_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
1163 throw BESInternalError(prolog + "Could not write size info to the cache info file!", __FILE__,
1164 __LINE__);
1165 }
1166 }
1167 catch (...) {
1168 if (cfile_fd != -1)
1169 unlock(cfile_fd);
1170 }
1171#if 0
1172 unlock_cache();
1173 }
1174catch (...) {
1175 unlock_cache();
1176 throw;
1177 }
1178#endif
1179}
1180
1190bool BESFileLockingCache::dir_exists(const string &dir)
1191{
1192 struct stat buf{0};
1193
1194 return (stat(dir.c_str(), &buf) == 0) && (buf.st_mode & S_IFDIR);
1195}
1196
1205void BESFileLockingCache::dump(ostream &strm) const
1206{
1207 strm << BESIndent::LMarg << prolog << "(" << (void *) this << ")" << endl;
1208 BESIndent::Indent();
1209 strm << BESIndent::LMarg << "cache dir: " << d_cache_dir << endl;
1210 strm << BESIndent::LMarg << "prefix: " << d_prefix << endl;
1211 strm << BESIndent::LMarg << "size (bytes): " << d_max_cache_size_in_bytes << endl;
1212 BESIndent::UnIndent();
1213}
virtual unsigned long long get_cache_size()
Get the cache size.
void initialize(const std::string &cache_dir, const std::string &prefix, unsigned long long size)
Initialize an instance of FileLockingCache.
virtual void unlock_and_close(const std::string &target)
std::string get_cache_directory() const
bool is_unlimited() const
Is this cache allowed to store as much as it wants?
virtual unsigned long long update_cache_info(const std::string &target)
Update the cache info file to include 'target'.
void enable()
Enable the cache.
virtual bool create_and_lock(const std::string &target, int &fd)
Create a file in the cache and lock it for write access.
virtual void exclusive_to_shared_lock(int fd)
Transfer from an exclusive lock to a shared lock.
virtual bool get_read_lock(const std::string &target, int &fd)
Get a read-only lock on the file if it exists.
static bool dir_exists(const std::string &dir)
virtual bool get_exclusive_lock(const std::string &target, int &fd)
void disable()
Disable the cache.
virtual bool get_exclusive_lock_nb(const std::string &target, int &fd)
std::string get_cache_file_prefix() const
void dump(std::ostream &strm) const override
dumps information about this object
virtual void purge_file(const std::string &file)
Purge a single file from the cache.
virtual bool cache_too_big(unsigned long long current_size) const
look at the cache size; is it too large? Look at the cache size and see if it is too big.
virtual void update_and_purge(const std::string &new_file)
Purge files from the cache.
virtual std::string get_cache_file_name(const std::string &src, bool mangle=true)
exception thrown if internal error encountered
static std::string assemblePath(const std::string &firstPart, const std::string &secondPart, bool leadingSlash=false, bool trailingSlash=false)
Assemble path fragments making sure that they are separated by a single '/' character.
Definition BESUtil.cc:804