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