bes  Updated for version 3.20.10
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, prolog << "Recording descriptor: " << file << ", " << fd << endl);
153 
154  d_locks.insert(std::pair<string, int>(file, fd));
155 }
156 
157 inline int BESFileLockingCache::m_remove_descriptor(const string &file)
158 {
159  BESDEBUG(LOCK, prolog << "d_locks size: " << d_locks.size() << endl);
160 
161  FilesAndLockDescriptors::iterator i = d_locks.find(file);
162  if (i == d_locks.end()) return -1;
163 
164  int fd = i->second;
165  d_locks.erase(i);
166 
167  BESDEBUG(LOCK, prolog << "Found file descriptor [" << fd << "] for file: " << file << endl);
168 
169  return fd;
170 }
171 
172 #if USE_GET_SHARED_LOCK
173 inline int BESFileLockingCache::m_find_descriptor(const string &file)
174 {
175  BESDEBUG(LOCK, prolog << "d_locks size: " << d_locks.size() << endl);
176 
177  FilesAndLockDescriptors::iterator i = d_locks.find(file);
178  if (i == d_locks.end()) return -1;
179 
180  BESDEBUG(LOCK, prolog << "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(prolog + "An error occurred trying to unlock the file: " + get_errno(), __FILE__, __LINE__);
246  }
247 
248  BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(fd) << endl);
249 
250  if (close(fd) == -1) throw BESInternalError(prolog + "Could not close the (just) unlocked file.", __FILE__, __LINE__);
251 
252  BESDEBUG(LOCK, prolog << "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, prolog << "BEGIN" << endl);
288 
289  if (d_cache_dir.empty()) {
290  BESDEBUG(CACHE, prolog << "The cache directory was not specified. CACHE IS DISABLED." << endl);
291 
292  disable();
293  return false;
294  }
295 
296 
297  int status = mkdir(d_cache_dir.c_str(), 0775);
298  // If there is an error and it's not that the dir already exists,
299  // throw an exception.
300  if (status == -1 && errno != EEXIST) {
301  string err = prolog + "The cache directory " + d_cache_dir + " could not be created: " + strerror(errno);
302  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
303  }
304 
305  if (d_prefix.empty()) {
306  string err = prolog + "The cache file prefix was not specified, must not be empty";
307  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
308  }
309 
310  // I changed this from '<=' to '<' since the code now uses a cache size
311  // of zero to indicate that the cache will never be purged. The other
312  // size-related methods all still work. Since the field is unsigned,
313  // testing for '< 0' is pointless. Later on in this code the value is capped
314  // at MAX_CACHE_SIZE_IN_MEGABYTES (set in this file), which is 2^44.
315  // jhrg 2.28.18
316 #if 0
317  if (d_max_cache_size_in_bytes < 0) {
318  string err = "The cache size was not specified, must be greater than zero";
319  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
320  }
321 #endif
322 
323  BESDEBUG(CACHE,
324  "BESFileLockingCache::" << __func__ << "() -" <<
325  " d_cache_dir: " << d_cache_dir <<
326  " d_prefix: " << d_prefix <<
327  " d_max_cache_size_in_bytes: " << d_max_cache_size_in_bytes << endl);
328 
329  enable();
330  return true;
331 }
332 
342 static bool createLockedFile(const string &file_name, int &ref_fd)
343 {
344  BESDEBUG(LOCK, prolog << "BEGIN file: " << file_name <<endl);
345 
346  int fd;
347  if ((fd = open(file_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666)) < 0) {
348  switch (errno) {
349  case EEXIST:
350  return false;
351 
352  default:
353  throw BESInternalError(file_name + ": " + get_errno(), __FILE__, __LINE__);
354  }
355  }
356 
357  struct flock *l = lock(F_WRLCK);
358  // F_SETLKW == set lock, blocking
359  if (fcntl(fd, F_SETLKW, l) == -1) {
360  close(fd);
361  ostringstream oss;
362  oss << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
363  throw BESInternalError(oss.str(), __FILE__, __LINE__);
364  }
365 
366  BESDEBUG(LOCK, prolog << "END file: " << file_name <<endl);
367 
368  // Success
369  ref_fd = fd;
370  return true;
371 }
372 
382 bool BESFileLockingCache::m_initialize_cache_info()
383 {
384  BESDEBUG(CACHE, prolog << "BEGIN" << endl);
385 
386  // The value set in configuration files, etc., is the size in megabytes. The private
387  // variable holds the size in bytes (converted below).
388  d_max_cache_size_in_bytes = min(d_max_cache_size_in_bytes, MAX_CACHE_SIZE_IN_MEGABYTES);
389  d_max_cache_size_in_bytes *= BYTES_PER_MEG;
390  d_target_size = d_max_cache_size_in_bytes * 0.8;
391 
392  BESDEBUG(CACHE, prolog << "d_max_cache_size_in_bytes: "
393  << d_max_cache_size_in_bytes << " d_target_size: "<<d_target_size<< endl);
394 
395  bool status = m_check_ctor_params(); // Throws BESError on error; otherwise sets the cache_enabled() property
396  if (status) {
397  d_cache_info = BESUtil::assemblePath(d_cache_dir, d_prefix + CACHE_CONTROL, true);
398 
399  BESDEBUG(CACHE, prolog << "d_cache_info: " << d_cache_info << endl);
400 
401  // See if we can create it. If so, that means it doesn't exist. So make it and
402  // set the cache initial size to zero.
403  if (createLockedFile(d_cache_info, d_cache_info_fd)) {
404  // initialize the cache size to zero
405  unsigned long long size = 0;
406  if (write(d_cache_info_fd, &size, sizeof(unsigned long long)) != sizeof(unsigned long long))
407  throw BESInternalError(prolog + "Could not write size info to the cache info file `" + d_cache_info + "`",
408  __FILE__,
409  __LINE__);
410 
411  // This leaves the d_cache_info_fd file descriptor open
412  unlock_cache();
413  }
414  else {
415  if ((d_cache_info_fd = open(d_cache_info.c_str(), O_RDWR)) == -1) {
416  throw BESInternalError(prolog + "Failed to open cache info file: " + d_cache_info + " errno: " + get_errno(), __FILE__, __LINE__);
417  }
418  }
419 
420  BESDEBUG(CACHE,prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
421  }
422 
423  BESDEBUG(CACHE,prolog << "END [" << "CACHE IS " << (cache_enabled()?"ENABLED]":"DISABLED]") << endl);
424 
425  return status;
426 }
427 
428 static const string chars_excluded_from_filenames = "<>=,/()\\\"\':? []()$";
429 
444 string BESFileLockingCache::get_cache_file_name(const string &src, bool mangle)
445 {
446  // Old way of building String, retired 10/02/2015 - ndp
447  // Return d_cache_dir + "/" + d_prefix + BESFileLockingCache::DAP_CACHE_CHAR + target;
448  BESDEBUG(CACHE, prolog << "src: '" << src << "' mangle: "<< mangle << endl);
449 
450  string target = get_cache_file_prefix() + src;
451 
452  if (mangle) {
453  string::size_type pos = target.find_first_of(chars_excluded_from_filenames);
454  while (pos != string::npos) {
455  target.replace(pos, 1, "#", 1);
456  pos = target.find_first_of(chars_excluded_from_filenames);
457  }
458  }
459 
460  if (target.length() > 254) {
461  ostringstream msg;
462  msg << prolog << "Cache filename is longer than 254 characters (name length: ";
463  msg << target.length() << ", name: " << target;
464  throw BESInternalError(msg.str(), __FILE__, __LINE__);
465  }
466 
467  target = BESUtil::assemblePath(get_cache_directory(), target, true);
468 
469  BESDEBUG(CACHE, prolog << "target: '" << target << "'" << endl);
470 
471  return target;
472 }
473 
474 #if USE_GET_SHARED_LOCK
488 static bool getSharedLock(const string &file_name, int &ref_fd)
489 {
490  BESDEBUG(LOCK, prolog << "Acquiring cache read lock for " << file_name <<endl);
491 
492  int fd;
493  if ((fd = open(file_name.c_str(), O_RDONLY)) < 0) {
494  switch (errno) {
495  case ENOENT:
496  return false;
497 
498  default:
499  throw BESInternalError(get_errno(), __FILE__, __LINE__);
500  }
501  }
502 
503  struct flock *l = lock(F_RDLCK);
504  if (fcntl(fd, F_SETLKW, l) == -1) {
505  close(fd);
506  ostringstream oss;
507  oss << prolog << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
508  throw BESInternalError(oss.str(), __FILE__, __LINE__);
509  }
510 
511  BESDEBUG(LOCK, prolog << "SUCCESS Read Lock Acquired For " << file_name <<endl);
512 
513  // Success
514  ref_fd = fd;
515  return true;
516 }
517 #endif
518 
537 bool BESFileLockingCache::get_read_lock(const string &target, int &fd)
538 {
539  lock_cache_read();
540 
541  bool status = true;
542 
543 #if USE_GET_SHARED_LOCK
544  status = getSharedLock(target, fd);
545 
546  if (status) m_record_descriptor(target, fd);
547 #else
548  fd = m_find_descriptor(target);
549  // fd == -1 --> The file is not currently open
550  if ((fd == -1) && (fd = open(target.c_str(), O_RDONLY)) < 0) {
551  switch (errno) {
552  case ENOENT:
553  return false; // The file does not exist
554 
555  default:
556  throw BESInternalError(get_errno(), __FILE__, __LINE__);
557  }
558  }
559 
560  // The file might be open for writing, so setting a read lock is
561  // not possible.
562  struct flock *l = lock(F_RDLCK);
563  if (fcntl(fd, F_SETLKW, l) == -1) {
564  return false; // cannot get the lock
565  }
566 
567  m_record_descriptor(target, fd);
568 #endif
569 
570  unlock_cache();
571 
572  return status;
573 }
574 
592 bool BESFileLockingCache::create_and_lock(const string &target, int &fd)
593 {
595 
596  bool status = createLockedFile(target, fd);
597 
598  BESDEBUG(LOCK, prolog << "target: " << target << " (status: " << status << ", fd: " << fd << ")" << endl);
599 
600  if (status) m_record_descriptor(target, fd);
601 
602  unlock_cache();
603 
604  return status;
605 }
606 
623 {
624  struct flock lock;
625  lock.l_type = F_RDLCK;
626  lock.l_whence = SEEK_SET;
627  lock.l_start = 0;
628  lock.l_len = 0;
629  lock.l_pid = getpid();
630 
631  if (fcntl(fd, F_SETLKW, &lock) == -1) {
632  throw BESInternalError(get_errno(), __FILE__, __LINE__);
633  }
634 
635  BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(fd) << endl);
636 }
637 
647 {
648  BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
649 
650  if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_WRLCK)) == -1) {
651  throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
652  __LINE__);
653  }
654 
655  BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
656 }
657 
662 {
663  BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
664 
665  if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_RDLCK)) == -1) {
666  throw BESInternalError(prolog + "An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
667  __LINE__);
668  }
669 
670  BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
671 }
672 
679 {
680  BESDEBUG(LOCK, prolog << "d_cache_info_fd: " << d_cache_info_fd << endl);
681 
682  if (fcntl(d_cache_info_fd, F_SETLK, lock(F_UNLCK)) == -1) {
683  throw BESInternalError(prolog +"An error occurred trying to unlock the cache-control file" + get_errno(), __FILE__,
684  __LINE__);
685  }
686 
687  BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
688 }
689 
705 void BESFileLockingCache::unlock_and_close(const string &file_name)
706 {
707  BESDEBUG(LOCK, prolog << "BEGIN file: " << file_name << endl);
708 
709  int fd = m_remove_descriptor(file_name); // returns -1 when no more files desp. remain
710  while (fd != -1) {
711  unlock(fd);
712  fd = m_remove_descriptor(file_name);
713  }
714 
715  BESDEBUG(LOCK_STATUS, prolog << "lock status: " << lockStatus(d_cache_info_fd) << endl);
716  BESDEBUG(LOCK, prolog << "END"<< endl);
717 }
718 
729 unsigned long long BESFileLockingCache::update_cache_info(const string &target)
730 {
731  unsigned long long current_size;
732  try {
734 
735  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
736  throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
737 
738  // read the size from the cache info file
739  if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
740  throw BESInternalError(prolog + "Could not get read size info from the cache info file!", __FILE__, __LINE__);
741 
742  struct stat buf;
743  int statret = stat(target.c_str(), &buf);
744  if (statret == 0)
745  current_size += buf.st_size;
746  else
747  throw BESInternalError(prolog + "Could not read the size of the new file: " + target + " : " + get_errno(), __FILE__,
748  __LINE__);
749 
750  BESDEBUG(CACHE, prolog << "cache size updated to: " << current_size << endl);
751 
752  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
753  throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
754 
755  if (write(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
756  throw BESInternalError(prolog + "Could not write size info from the cache info file!", __FILE__, __LINE__);
757 
758  unlock_cache();
759  }
760  catch (...) {
761  unlock_cache();
762  throw;
763  }
764 
765  return current_size;
766 }
767 
772 bool BESFileLockingCache::cache_too_big(unsigned long long current_size) const
773 {
774  return current_size > d_max_cache_size_in_bytes;
775 }
776 
785 {
786  unsigned long long current_size;
787  try {
788  lock_cache_read();
789 
790  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
791  throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
792  // read the size from the cache info file
793  if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
794  throw BESInternalError(prolog + "Could not get read size info from the cache info file!", __FILE__, __LINE__);
795 
796  unlock_cache();
797  }
798  catch (...) {
799  unlock_cache();
800  throw;
801  }
802 
803  return current_size;
804 }
805 
806 static bool entry_op(cache_entry &e1, cache_entry &e2)
807 {
808  return e1.time < e2.time;
809 }
810 
812 unsigned long long BESFileLockingCache::m_collect_cache_dir_info(CacheFiles &contents)
813 {
814  DIR *dip = opendir(d_cache_dir.c_str());
815  if (!dip) throw BESInternalError(prolog + "Unable to open cache directory " + d_cache_dir, __FILE__, __LINE__);
816 
817  struct dirent *dit;
818  vector<string> files;
819  // go through the cache directory and collect all of the files that
820  // start with the matching prefix
821  while ((dit = readdir(dip)) != NULL) {
822  string dirEntry = dit->d_name;
823  if (dirEntry.compare(0, d_prefix.length(), d_prefix) == 0 && dirEntry != d_cache_info) {
824  files.push_back(d_cache_dir + "/" + dirEntry);
825  }
826  }
827 
828  closedir(dip);
829 
830  unsigned long long current_size = 0;
831  struct stat buf;
832  for (vector<string>::iterator file = files.begin(); file != files.end(); ++file) {
833  if (stat(file->c_str(), &buf) == 0) {
834  current_size += buf.st_size;
835  cache_entry entry;
836  entry.name = *file;
837  entry.size = buf.st_size;
838  entry.time = buf.st_atime;
839  // Sanity check; Removed after initial testing since some files might be zero bytes
840 #if 0
841  if (entry.size == 0)
842  throw BESInternalError("Zero-byte file found in cache. " + *file, __FILE__, __LINE__);
843 #endif
844  contents.push_back(entry);
845  }
846  }
847 
848  // Sort so smaller (older) times are first.
849  contents.sort(entry_op);
850 
851  return current_size;
852 }
853 
870 bool BESFileLockingCache::get_exclusive_lock_nb(const string &file_name, int &ref_fd)
871 {
872  BESDEBUG(LOCK, prolog << "BEGIN filename: " << file_name <<endl);
873 
874  int fd;
875  if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
876  switch (errno) {
877  case ENOENT:
878  return false;
879 
880  default:
881  throw BESInternalError(prolog + "errno: " + get_errno(), __FILE__, __LINE__);
882  }
883  }
884 
885  struct flock *l = lock(F_WRLCK);
886  if (fcntl(fd, F_SETLK, l) == -1) {
887  switch (errno) {
888  case EAGAIN:
889  case EACCES:
890  BESDEBUG(LOCK,prolog << "exit (false): " << file_name << " by: " << l->l_pid << endl);
891  close(fd);
892  return false;
893 
894  default: {
895  close(fd);
896  ostringstream oss;
897  oss << prolog << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
898  throw BESInternalError(oss.str(), __FILE__, __LINE__);
899  }
900  }
901  }
902 
903 
904  // Success
905  m_record_descriptor(file_name,fd);
906  ref_fd = fd;
907  BESDEBUG(LOCK, prolog << "END filename: " << file_name <<endl);
908  return true;
909 }
910 
911 
927 void BESFileLockingCache::update_and_purge(const string &new_file)
928 {
929  BESDEBUG(CACHE, prolog << "Starting the purge" << endl);
930 
931  if (is_unlimited()) {
932  BESDEBUG(CACHE, prolog << "Size is set to unlimited, so no need to purge." << endl);
933  return;
934  }
935 
936  try {
938 
939  CacheFiles contents;
940  unsigned long long computed_size = m_collect_cache_dir_info(contents);
941 #if 0
942  if (BESISDEBUG( "cache_contents" )) {
943  BESDEBUG(CACHE, "BEFORE Purge " << computed_size/BYTES_PER_MEG << endl );
944  CacheFiles::iterator ti = contents.begin();
945  CacheFiles::iterator te = contents.end();
946  for (; ti != te; ti++) {
947  BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
948  }
949  }
950 #endif
951  BESDEBUG(CACHE, prolog << "Current and target size (in MB) "
952  << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
953 
954  // This deletes files and updates computed_size
955  if (cache_too_big(computed_size)) {
956 
957  // d_target_size is 80% of the maximum cache size.
958  // Grab the first which is the oldest in terms of access time.
959  CacheFiles::iterator i = contents.begin();
960  while (i != contents.end() && computed_size > d_target_size) {
961  // Grab an exclusive lock but do not block - if another process has the file locked
962  // just move on to the next file. Also test to see if the current file is the file
963  // this process just added to the cache - don't purge that!
964  int cfile_fd;
965  if (i->name != new_file && get_exclusive_lock_nb(i->name, cfile_fd)) {
966  BESDEBUG(CACHE, prolog << "purge: " << i->name << " removed." << endl);
967 
968  if (unlink(i->name.c_str()) != 0)
969  throw BESInternalError(
970  prolog + "Unable to purge the file " + i->name + " from the cache: " + get_errno(), __FILE__,
971  __LINE__);
972 
973  unlock(cfile_fd);
974  computed_size -= i->size;
975  }
976  ++i;
977 
978  BESDEBUG(CACHE,prolog << "Current and target size (in MB) "
979  << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
980  }
981  }
982 
983  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
984  throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
985 
986  if (write(d_cache_info_fd, &computed_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
987  throw BESInternalError(prolog + "Could not write size info to the cache info file!", __FILE__, __LINE__);
988 #if 0
989  if (BESISDEBUG( "cache_contents" )) {
990  contents.clear();
991  computed_size = m_collect_cache_dir_info(contents);
992  BESDEBUG(CACHE, "AFTER Purge " << computed_size/BYTES_PER_MEG << endl );
993  CacheFiles::iterator ti = contents.begin();
994  CacheFiles::iterator te = contents.end();
995  for (; ti != te; ti++) {
996  BESDEBUG(CACHE, (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
997  }
998  }
999 #endif
1000  unlock_cache();
1001  }
1002  catch (...) {
1003  unlock_cache();
1004  throw;
1005  }
1006 }
1007 
1024 bool BESFileLockingCache::get_exclusive_lock(const string &file_name, int &ref_fd)
1025 {
1026  BESDEBUG(LOCK, prolog << "BEGIN filename: " << file_name <<endl);
1027 
1028  int fd;
1029  if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
1030  switch (errno) {
1031  case ENOENT:
1032  return false;
1033 
1034  default:
1035  {
1036  stringstream msg;
1037  msg << prolog << "FAILED to open file: " << file_name << " errno: " << get_errno();
1038  BESDEBUG(LOCK, msg.str() << endl);
1039  throw BESInternalError(msg.str() , __FILE__, __LINE__);
1040  }
1041  }
1042  }
1043 
1044  struct flock *l = lock(F_WRLCK);
1045  if (fcntl(fd, F_SETLKW, l) == -1) { // F_SETLKW == blocking lock
1046  close(fd);
1047  ostringstream oss;
1048  oss << "cache process: " << l->l_pid << " triggered a locking error for '" << file_name << "': " << get_errno();
1049  throw BESInternalError(oss.str(), __FILE__, __LINE__);
1050  }
1051 
1052  // Success
1053  m_record_descriptor(file_name,fd);
1054  ref_fd = fd;
1055 
1056  BESDEBUG(LOCK, prolog << "END filename: " << file_name <<endl);
1057  return true;
1058 }
1059 
1070 void BESFileLockingCache::purge_file(const string &file)
1071 {
1072  BESDEBUG(CACHE, prolog << "Starting the purge" << endl);
1073 
1074  try {
1075  lock_cache_write();
1076 
1077  // Grab an exclusive lock on the file
1078  int cfile_fd;
1079  if (get_exclusive_lock(file, cfile_fd)) {
1080  // Get the file's size
1081  unsigned long long size = 0;
1082  struct stat buf;
1083  if (stat(file.c_str(), &buf) == 0) {
1084  size = buf.st_size;
1085  }
1086 
1087  BESDEBUG(CACHE, prolog << "file: " << file << " removed." << endl);
1088 
1089  if (unlink(file.c_str()) != 0)
1090  throw BESInternalError(prolog + "Unable to purge the file " + file + " from the cache: " + get_errno(), __FILE__,
1091  __LINE__);
1092 
1093  unlock(cfile_fd);
1094 
1095  unsigned long long cache_size = get_cache_size() - size;
1096 
1097  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
1098  throw BESInternalError(prolog + "Could not rewind to front of cache info file.", __FILE__, __LINE__);
1099 
1100  if (write(d_cache_info_fd, &cache_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
1101  throw BESInternalError(prolog + "Could not write size info to the cache info file!", __FILE__, __LINE__);
1102  }
1103 
1104  unlock_cache();
1105  }
1106  catch (...) {
1107  unlock_cache();
1108  throw;
1109  }
1110 }
1111 
1121 bool BESFileLockingCache::dir_exists(const string &dir)
1122 {
1123  struct stat buf;
1124 
1125  return (stat(dir.c_str(), &buf) == 0) && (buf.st_mode & S_IFDIR);
1126 }
1127 
1136 void BESFileLockingCache::dump(ostream &strm) const
1137 {
1138  strm << BESIndent::LMarg << prolog << "(" << (void *) this << ")" << endl;
1139  BESIndent::Indent();
1140  strm << BESIndent::LMarg << "cache dir: " << d_cache_dir << endl;
1141  strm << BESIndent::LMarg << "prefix: " << d_prefix << endl;
1142  strm << BESIndent::LMarg << "size (bytes): " << d_max_cache_size_in_bytes << endl;
1143  BESIndent::UnIndent();
1144 }
Abstract exception class for the BES with basic string message.
Definition: BESError.h:58
virtual void dump(std::ostream &strm) const
dumps information about this object
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)
const std::string get_cache_directory()
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'.
virtual void lock_cache_write()
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)
const std::string get_cache_file_prefix()
virtual void lock_cache_read()
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)
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:840