bes  Updated for version 3.20.5
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/file.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <dirent.h>
35 #include <fcntl.h>
36 
37 #ifdef HAVE_STDLIB_H
38 #include <stdlib.h>
39 #endif
40 
41 #include <string>
42 #include <sstream>
43 #include <vector>
44 #include <cstring>
45 #include <cerrno>
46 
47 #include "BESInternalError.h"
48 
49 #include "BESUtil.h"
50 #include "BESDebug.h"
51 #include "BESLog.h"
52 
53 #include "BESFileLockingCache.h"
54 
55 // Symbols used with BESDEBUG.
56 #define CACHE "cache"
57 #define LOCK "cache-lock"
58 #define LOCK_STATUS "cache-lock-status"
59 
60 #define CACHE_CONTROL "cache_control"
61 
62 #define prolog std::string("BESFileLockingCache::").append(__func__).append("() - ")
63 
64 using namespace std;
65 
66 // conversion factor
67 static const unsigned long long BYTES_PER_MEG = 1048576ULL;
68 
69 // Max cache size in megs, so we can check the user input and warn.
70 // 2^64 / 2^20 == 2^44
71 static const unsigned long long MAX_CACHE_SIZE_IN_MEGABYTES = (1ULL << 44);
72 
94 BESFileLockingCache::BESFileLockingCache(const string &cache_dir, const string &prefix, unsigned long long size) :
95  d_cache_dir(cache_dir), d_prefix(prefix), d_max_cache_size_in_bytes(size), d_target_size(0), d_cache_info(""),
96  d_cache_info_fd(-1)
97 {
98  m_initialize_cache_info();
99 }
100 
116 void BESFileLockingCache::initialize(const string &cache_dir, const string &prefix, unsigned long long size)
117 {
118  d_cache_dir = cache_dir;
119  d_prefix = prefix;
120  d_max_cache_size_in_bytes = size; // converted later on to bytes
121 
122  m_initialize_cache_info();
123 }
124 
125 static inline string get_errno()
126 {
127  char *s_err = strerror(errno);
128  if (s_err)
129  return s_err;
130  else
131  return "Unknown error.";
132 }
133 
134 // Build a lock of a certain type.
135 static inline struct flock *lock(int type)
136 {
137  static struct flock lock;
138  lock.l_type = type;
139  lock.l_whence = SEEK_SET;
140  lock.l_start = 0;
141  lock.l_len = 0;
142  lock.l_pid = getpid();
143 
144  return &lock;
145 }
146 
147 inline void BESFileLockingCache::m_record_descriptor(const string &file, int fd)
148 {
149  BESDEBUG(LOCK,
150  "BESFileLockingCache::m_record_descriptor() - Recording descriptor: " << file << ", " << fd << endl);
151 
152  d_locks.insert(std::pair<string, int>(file, fd));
153 }
154 
155 inline int BESFileLockingCache::m_remove_descriptor(const string &file)
156 {
157  BESDEBUG(LOCK, "BESFileLockingCache::m_remove_descriptor(): d_locks size: " << d_locks.size() << endl);
158 
159  FilesAndLockDescriptors::iterator i = d_locks.find(file);
160  if (i == d_locks.end()) return -1;
161 
162  int fd = i->second;
163  d_locks.erase(i);
164 
165  BESDEBUG(LOCK,
166  "BESFileLockingCache::m_remove_descriptor(): Found file descriptor [" << fd << "] for file: " << file << endl);
167 
168  return fd;
169 }
170 
171 #if USE_GET_SHARED_LOCK
172 inline int BESFileLockingCache::m_find_descriptor(const string &file)
173 {
174  BESDEBUG(LOCK, "BESFileLockingCache::m_find_descriptor(): d_locks size: " << d_locks.size() << endl);
175 
176  FilesAndLockDescriptors::iterator i = d_locks.find(file);
177  if (i == d_locks.end()) return -1;
178 
179  BESDEBUG(LOCK,
180  "BESFileLockingCache::m_find_descriptor(): Found file descriptor [" << i->second << "] for file: " << file << endl);
181 
182  return i->second; // return the file descriptor bound to 'file'
183 }
184 #endif
185 
191 static string lockStatus(const int fd)
192 {
193  struct flock lock_query;
194 
195  lock_query.l_type = F_WRLCK; /* Test for any lock on any part of a file. */
196  lock_query.l_start = 0;
197  lock_query.l_whence = SEEK_SET;
198  lock_query.l_len = 0;
199  lock_query.l_pid = 0;
200 
201  int ret = fcntl(fd, F_GETLK, &lock_query);
202 
203  stringstream ss;
204  ss << endl;
205  if (ret == -1) {
206  ss << "fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << " errno[" << errno << "]: "
207  << strerror(errno) << endl;
208  }
209  else {
210  ss << "fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << endl;
211  }
212 
213  ss << "lock_info.l_len: " << lock_query.l_len << endl;
214  ss << "lock_info.l_pid: " << lock_query.l_pid << endl;
215  ss << "lock_info.l_start: " << lock_query.l_start << endl;
216 
217  string type;
218  switch (lock_query.l_type) {
219  case F_RDLCK:
220  type = "F_RDLCK";
221  break;
222  case F_WRLCK:
223  type = "F_WRLCK";
224  break;
225  case F_UNLCK:
226  type = "F_UNLCK";
227  break;
228 
229  }
230 
231  ss << "lock_info.l_type: " << type << endl;
232  ss << "lock_info.l_whence: " << lock_query.l_whence << endl;
233 
234  return ss.str();
235 }
236 
242 static void unlock(int fd)
243 {
244  if (fcntl(fd, F_SETLK, lock(F_UNLCK)) == -1) {
245  throw BESInternalError("An error occurred trying to unlock the file: " + get_errno(), __FILE__, __LINE__);
246  }
247 
248  BESDEBUG(LOCK_STATUS, "BESFileLockingCache::unlock() - lock status: " << lockStatus(fd) << endl);
249 
250  if (close(fd) == -1) throw BESInternalError("Could not close the (just) unlocked file.", __FILE__, __LINE__);
251 
252  BESDEBUG(LOCK, "BESFileLockingCache::unlock() - File Closed. fd: " << fd << endl);
253 }
254 
274 bool BESFileLockingCache::m_check_ctor_params()
275 {
276  // Should this really be a fatal error? What about just not
277  // using the cache in this case or writing out a warning message
278  // to the log. jhrg 10/23/15
279  //
280  // Yes, leave this as a fatal error and handle the case when cache_dir is
281  // empty in code that specializes this class. Those child classes are
282  // all singletons and their get_instance() methods need to return null
283  // when caching is turned off. You cannot do that here without throwing
284  // and we don't want to throw an exception for every call to a child's
285  // get_instance() method just because someone doesn't want to use a cache.
286  // jhrg 9/27/16
287  BESDEBUG(CACHE, "BESFileLockingCache::" <<__func__ << "() - BEGIN" << endl);
288 
289  if (d_cache_dir.empty()) {
290  BESDEBUG(CACHE, "BESFileLockingCache::" <<__func__ << "() - " <<
291  "The cache directory was not specified. CACHE IS DISABLED." << endl);
292 
293  disable();
294  return false;
295  }
296 
297 
298  int status = mkdir(d_cache_dir.c_str(), 0775);
299  // If there is an error and it's not that the dir already exists,
300  // throw an exception.
301  if (status == -1 && errno != EEXIST) {
302  string err = "The cache directory " + d_cache_dir + " could not be created: " + strerror(errno);
303  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
304  }
305 
306  if (d_prefix.empty()) {
307  string err = "The cache file prefix was not specified, must not be empty";
308  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
309  }
310 
311  // I changed this from '<=' to '<' since the code now uses a cache size
312  // of zero to indicate that the cache will never be purged. The other
313  // size-related methods all still work. Since the field is unsigned,
314  // testing for '< 0' is pointless. Later on in this code the value is capped
315  // at MAX_CACHE_SIZE_IN_MEGABYTES (set in this file), which is 2^44.
316  // jhrg 2.28.18
317 #if 0
318  if (d_max_cache_size_in_bytes < 0) {
319  string err = "The cache size was not specified, must be greater than zero";
320  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
321  }
322 #endif
323 
324  BESDEBUG(CACHE,
325  "BESFileLockingCache::" << __func__ << "() -" <<
326  " d_cache_dir: " << d_cache_dir <<
327  " d_prefix: " << d_prefix <<
328  " d_max_cache_size_in_bytes: " << d_max_cache_size_in_bytes << endl);
329 
330  enable();
331  return true;
332 }
333 
343 static bool createLockedFile(string file_name, int &ref_fd)
344 {
345  BESDEBUG(LOCK, "createLockedFile() - filename: " << file_name <<endl);
346 
347  int fd;
348  if ((fd = open(file_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666)) < 0) {
349  switch (errno) {
350  case EEXIST:
351  return false;
352 
353  default:
354  throw BESInternalError(file_name + ": " + get_errno(), __FILE__, __LINE__);
355  }
356  }
357 
358  struct flock *l = lock(F_WRLCK);
359  // F_SETLKW == set lock, blocking
360  if (fcntl(fd, F_SETLKW, l) == -1) {
361  close(fd);
362  ostringstream oss;
363  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
364  throw BESInternalError(oss.str(), __FILE__, __LINE__);
365  }
366 
367  BESDEBUG(LOCK, "createLockedFile exit: " << file_name <<endl);
368 
369  // Success
370  ref_fd = fd;
371  return true;
372 }
373 
383 bool BESFileLockingCache::m_initialize_cache_info()
384 {
385  BESDEBUG(CACHE, "BESFileLockingCache::m_initialize_cache_info() - BEGIN" << endl);
386 
387  // The value set in configuration files, etc., is the size in megabytes. The private
388  // variable holds the size in bytes (converted below).
389  d_max_cache_size_in_bytes = min(d_max_cache_size_in_bytes, MAX_CACHE_SIZE_IN_MEGABYTES);
390  d_max_cache_size_in_bytes *= BYTES_PER_MEG;
391  d_target_size = d_max_cache_size_in_bytes * 0.8;
392 
393  BESDEBUG(CACHE,
394  "BESFileLockingCache::m_initialize_cache_info() - d_max_cache_size_in_bytes: "
395  << d_max_cache_size_in_bytes << " d_target_size: "<<d_target_size<< endl);
396 
397  bool status = m_check_ctor_params(); // Throws BESError on error; otherwise sets the cache_enabled() property
398  if (status) {
399  d_cache_info = BESUtil::assemblePath(d_cache_dir, d_prefix + CACHE_CONTROL, true);
400 
401  BESDEBUG(CACHE, "BESFileLockingCache::m_initialize_cache_info() - d_cache_info: " << d_cache_info << endl);
402 
403  // See if we can create it. If so, that means it doesn't exist. So make it and
404  // set the cache initial size to zero.
405  if (createLockedFile(d_cache_info, d_cache_info_fd)) {
406  // initialize the cache size to zero
407  unsigned long long size = 0;
408  if (write(d_cache_info_fd, &size, sizeof(unsigned long long)) != sizeof(unsigned long long))
409  throw BESInternalError("Could not write size info to the cache info file `" + d_cache_info + "`",
410  __FILE__,
411  __LINE__);
412 
413  // This leaves the d_cache_info_fd file descriptor open
414  unlock_cache();
415  }
416  else {
417  if ((d_cache_info_fd = open(d_cache_info.c_str(), O_RDWR)) == -1) {
418  throw BESInternalError(get_errno(), __FILE__, __LINE__);
419  }
420  }
421 
422  BESDEBUG(CACHE,
423  "BESFileLockingCache::m_initialize_cache_info() - d_cache_info_fd: " << d_cache_info_fd << endl);
424  }
425 
426  BESDEBUG(CACHE,
427  "BESFileLockingCache::m_initialize_cache_info() - END [" << "CACHE IS " << (cache_enabled()?"ENABLED]":"DISABLED]") << endl);
428 
429  return status;
430 }
431 
432 static const string chars_excluded_from_filenames = "<>=,/()\\\"\':? []()$";
433 
448 string BESFileLockingCache::get_cache_file_name(const string &src, bool mangle)
449 {
450  // Old way of building String, retired 10/02/2015 - ndp
451  // Return d_cache_dir + "/" + d_prefix + BESFileLockingCache::DAP_CACHE_CHAR + target;
452  BESDEBUG(CACHE, __FUNCTION__ << " - src: '" << src << "' mangle: "<< mangle << endl);
453 
454  string target = get_cache_file_prefix() + src;
455 
456  if (mangle) {
457  string::size_type pos = target.find_first_of(chars_excluded_from_filenames);
458  while (pos != string::npos) {
459  target.replace(pos, 1, "#", 1);
460  pos = target.find_first_of(chars_excluded_from_filenames);
461  }
462  }
463 
464  if (target.length() > 254) {
465  ostringstream msg;
466  msg << "Cache filename is longer than 254 characters (name length: ";
467  msg << target.length() << ", name: " << target;
468  throw BESInternalError(msg.str(), __FILE__, __LINE__);
469  }
470 
471  target = BESUtil::assemblePath(get_cache_directory(), target, true);
472 
473  BESDEBUG(CACHE, __FUNCTION__ << " - target: '" << target << "'" << endl);
474 
475  return target;
476 }
477 
478 #if USE_GET_SHARED_LOCK
479 
492 static bool getSharedLock(const string &file_name, int &ref_fd)
493 {
494  BESDEBUG(LOCK, "getSharedLock(): Acquiring cache read lock for " << file_name <<endl);
495 
496  int fd;
497  if ((fd = open(file_name.c_str(), O_RDONLY)) < 0) {
498  switch (errno) {
499  case ENOENT:
500  return false;
501 
502  default:
503  throw BESInternalError(get_errno(), __FILE__, __LINE__);
504  }
505  }
506 
507  struct flock *l = lock(F_RDLCK);
508  if (fcntl(fd, F_SETLKW, l) == -1) {
509  close(fd);
510  ostringstream oss;
511  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
512  BESDEBUG(LOCK, "getSharedLock(): FAIL Could not get read lock for " << file_name << ": " << oss.str() << endl);
513  throw BESInternalError(oss.str(), __FILE__, __LINE__);
514  }
515 
516  BESDEBUG(LOCK, "getSharedLock(): SUCCESS Read Lock Acquired For " << file_name <<endl);
517 
518  // Success
519  ref_fd = fd;
520  return true;
521 }
522 #endif
523 
542 bool BESFileLockingCache::get_read_lock(const string &target, int &fd)
543 {
544  lock_cache_read();
545 
546  bool status = true;
547 
548 #if USE_GET_SHARED_LOCK
549  status = getSharedLock(target, fd);
550 
551  if (status) m_record_descriptor(target, fd);
552 #else
553  fd = m_find_descriptor(target);
554  // fd == -1 --> The file is not currently open
555  if ((fd == -1) && (fd = open(target.c_str(), O_RDONLY)) < 0) {
556  switch (errno) {
557  case ENOENT:
558  return false; // The file does not exist
559 
560  default:
561  throw BESInternalError(get_errno(), __FILE__, __LINE__);
562  }
563  }
564 
565  // The file might be open for writing, so setting a read lock is
566  // not possible.
567  struct flock *l = lock(F_RDLCK);
568  if (fcntl(fd, F_SETLKW, l) == -1) {
569  return false; // cannot get the lock
570  }
571 
572  m_record_descriptor(target, fd);
573 #endif
574 
575  unlock_cache();
576 
577  return status;
578 }
579 
597 bool BESFileLockingCache::create_and_lock(const string &target, int &fd)
598 {
600 
601  bool status = createLockedFile(target, fd);
602 
603  BESDEBUG(LOCK,
604  "BESFileLockingCache::create_and_lock() - " << target << " (status: " << status << ", fd: " << fd << ")" << endl);
605 
606  if (status) m_record_descriptor(target, fd);
607 
608  unlock_cache();
609 
610  return status;
611 }
612 
629 {
630  struct flock lock;
631  lock.l_type = F_RDLCK;
632  lock.l_whence = SEEK_SET;
633  lock.l_start = 0;
634  lock.l_len = 0;
635  lock.l_pid = getpid();
636 
637  if (fcntl(fd, F_SETLKW, &lock) == -1) {
638  throw BESInternalError(get_errno(), __FILE__, __LINE__);
639  }
640 
641  BESDEBUG(LOCK_STATUS, "BESFileLockingCache::exclusive_to_shared_lock() - lock status: " << lockStatus(fd) << endl);
642 }
643 
653 {
654  BESDEBUG(LOCK, "BESFileLockingCache::lock_cache_write() - d_cache_info_fd: " << d_cache_info_fd << endl);
655 
656  if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_WRLCK)) == -1) {
657  throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
658  __LINE__);
659  }
660 
661  BESDEBUG(LOCK_STATUS, "BESFileLockingCache::lock_cache_write() - lock status: " << lockStatus(d_cache_info_fd) << endl);
662 }
663 
668 {
669  BESDEBUG(LOCK, "BESFileLockingCache::lock_cache_read() - d_cache_info_fd: " << d_cache_info_fd << endl);
670 
671  if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_RDLCK)) == -1) {
672  throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
673  __LINE__);
674  }
675 
676  BESDEBUG(LOCK_STATUS, "BESFileLockingCache::lock_cache_read() - lock status: " << lockStatus(d_cache_info_fd) << endl);
677 }
678 
685 {
686  BESDEBUG(LOCK, "BESFileLockingCache::unlock_cache() - d_cache_info_fd: " << d_cache_info_fd << endl);
687 
688  if (fcntl(d_cache_info_fd, F_SETLK, lock(F_UNLCK)) == -1) {
689  throw BESInternalError("An error occurred trying to unlock the cache-control file" + get_errno(), __FILE__,
690  __LINE__);
691  }
692 
693  BESDEBUG(LOCK_STATUS, "BESFileLockingCache::unlock_cache() - lock status: " << lockStatus(d_cache_info_fd) << endl);
694 }
695 
711 void BESFileLockingCache::unlock_and_close(const string &file_name)
712 {
713  BESDEBUG(LOCK, "BESFileLockingCache::unlock_and_close() - BEGIN file: " << file_name << endl);
714 
715  int fd = m_remove_descriptor(file_name); // returns -1 when no more files desp. remain
716  while (fd != -1) {
717  unlock(fd);
718  fd = m_remove_descriptor(file_name);
719  }
720 
721  BESDEBUG(LOCK_STATUS, "BESFileLockingCache::unlock_and_close() - lock status: " << lockStatus(d_cache_info_fd) << endl);
722  BESDEBUG(LOCK, "BESFileLockingCache::unlock_and_close() - END"<< endl);
723 }
724 
735 unsigned long long BESFileLockingCache::update_cache_info(const string &target)
736 {
737  unsigned long long current_size;
738  try {
740 
741  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
742  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
743 
744  // read the size from the cache info file
745  if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
746  throw BESInternalError("Could not get read size info from the cache info file!", __FILE__, __LINE__);
747 
748  struct stat buf;
749  int statret = stat(target.c_str(), &buf);
750  if (statret == 0)
751  current_size += buf.st_size;
752  else
753  throw BESInternalError("Could not read the size of the new file: " + target + " : " + get_errno(), __FILE__,
754  __LINE__);
755 
756  BESDEBUG(CACHE, "BESFileLockingCache::update_cache_info() - cache size updated to: " << current_size << endl);
757 
758  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
759  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
760 
761  if (write(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
762  throw BESInternalError("Could not write size info from the cache info file!", __FILE__, __LINE__);
763 
764  unlock_cache();
765  }
766  catch (...) {
767  unlock_cache();
768  throw;
769  }
770 
771  return current_size;
772 }
773 
778 bool BESFileLockingCache::cache_too_big(unsigned long long current_size) const
779 {
780  return current_size > d_max_cache_size_in_bytes;
781 }
782 
791 {
792  unsigned long long current_size;
793  try {
794  lock_cache_read();
795 
796  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
797  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
798  // read the size from the cache info file
799  if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
800  throw BESInternalError("Could not get read size info from the cache info file!", __FILE__, __LINE__);
801 
802  unlock_cache();
803  }
804  catch (...) {
805  unlock_cache();
806  throw;
807  }
808 
809  return current_size;
810 }
811 
812 static bool entry_op(cache_entry &e1, cache_entry &e2)
813 {
814  return e1.time < e2.time;
815 }
816 
818 unsigned long long BESFileLockingCache::m_collect_cache_dir_info(CacheFiles &contents)
819 {
820  DIR *dip = opendir(d_cache_dir.c_str());
821  if (!dip) throw BESInternalError("Unable to open cache directory " + d_cache_dir, __FILE__, __LINE__);
822 
823  struct dirent *dit;
824  vector<string> files;
825  // go through the cache directory and collect all of the files that
826  // start with the matching prefix
827  while ((dit = readdir(dip)) != NULL) {
828  string dirEntry = dit->d_name;
829  if (dirEntry.compare(0, d_prefix.length(), d_prefix) == 0 && dirEntry != d_cache_info) {
830  files.push_back(d_cache_dir + "/" + dirEntry);
831  }
832  }
833 
834  closedir(dip);
835 
836  unsigned long long current_size = 0;
837  struct stat buf;
838  for (vector<string>::iterator file = files.begin(); file != files.end(); ++file) {
839  if (stat(file->c_str(), &buf) == 0) {
840  current_size += buf.st_size;
841  cache_entry entry;
842  entry.name = *file;
843  entry.size = buf.st_size;
844  entry.time = buf.st_atime;
845  // Sanity check; Removed after initial testing since some files might be zero bytes
846 #if 0
847  if (entry.size == 0)
848  throw BESInternalError("Zero-byte file found in cache. " + *file, __FILE__, __LINE__);
849 #endif
850  contents.push_back(entry);
851  }
852  }
853 
854  // Sort so smaller (older) times are first.
855  contents.sort(entry_op);
856 
857  return current_size;
858 }
859 
876 static bool getExclusiveLockNB(string file_name, int &ref_fd)
877 {
878  BESDEBUG(LOCK, "getExclusiveLock_nonblocking: " << file_name <<endl);
879 
880  int fd;
881  if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
882  switch (errno) {
883  case ENOENT:
884  return false;
885 
886  default:
887  throw BESInternalError(get_errno(), __FILE__, __LINE__);
888  }
889  }
890 
891  struct flock *l = lock(F_WRLCK);
892  if (fcntl(fd, F_SETLK, l) == -1) {
893  switch (errno) {
894  case EAGAIN:
895  case EACCES:
896  BESDEBUG(LOCK,
897  "getExclusiveLockNB exit (false): " << file_name << " by: " << l->l_pid << endl);
898  close(fd);
899  return false;
900 
901  default: {
902  close(fd);
903  ostringstream oss;
904  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
905  throw BESInternalError(oss.str(), __FILE__, __LINE__);
906  }
907  }
908  }
909 
910  BESDEBUG(LOCK, "getExclusiveLock_nonblocking exit (true): " << file_name <<endl);
911 
912  // Success
913  ref_fd = fd;
914  return true;
915 }
916 
932 void BESFileLockingCache::update_and_purge(const string &new_file)
933 {
934  BESDEBUG(CACHE, "purge - starting the purge" << endl);
935 
936  if (is_unlimited()) {
937  BESDEBUG(CACHE, "purge - unlimited so no need to purge." << endl);
938  return;
939  }
940 
941  try {
943 
944  CacheFiles contents;
945  unsigned long long computed_size = m_collect_cache_dir_info(contents);
946 #if 0
947  if (BESISDEBUG( "cache_contents" )) {
948  BESDEBUG(CACHE, "BEFORE Purge " << computed_size/BYTES_PER_MEG << endl );
949  CacheFiles::iterator ti = contents.begin();
950  CacheFiles::iterator te = contents.end();
951  for (; ti != te; ti++) {
952  BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
953  }
954  }
955 #endif
956  BESDEBUG(CACHE,
957  "BESFileLockingCache::update_and_purge() - current and target size (in MB) "
958  << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
959 
960  // This deletes files and updates computed_size
961  if (cache_too_big(computed_size)) {
962 
963  // d_target_size is 80% of the maximum cache size.
964  // Grab the first which is the oldest in terms of access time.
965  CacheFiles::iterator i = contents.begin();
966  while (i != contents.end() && computed_size > d_target_size) {
967  // Grab an exclusive lock but do not block - if another process has the file locked
968  // just move on to the next file. Also test to see if the current file is the file
969  // this process just added to the cache - don't purge that!
970  int cfile_fd;
971  if (i->name != new_file && getExclusiveLockNB(i->name, cfile_fd)) {
972  BESDEBUG(CACHE, "purge: " << i->name << " removed." << endl);
973 
974  if (unlink(i->name.c_str()) != 0)
975  throw BESInternalError(
976  "Unable to purge the file " + i->name + " from the cache: " + get_errno(), __FILE__,
977  __LINE__);
978 
979  unlock(cfile_fd);
980  computed_size -= i->size;
981  }
982  ++i;
983 
984  BESDEBUG(CACHE,
985  "BESFileLockingCache::update_and_purge() - current and target size (in MB) "
986  << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
987  }
988  }
989 
990  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
991  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
992 
993  if (write(d_cache_info_fd, &computed_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
994  throw BESInternalError("Could not write size info to the cache info file!", __FILE__, __LINE__);
995 #if 0
996  if (BESISDEBUG( "cache_contents" )) {
997  contents.clear();
998  computed_size = m_collect_cache_dir_info(contents);
999  BESDEBUG(CACHE, "AFTER Purge " << computed_size/BYTES_PER_MEG << endl );
1000  CacheFiles::iterator ti = contents.begin();
1001  CacheFiles::iterator te = contents.end();
1002  for (; ti != te; ti++) {
1003  BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
1004  }
1005  }
1006 #endif
1007  unlock_cache();
1008  }
1009  catch (...) {
1010  unlock_cache();
1011  throw;
1012  }
1013 }
1014 
1031 static bool getExclusiveLock(string file_name, int &ref_fd)
1032 {
1033  BESDEBUG(LOCK, "BESFileLockingCache::getExclusiveLock() - " << file_name <<endl);
1034 
1035  int fd;
1036  if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
1037  switch (errno) {
1038  case ENOENT:
1039  return false;
1040 
1041  default:
1042  BESDEBUG(LOCK, __func__ << "() - FAILED to open file: " << file_name << endl);
1043  throw BESInternalError(get_errno(), __FILE__, __LINE__);
1044  }
1045  }
1046 
1047  struct flock *l = lock(F_WRLCK);
1048  if (fcntl(fd, F_SETLKW, l) == -1) { // F_SETLKW == blocking lock
1049  close(fd);
1050  ostringstream oss;
1051  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
1052  throw BESInternalError(oss.str(), __FILE__, __LINE__);
1053  }
1054 
1055  BESDEBUG(LOCK, "BESFileLockingCache::getExclusiveLock() - exit: " << file_name <<endl);
1056 
1057  // Success
1058  ref_fd = fd;
1059  return true;
1060 }
1061 
1072 void BESFileLockingCache::purge_file(const string &file)
1073 {
1074  BESDEBUG(CACHE, "BESFileLockingCache::purge_file() - starting the purge" << endl);
1075 
1076  try {
1077  lock_cache_write();
1078 
1079  // Grab an exclusive lock on the file
1080  int cfile_fd;
1081  if (getExclusiveLock(file, cfile_fd)) {
1082  // Get the file's size
1083  unsigned long long size = 0;
1084  struct stat buf;
1085  if (stat(file.c_str(), &buf) == 0) {
1086  size = buf.st_size;
1087  }
1088 
1089  BESDEBUG(CACHE, "BESFileLockingCache::purge_file() - " << file << " removed." << endl);
1090 
1091  if (unlink(file.c_str()) != 0)
1092  throw BESInternalError("Unable to purge the file " + file + " from the cache: " + get_errno(), __FILE__,
1093  __LINE__);
1094 
1095  unlock(cfile_fd);
1096 
1097  unsigned long long cache_size = get_cache_size() - size;
1098 
1099  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
1100  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
1101 
1102  if (write(d_cache_info_fd, &cache_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
1103  throw BESInternalError("Could not write size info to the cache info file!", __FILE__, __LINE__);
1104  }
1105 
1106  unlock_cache();
1107  }
1108  catch (...) {
1109  unlock_cache();
1110  throw;
1111  }
1112 }
1113 
1123 bool BESFileLockingCache::dir_exists(const string &dir)
1124 {
1125  struct stat buf;
1126 
1127  return (stat(dir.c_str(), &buf) == 0) && (buf.st_mode & S_IFDIR);
1128 }
1129 
1138 void BESFileLockingCache::dump(ostream &strm) const
1139 {
1140  strm << BESIndent::LMarg << "BESFileLockingCache::dump - (" << (void *) this << ")" << endl;
1141  BESIndent::Indent();
1142  strm << BESIndent::LMarg << "cache dir: " << d_cache_dir << endl;
1143  strm << BESIndent::LMarg << "prefix: " << d_prefix << endl;
1144  strm << BESIndent::LMarg << "size (bytes): " << d_max_cache_size_in_bytes << endl;
1145  BESIndent::UnIndent();
1146 }
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.
exception thrown if inernal error encountered
const std::string get_cache_file_prefix()
virtual void update_and_purge(const std::string &new_file)
Purge files from the cache.
virtual unsigned long long update_cache_info(const std::string &target)
Update the cache info file to include 'target'.
void disable()
Disable the cache.
bool is_unlimited() const
Is this cache allowed to store as much as it wants?
static bool dir_exists(const std::string &dir)
virtual void unlock_and_close(const std::string &target)
virtual std::string get_cache_file_name(const std::string &src, bool mangle=true)
virtual unsigned long long get_cache_size()
Get the cache size.
Abstract exception class for the BES with basic string message.
Definition: BESError.h:58
virtual bool create_and_lock(const std::string &target, int &fd)
Create a file in the cache and lock it for write access.
const std::string get_cache_directory()
void initialize(const std::string &cache_dir, const std::string &prefix, unsigned long long size)
Initialize an instance of FileLockingCache.
virtual void lock_cache_write()
virtual void dump(ostream &strm) const
dumps information about this object
void enable()
Enable the cache.
virtual void lock_cache_read()
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 string assemblePath(const string &firstPart, const string &secondPart, bool leadingSlash=false, bool trailingSlash=false)
Assemble path fragments making sure that they are separated by a single '/' character.
Definition: BESUtil.cc:818
virtual void purge_file(const std::string &file)
Purge a single file from the cache.