bes  Updated for version 3.19.1
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 using namespace std;
56 
57 // conversion factor
58 static const unsigned long long BYTES_PER_MEG = 1048576ULL;
59 
60 // Max cache size in megs, so we can check the user input and warn.
61 // 2^64 / 2^20 == 2^44
62 static const unsigned long long MAX_CACHE_SIZE_IN_MEGABYTES = (1ULL << 44);
63 
77 BESFileLockingCache::BESFileLockingCache(const string &cache_dir, const string &prefix, unsigned long long size) :
78  d_cache_dir(cache_dir), d_prefix(prefix), d_max_cache_size_in_bytes(size), d_target_size(0), d_cache_info(""),
79  d_cache_info_fd(-1)
80 {
81  m_initialize_cache_info();
82 }
83 
84 void BESFileLockingCache::initialize(const string &cache_dir, const string &prefix, unsigned long long size)
85 {
86  d_cache_dir = cache_dir;
87  d_prefix = prefix;
88  d_max_cache_size_in_bytes = size;
89 
90  m_initialize_cache_info();
91 }
92 
93 static inline string get_errno()
94 {
95  char *s_err = strerror(errno);
96  if (s_err)
97  return s_err;
98  else
99  return "Unknown error.";
100 }
101 
102 // Build a lock of a certain type.
103 static inline struct flock *lock(int type)
104 {
105  static struct flock lock;
106  lock.l_type = type;
107  lock.l_whence = SEEK_SET;
108  lock.l_start = 0;
109  lock.l_len = 0;
110  lock.l_pid = getpid();
111 
112  return &lock;
113 }
114 
115 inline void BESFileLockingCache::m_record_descriptor(const string &file, int fd)
116 {
117  BESDEBUG("cache",
118  "BESFileLockingCache::m_record_descriptor() - Recording descriptor: " << file << ", " << fd << endl);
119 
120  d_locks.insert(std::pair<string, int>(file, fd));
121 }
122 
123 inline int BESFileLockingCache::m_remove_descriptor(const string &file)
124 {
125  BESDEBUG("cache", "BESFileLockingCache::m_remove_descriptor(): d_locks size: " << d_locks.size() << endl);
126 
127  FilesAndLockDescriptors::iterator i = d_locks.find(file);
128  if (i == d_locks.end()) return -1;
129 
130  int fd = i->second;
131  d_locks.erase(i);
132 
133  BESDEBUG("cache",
134  "BESFileLockingCache::m_remove_descriptor(): Found file descriptor [" << fd << "] for file: " << file << endl);
135 
136  return fd;
137 }
138 
139 #if USE_GET_SHARED_LOCK
140 inline int BESFileLockingCache::m_find_descriptor(const string &file)
141 {
142  BESDEBUG("cache", "BESFileLockingCache::m_find_descriptor(): d_locks size: " << d_locks.size() << endl);
143 
144  FilesAndLockDescriptors::iterator i = d_locks.find(file);
145  if (i == d_locks.end()) return -1;
146 
147  BESDEBUG("cache",
148  "BESFileLockingCache::m_find_descriptor(): Found file descriptor [" << i->second << "] for file: " << file << endl);
149 
150  return i->second; // return the file descriptor bound to 'file'
151 }
152 #endif
153 
159 string lockStatus(const int fd)
160 {
161  struct flock lock_query;
162 
163  lock_query.l_type = F_WRLCK; /* Test for any lock on any part of a file. */
164  lock_query.l_start = 0;
165  lock_query.l_whence = SEEK_SET;
166  lock_query.l_len = 0;
167  lock_query.l_pid = 0;
168 
169  int ret = fcntl(fd, F_GETLK, &lock_query);
170 
171  stringstream ss;
172  ss << endl;
173  if (ret == -1) {
174  ss << "ERROR! fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << " errno[" << errno << "]: "
175  << strerror(errno) << endl;
176  }
177  else {
178  ss << "SUCCESS. fnctl(" << fd << ",F_GETLK, &lock) returned: " << ret << endl;
179  }
180 
181  ss << "lock_info.l_len: " << lock_query.l_len << endl;
182  ss << "lock_info.l_pid: " << lock_query.l_pid << endl;
183  ss << "lock_info.l_start: " << lock_query.l_start << endl;
184 
185  string type;
186  switch (lock_query.l_type) {
187  case F_RDLCK:
188  type = "F_RDLCK";
189  break;
190  case F_WRLCK:
191  type = "F_WRLCK";
192  break;
193  case F_UNLCK:
194  type = "F_UNLCK";
195  break;
196 
197  }
198 
199  ss << "lock_info.l_type: " << type << endl;
200  ss << "lock_info.l_whence: " << lock_query.l_whence << endl;
201 
202  return ss.str();
203 }
204 
210 static void unlock(int fd)
211 {
212  if (fcntl(fd, F_SETLK, lock(F_UNLCK)) == -1) {
213  throw BESInternalError("An error occurred trying to unlock the file: " + get_errno(), __FILE__, __LINE__);
214  }
215 
216  BESDEBUG("cache", "BESFileLockingCache::unlock() - lock status: " << lockStatus(fd) << endl);
217 
218  if (close(fd) == -1) throw BESInternalError("Could not close the (just) unlocked file.", __FILE__, __LINE__);
219 
220  BESDEBUG("cache", "BESFileLockingCache::unlock() - File Closed. fd: " << fd << endl);
221 }
222 
231 bool BESFileLockingCache::m_check_ctor_params()
232 {
233  // Should this really be a fatal error? What about just not
234  // using the cache in this case or writing out a warning message
235  // to the log. jhrg 10/23/15
236  //
237  // Yes, leave this as a fatal error and handle the case when cache_dir is
238  // empty in code that specializes this class. Those child classes are
239  // all singletons and their get_instance() methods need to return null
240  // when caching is turned off. You cannot do that here without throwing
241  // and we don't want to throw an exception for every call to a child's
242  // get_instance() method just because someone doesn't want to use a cache.
243  // jhrg 9/27/16
244  BESDEBUG("cache", "BESFileLockingCache::" <<__func__ << "() - " <<
245  "d_cache_dir: '" << d_cache_dir << "'" << endl);
246 
247  if (d_cache_dir.empty()) {
248  BESDEBUG("cache", "BESFileLockingCache::" <<__func__ << "() - " <<
249  "The cache directory was not specified. CACHE IS DISABLED." << endl);
250  disable();
251  return false;
252  }
253 
254  int status = mkdir(d_cache_dir.c_str(), 0775);
255  // If there is an error and it's not that the dir already exists,
256  // throw an exception.
257  if (status == -1 && errno != EEXIST) {
258  string err = "The cache directory " + d_cache_dir + " could not be created: " + strerror(errno);
259  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
260  }
261 
262  if (d_prefix.empty()) {
263  string err = "The cache file prefix was not specified, must not be empty";
264  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
265  }
266 
267  if (d_max_cache_size_in_bytes <= 0) {
268  string err = "The cache size was not specified, must be greater than zero";
269  throw BESError(err, BES_SYNTAX_USER_ERROR, __FILE__, __LINE__);
270  }
271 
272  enable();
273 
274  BESDEBUG("cache",
275  "BESFileLockingCache::" << __func__ << "() -" <<
276  " d_cache_dir: " << d_cache_dir <<
277  " d_prefix: " << d_prefix <<
278  " d_max_cache_size_in_bytes: " << d_max_cache_size_in_bytes <<
279  " (The cache has been " << (cache_enabled()?"enabled)":"disabled)") << endl);
280 
281  return true;
282 }
283 
293 static bool createLockedFile(string file_name, int &ref_fd)
294 {
295  BESDEBUG("cache2", "createLockedFile() - filename: " << file_name <<endl);
296 
297  int fd;
298  if ((fd = open(file_name.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666)) < 0) {
299  switch (errno) {
300  case EEXIST:
301  return false;
302 
303  default:
304  throw BESInternalError(file_name + ": " + get_errno(), __FILE__, __LINE__);
305  }
306  }
307 
308  struct flock *l = lock(F_WRLCK);
309  // F_SETLKW == set lock, blocking
310  if (fcntl(fd, F_SETLKW, l) == -1) {
311  close(fd);
312  ostringstream oss;
313  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
314  throw BESInternalError(oss.str(), __FILE__, __LINE__);
315  }
316 
317  BESDEBUG("cache2", "createLockedFile exit: " << file_name <<endl);
318 
319  // Success
320  ref_fd = fd;
321  return true;
322 }
323 
325 bool BESFileLockingCache::m_initialize_cache_info()
326 {
327  BESDEBUG("cache", "BESFileLockingCache::m_initialize_cache_info() - BEGIN" << endl);
328 
329  // The value set in configuration files, etc., is the size in megabytes. The private
330  // variable holds the size in bytes (converted below).
331  d_max_cache_size_in_bytes = min(d_max_cache_size_in_bytes, MAX_CACHE_SIZE_IN_MEGABYTES);
332  d_max_cache_size_in_bytes *= BYTES_PER_MEG;
333  d_target_size = d_max_cache_size_in_bytes * 0.8;
334 
335  BESDEBUG("cache",
336  "BESFileLockingCache::m_initialize_cache_info() - d_max_cache_size_in_bytes: " << d_max_cache_size_in_bytes << " d_target_size: "<<d_target_size<< endl);
337 
338  bool status = m_check_ctor_params(); // Throws BESError on error.
339  if (status) {
340  d_cache_info = BESUtil::assemblePath(d_cache_dir, d_prefix + ".cache_control", true);
341 
342  BESDEBUG("cache", "BESFileLockingCache::m_initialize_cache_info() - d_cache_info: " << d_cache_info << endl);
343 
344  // See if we can create it. If so, that means it doesn't exist. So make it and
345  // set the cache initial size to zero.
346  if (createLockedFile(d_cache_info, d_cache_info_fd)) {
347  // initialize the cache size to zero
348  unsigned long long size = 0;
349  if (write(d_cache_info_fd, &size, sizeof(unsigned long long)) != sizeof(unsigned long long))
350  throw BESInternalError("Could not write size info to the cache info file `" + d_cache_info + "`",
351  __FILE__,
352  __LINE__);
353 
354  // This leaves the d_cache_info_fd file descriptor open
355  unlock_cache();
356  }
357  else {
358  if ((d_cache_info_fd = open(d_cache_info.c_str(), O_RDWR)) == -1) {
359  throw BESInternalError(get_errno(), __FILE__, __LINE__);
360  }
361  }
362  BESDEBUG("cache",
363  "BESFileLockingCache::m_initialize_cache_info() - d_cache_info_fd: " << d_cache_info_fd << endl);
364  }
365  BESDEBUG("cache",
366  "BESFileLockingCache::m_initialize_cache_info() - END [" << "CACHE IS " << (cache_enabled()?"ENABLED]":"DISABLED]") << endl);
367 
368  return status;
369 }
370 
371 const string chars_excluded_from_filenames = "<>=,/()\\\"\':? []()$";
372 
387 string BESFileLockingCache::get_cache_file_name(const string &src, bool mangle)
388 {
389  // Old way of building String, retired 10/02/2015 - ndp
390  // Return d_cache_dir + "/" + d_prefix + BESFileLockingCache::DAP_CACHE_CHAR + target;
391  BESDEBUG("cache", __FUNCTION__ << " - src: '" << src << "' mangle: "<< mangle << endl);
392 
393  string target = get_cache_file_prefix() + src;
394 
395  if (mangle) {
396  string::size_type pos = target.find_first_of(chars_excluded_from_filenames);
397  while (pos != string::npos) {
398  target.replace(pos, 1, "#", 1);
399  pos = target.find_first_of(chars_excluded_from_filenames);
400  }
401  }
402 
403  if (target.length() > 254) {
404  ostringstream msg;
405  msg << "Cache filename is longer than 254 characters (name length: ";
406  msg << target.length() << ", name: " << target;
407  throw BESInternalError(msg.str(), __FILE__, __LINE__);
408  }
409 
410  target = BESUtil::assemblePath(get_cache_directory(), target, true);
411 
412  BESDEBUG("cache", __FUNCTION__ << " - target: '" << target << "'" << endl);
413 
414  return target;
415 }
416 
417 #if USE_GET_SHARED_LOCK
418 
430 static bool getSharedLock(const string &file_name, int &ref_fd)
431 {
432  BESDEBUG("cache2", "getSharedLock(): Acquiring cache read lock for " << file_name <<endl);
433 
434  int fd;
435  if ((fd = open(file_name.c_str(), O_RDONLY)) < 0) {
436  switch (errno) {
437  case ENOENT:
438  return false;
439 
440  default:
441  throw BESInternalError(get_errno(), __FILE__, __LINE__);
442  }
443  }
444 
445  struct flock *l = lock(F_RDLCK);
446  if (fcntl(fd, F_SETLKW, l) == -1) {
447  close(fd);
448  ostringstream oss;
449  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
450  throw BESInternalError(oss.str(), __FILE__, __LINE__);
451  }
452 
453  BESDEBUG("cache2", "getSharedLock(): SUCCESS Read Lock Acquired For " << file_name <<endl);
454 
455  // Success
456  ref_fd = fd;
457  return true;
458 }
459 #endif
460 
479 bool BESFileLockingCache::get_read_lock(const string &target, int &fd)
480 {
481  lock_cache_read();
482 
483  bool status = true; // Used to support to techniques. jhrg 4/26/17
484 
485 #if USE_GET_SHARED_LOCK
486  status = getSharedLock(target, fd);
487 
488  if (status) m_record_descriptor(target, fd);
489 #else
490  fd = m_find_descriptor(target);
491  // fd == -1 --> The file is not currently open
492  if ((fd == -1) && (fd = open(target.c_str(), O_RDONLY)) < 0) {
493  switch (errno) {
494  case ENOENT:
495  return false; // The file does not exist
496 
497  default:
498  throw BESInternalError(get_errno(), __FILE__, __LINE__);
499  }
500  }
501 
502  // The file might be open for writing, so setting a read lock is
503  // not possible.
504  struct flock *l = lock(F_RDLCK);
505  if (fcntl(fd, F_SETLKW, l) == -1) {
506  return false; // cannot get the lock
507  }
508 
509  m_record_descriptor(target, fd);
510 #endif
511 
512  unlock_cache();
513 
514  return status;
515 }
516 
534 bool BESFileLockingCache::create_and_lock(const string &target, int &fd)
535 {
537 
538  bool status = createLockedFile(target, fd);
539 
540  BESDEBUG("cache",
541  "BESFileLockingCache::create_and_lock() - " << target << " (status: " << status << ", fd: " << fd << ")" << endl);
542 
543  if (status) m_record_descriptor(target, fd);
544 
545  unlock_cache();
546 
547  return status;
548 }
549 
566 {
567  struct flock lock;
568  lock.l_type = F_RDLCK;
569  lock.l_whence = SEEK_SET;
570  lock.l_start = 0;
571  lock.l_len = 0;
572  lock.l_pid = getpid();
573 
574  if (fcntl(fd, F_SETLKW, &lock) == -1) {
575  throw BESInternalError(get_errno(), __FILE__, __LINE__);
576  }
577 
578  BESDEBUG("cache", "BESFileLockingCache::exclusive_to_shared_lock() - lock status: " << lockStatus(fd) << endl);
579 }
580 
590 {
591  BESDEBUG("cache", "BESFileLockingCache::lock_cache_write() - d_cache_info_fd: " << d_cache_info_fd << endl);
592 
593  if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_WRLCK)) == -1) {
594  throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
595  __LINE__);
596  }
597 
598  BESDEBUG("cache", "BESFileLockingCache::lock_cache_write() - lock status: " << lockStatus(d_cache_info_fd) << endl);
599 }
600 
605 {
606  BESDEBUG("cache", "BESFileLockingCache::lock_cache_read() - d_cache_info_fd: " << d_cache_info_fd << endl);
607 
608  if (fcntl(d_cache_info_fd, F_SETLKW, lock(F_RDLCK)) == -1) {
609  throw BESInternalError("An error occurred trying to lock the cache-control file" + get_errno(), __FILE__,
610  __LINE__);
611  }
612 
613  BESDEBUG("cache", "BESFileLockingCache::lock_cache_read() - lock status: " << lockStatus(d_cache_info_fd) << endl);
614 }
615 
622 {
623  BESDEBUG("cache", "BESFileLockingCache::unlock_cache() - d_cache_info_fd: " << d_cache_info_fd << endl);
624 
625  if (fcntl(d_cache_info_fd, F_SETLK, lock(F_UNLCK)) == -1) {
626  throw BESInternalError("An error occurred trying to unlock the cache-control file" + get_errno(), __FILE__,
627  __LINE__);
628  }
629 
630  BESDEBUG("cache", "BESFileLockingCache::unlock_cache() - lock status: " << lockStatus(d_cache_info_fd) << endl);
631 }
632 
648 void BESFileLockingCache::unlock_and_close(const string &file_name)
649 {
650  BESDEBUG("cache2", "BESFileLockingCache::unlock_and_close() - BEGIN file: " << file_name << endl);
651 
652  int fd = m_remove_descriptor(file_name); // returns -1 when no more files desp. remain
653  while (fd != -1) {
654  unlock(fd);
655  fd = m_remove_descriptor(file_name);
656  }
657 
658  BESDEBUG("cache", "BESFileLockingCache::unlock_and_close() - lock status: " << lockStatus(d_cache_info_fd) << endl);
659  BESDEBUG("cache2", "BESFileLockingCache::unlock_and_close() - END"<< endl);
660 }
661 
672 unsigned long long BESFileLockingCache::update_cache_info(const string &target)
673 {
674  unsigned long long current_size;
675  try {
677 
678  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
679  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
680 
681  // read the size from the cache info file
682  if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
683  throw BESInternalError("Could not get read size info from the cache info file!", __FILE__, __LINE__);
684 
685  struct stat buf;
686  int statret = stat(target.c_str(), &buf);
687  if (statret == 0)
688  current_size += buf.st_size;
689  else
690  throw BESInternalError("Could not read the size of the new file: " + target + " : " + get_errno(), __FILE__,
691  __LINE__);
692 
693  BESDEBUG("cache", "BESFileLockingCache::update_cache_info() - cache size updated to: " << current_size << endl);
694 
695  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
696  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
697 
698  if (write(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
699  throw BESInternalError("Could not write size info from the cache info file!", __FILE__, __LINE__);
700 
701  unlock_cache();
702  }
703  catch (...) {
704  unlock_cache();
705  throw;
706  }
707 
708  return current_size;
709 }
710 
715 bool BESFileLockingCache::cache_too_big(unsigned long long current_size) const
716 {
717  return current_size > d_max_cache_size_in_bytes;
718 }
719 
728 {
729  unsigned long long current_size;
730  try {
731  lock_cache_read();
732 
733  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
734  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
735  // read the size from the cache info file
736  if (read(d_cache_info_fd, &current_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
737  throw BESInternalError("Could not get read size info from the cache info file!", __FILE__, __LINE__);
738 
739  unlock_cache();
740  }
741  catch (...) {
742  unlock_cache();
743  throw;
744  }
745 
746  return current_size;
747 }
748 
749 static bool entry_op(cache_entry &e1, cache_entry &e2)
750 {
751  return e1.time < e2.time;
752 }
753 
755 unsigned long long BESFileLockingCache::m_collect_cache_dir_info(CacheFiles &contents)
756 {
757  DIR *dip = opendir(d_cache_dir.c_str());
758  if (!dip) throw BESInternalError("Unable to open cache directory " + d_cache_dir, __FILE__, __LINE__);
759 
760  struct dirent *dit;
761  vector<string> files;
762  // go through the cache directory and collect all of the files that
763  // start with the matching prefix
764  while ((dit = readdir(dip)) != NULL) {
765  string dirEntry = dit->d_name;
766  if (dirEntry.compare(0, d_prefix.length(), d_prefix) == 0 && dirEntry != d_cache_info) {
767  files.push_back(d_cache_dir + "/" + dirEntry);
768  }
769  }
770 
771  closedir(dip);
772 
773  unsigned long long current_size = 0;
774  struct stat buf;
775  for (vector<string>::iterator file = files.begin(); file != files.end(); ++file) {
776  if (stat(file->c_str(), &buf) == 0) {
777  current_size += buf.st_size;
778  cache_entry entry;
779  entry.name = *file;
780  entry.size = buf.st_size;
781  entry.time = buf.st_atime;
782  // Sanity check; Removed after initial testing since some files might be zero bytes
783 #if 0
784  if (entry.size == 0)
785  throw BESInternalError("Zero-byte file found in cache. " + *file, __FILE__, __LINE__);
786 #endif
787  contents.push_back(entry);
788  }
789  }
790 
791  // Sort so smaller (older) times are first.
792  contents.sort(entry_op);
793 
794  return current_size;
795 }
796 
813 static bool getExclusiveLockNB(string file_name, int &ref_fd)
814 {
815  BESDEBUG("cache2", "getExclusiveLock_nonblocking: " << file_name <<endl);
816 
817  int fd;
818  if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
819  switch (errno) {
820  case ENOENT:
821  return false;
822 
823  default:
824  throw BESInternalError(get_errno(), __FILE__, __LINE__);
825  }
826  }
827 
828  struct flock *l = lock(F_WRLCK);
829  if (fcntl(fd, F_SETLK, l) == -1) {
830  switch (errno) {
831  case EAGAIN:
832  case EACCES:
833  BESDEBUG("cache2",
834  "getExclusiveLockNB exit (false): " << file_name << " by: " << l->l_pid << endl);
835  close(fd);
836  return false;
837 
838  default: {
839  close(fd);
840  ostringstream oss;
841  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
842  throw BESInternalError(oss.str(), __FILE__, __LINE__);
843  }
844  }
845  }
846 
847  BESDEBUG("cache2", "getExclusiveLock_nonblocking exit (true): " << file_name <<endl);
848 
849  // Success
850  ref_fd = fd;
851  return true;
852 }
853 
865 void BESFileLockingCache::update_and_purge(const string &new_file)
866 {
867  BESDEBUG("cache", "purge - starting the purge" << endl);
868 
869  try {
871 
872  CacheFiles contents;
873  unsigned long long computed_size = m_collect_cache_dir_info(contents);
874 #if 0
875  if (BESISDEBUG( "cache_contents" )) {
876  BESDEBUG("cache", "BEFORE Purge " << computed_size/BYTES_PER_MEG << endl );
877  CacheFiles::iterator ti = contents.begin();
878  CacheFiles::iterator te = contents.end();
879  for (; ti != te; ti++) {
880  BESDEBUG("cache", (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
881  }
882  }
883 #endif
884  BESDEBUG("cache",
885  "BESFileLockingCache::update_and_purge() - current and target size (in MB) "
886  << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
887 
888  // This deletes files and updates computed_size
889  if (cache_too_big(computed_size)) {
890 
891  // d_target_size is 80% of the maximum cache size.
892  // Grab the first which is the oldest in terms of access time.
893  CacheFiles::iterator i = contents.begin();
894  while (i != contents.end() && computed_size > d_target_size) {
895  // Grab an exclusive lock but do not block - if another process has the file locked
896  // just move on to the next file. Also test to see if the current file is the file
897  // this process just added to the cache - don't purge that!
898  int cfile_fd;
899  if (i->name != new_file && getExclusiveLockNB(i->name, cfile_fd)) {
900  BESDEBUG("cache", "purge: " << i->name << " removed." << endl);
901 
902  if (unlink(i->name.c_str()) != 0)
903  throw BESInternalError(
904  "Unable to purge the file " + i->name + " from the cache: " + get_errno(), __FILE__,
905  __LINE__);
906 
907  unlock(cfile_fd);
908  computed_size -= i->size;
909  }
910  ++i;
911 
912  BESDEBUG("cache",
913  "BESFileLockingCache::update_and_purge() - current and target size (in MB) "
914  << computed_size/BYTES_PER_MEG << ", " << d_target_size/BYTES_PER_MEG << endl);
915  }
916  }
917 
918  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
919  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
920 
921  if (write(d_cache_info_fd, &computed_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
922  throw BESInternalError("Could not write size info to the cache info file!", __FILE__, __LINE__);
923 #if 0
924  if (BESISDEBUG( "cache_contents" )) {
925  contents.clear();
926  computed_size = m_collect_cache_dir_info(contents);
927  BESDEBUG("cache", "AFTER Purge " << computed_size/BYTES_PER_MEG << endl );
928  CacheFiles::iterator ti = contents.begin();
929  CacheFiles::iterator te = contents.end();
930  for (; ti != te; ti++) {
931  BESDEBUG("cache", (*ti).time << ": " << (*ti).name << ": size " << (*ti).size/BYTES_PER_MEG << endl );
932  }
933  }
934 #endif
935  unlock_cache();
936  }
937  catch (...) {
938  unlock_cache();
939  throw;
940  }
941 }
942 
959 static bool getExclusiveLock(string file_name, int &ref_fd)
960 {
961  BESDEBUG("cache2", "BESFileLockingCache::getExclusiveLock() - " << file_name <<endl);
962 
963  int fd;
964  if ((fd = open(file_name.c_str(), O_RDWR)) < 0) {
965  switch (errno) {
966  case ENOENT:
967  return false;
968 
969  default:
970  BESDEBUG("cache2", __func__ << "() - FAILED to open file: " << file_name << endl);
971  throw BESInternalError(get_errno(), __FILE__, __LINE__);
972  }
973  }
974 
975  struct flock *l = lock(F_WRLCK);
976  if (fcntl(fd, F_SETLKW, l) == -1) { // F_SETLKW == blocking lock
977  close(fd);
978  ostringstream oss;
979  oss << "cache process: " << l->l_pid << " triggered a locking error: " << get_errno();
980  throw BESInternalError(oss.str(), __FILE__, __LINE__);
981  }
982 
983  BESDEBUG("cache2", "BESFileLockingCache::getExclusiveLock() - exit: " << file_name <<endl);
984 
985  // Success
986  ref_fd = fd;
987  return true;
988 }
989 
1000 void BESFileLockingCache::purge_file(const string &file)
1001 {
1002  BESDEBUG("cache", "BESFileLockingCache::purge_file() - starting the purge" << endl);
1003 
1004  try {
1005  lock_cache_write();
1006 
1007  // Grab an exclusive lock on the file
1008  int cfile_fd;
1009  if (getExclusiveLock(file, cfile_fd)) {
1010  // Get the file's size
1011  unsigned long long size = 0;
1012  struct stat buf;
1013  if (stat(file.c_str(), &buf) == 0) {
1014  size = buf.st_size;
1015  }
1016 
1017  BESDEBUG("cache", "BESFileLockingCache::purge_file() - " << file << " removed." << endl);
1018 
1019  if (unlink(file.c_str()) != 0)
1020  throw BESInternalError("Unable to purge the file " + file + " from the cache: " + get_errno(), __FILE__,
1021  __LINE__);
1022 
1023  unlock(cfile_fd);
1024 
1025  unsigned long long cache_size = get_cache_size() - size;
1026 
1027  if (lseek(d_cache_info_fd, 0, SEEK_SET) == -1)
1028  throw BESInternalError("Could not rewind to front of cache info file.", __FILE__, __LINE__);
1029 
1030  if (write(d_cache_info_fd, &cache_size, sizeof(unsigned long long)) != sizeof(unsigned long long))
1031  throw BESInternalError("Could not write size info to the cache info file!", __FILE__, __LINE__);
1032  }
1033 
1034  unlock_cache();
1035  }
1036  catch (...) {
1037  unlock_cache();
1038  throw;
1039  }
1040 }
1041 
1051 bool BESFileLockingCache::dir_exists(const string &dir)
1052 {
1053  struct stat buf;
1054 
1055  return (stat(dir.c_str(), &buf) == 0) && (buf.st_mode & S_IFDIR);
1056 }
1057 
1066 void BESFileLockingCache::dump(ostream &strm) const
1067 {
1068  strm << BESIndent::LMarg << "BESFileLockingCache::dump - (" << (void *) this << ")" << endl;
1069  BESIndent::Indent();
1070  strm << BESIndent::LMarg << "cache dir: " << d_cache_dir << endl;
1071  strm << BESIndent::LMarg << "prefix: " << d_prefix << endl;
1072  strm << BESIndent::LMarg << "size (bytes): " << d_max_cache_size_in_bytes << endl;
1073  BESIndent::UnIndent();
1074 }
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
virtual bool create_and_lock(const string &target, int &fd)
Create a file in the cache and lock it for write access.
STL namespace.
void disable()
Disable the cache.
virtual unsigned long long get_cache_size()
Get the cache size. Read the size information from the cache info file and return it...
static string assemblePath(const string &firstPart, const string &secondPart, bool addLeadingSlash=false)
Assemble path fragments making sure that they are separated by a single &#39;/&#39; character.
Definition: BESUtil.cc:757
static bool dir_exists(const string &dir)
Abstract exception class for the BES with basic string message.
Definition: BESError.h:56
virtual void purge_file(const string &file)
Purge a single file from the cache.
const string get_cache_directory()
virtual void lock_cache_write()
virtual void dump(ostream &strm) const
dumps information about this object
virtual string get_cache_file_name(const string &src, bool mangle=true)
void enable()
Enabel the cache.
virtual bool get_read_lock(const string &target, int &fd)
Get a read-only lock on the file if it exists.
const string get_cache_file_prefix()
virtual void update_and_purge(const string &new_file)
Purge files from the cache.
virtual unsigned long long update_cache_info(const string &target)
Update the cache info file to include &#39;target&#39;.
virtual void lock_cache_read()
virtual void exclusive_to_shared_lock(int fd)
Transfer from an exclusive lock to a shared lock.
virtual void unlock_and_close(const string &target)