libdap++  Updated for version 3.8.2
HTTPCacheTable.cc
Go to the documentation of this file.
1 
2 // -*- mode: c++; c-basic-offset:4 -*-
3 
4 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
5 // Access Protocol.
6 
7 // Copyright (c) 2002,2003 OPeNDAP, Inc.
8 // Author: James Gallagher <jgallagher@opendap.org>
9 //
10 // This library is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU Lesser General Public
12 // License as published by the Free Software Foundation; either
13 // version 2.1 of the License, or (at your option) any later version.
14 //
15 // This library is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 // Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public
21 // License along with this library; if not, write to the Free Software
22 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 //
24 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
25 
26 #include "config.h"
27 
28 //#define DODS_DEBUG
29 
30 // TODO: Remove unneeded includes.
31 
32 #include <pthread.h>
33 #include <limits.h>
34 #include <unistd.h> // for stat
35 #include <sys/types.h> // for stat and mkdir
36 #include <sys/stat.h>
37 
38 #include <cstring>
39 #include <iostream>
40 #include <sstream>
41 #include <algorithm>
42 #include <iterator>
43 #include <set>
44 
45 #include "Error.h"
46 #include "InternalErr.h"
47 #include "ResponseTooBigErr.h"
48 #ifndef WIN32
49 #include "SignalHandler.h"
50 #endif
52 #include "HTTPCacheTable.h"
53 
54 #include "util_mit.h"
55 #include "debug.h"
56 
57 #ifdef WIN32
58 #include <direct.h>
59 #include <time.h>
60 #include <fcntl.h>
61 #define MKDIR(a,b) _mkdir((a))
62 #define REMOVE(a) remove((a))
63 #define MKSTEMP(a) _open(_mktemp((a)),_O_CREAT,_S_IREAD|_S_IWRITE)
64 #define DIR_SEPARATOR_CHAR '\\'
65 #define DIR_SEPARATOR_STR "\\"
66 #else
67 #define MKDIR(a,b) mkdir((a), (b))
68 #define REMOVE(a) remove((a))
69 #define MKSTEMP(a) mkstemp((a))
70 #define DIR_SEPARATOR_CHAR '/'
71 #define DIR_SEPARATOR_STR "/"
72 #endif
73 
74 #define CACHE_META ".meta"
75 #define CACHE_INDEX ".index"
76 #define CACHE_EMPTY_ETAG "@cache@"
77 
78 #define NO_LM_EXPIRATION 24*3600 // 24 hours
79 #define MAX_LM_EXPIRATION 48*3600 // Max expiration from LM
80 
81 // If using LM to find the expiration then take 10% and no more than
82 // MAX_LM_EXPIRATION.
83 #ifndef LM_EXPIRATION
84 #define LM_EXPIRATION(t) (min((MAX_LM_EXPIRATION), static_cast<int>((t) / 10)))
85 #endif
86 
87 const int CACHE_TABLE_SIZE = 1499;
88 
89 using namespace std;
90 
91 namespace libdap {
92 
96 int
97 get_hash(const string &url)
98 {
99  int hash = 0;
100 
101  for (const char *ptr = url.c_str(); *ptr; ptr++)
102  hash = (int)((hash * 3 + (*(unsigned char *)ptr)) % CACHE_TABLE_SIZE);
103 
104  return hash;
105 }
106 
107 HTTPCacheTable::HTTPCacheTable(const string &cache_root, int block_size) :
108  d_cache_root(cache_root),
109  d_block_size(block_size),
110  d_current_size(0),
111  d_new_entries(0)
112 {
113  d_cache_index = cache_root + CACHE_INDEX;
114 
115  d_cache_table = new CacheEntries*[CACHE_TABLE_SIZE];
116 
117  // Initialize the cache table.
118  for (int i = 0; i < CACHE_TABLE_SIZE; ++i)
119  d_cache_table[i] = 0;
120 
122 }
123 
127 static inline void
128 delete_cache_entry(HTTPCacheTable::CacheEntry *e)
129 {
130  DBG2(cerr << "Deleting CacheEntry: " << e << endl);
131  delete e;
132 }
133 
135  for (int i = 0; i < CACHE_TABLE_SIZE; ++i) {
136  HTTPCacheTable::CacheEntries *cp = get_cache_table()[i];
137  if (cp) {
138  // delete each entry
139  for_each(cp->begin(), cp->end(), delete_cache_entry);
140 
141  // now delete the vector that held the entries
142  delete get_cache_table()[i];
143  get_cache_table()[i] = 0;
144  }
145  }
146 
147  delete[] d_cache_table;
148 }
149 
157 class DeleteExpired : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
158  time_t d_time;
159  HTTPCacheTable &d_table;
160 
161 public:
162  DeleteExpired(HTTPCacheTable &table, time_t t) :
163  d_time(t), d_table(table) {
164  if (!t)
165  d_time = time(0); // 0 == now
166  }
167 
168  void operator()(HTTPCacheTable::CacheEntry *&e) {
169  if (e && !e->readers && (e->freshness_lifetime
170  < (e->corrected_initial_age + (d_time - e->response_time)))) {
171  DBG(cerr << "Deleting expired cache entry: " << e->url << endl);
172  d_table.remove_cache_entry(e);
173  delete e; e = 0;
174  }
175  }
176 };
177 
178 // @param time base deletes againt this time, defaults to 0 (now)
180  // Walk through and delete all the expired entries.
181  for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
182  HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
183  if (slot) {
184  for_each(slot->begin(), slot->end(), DeleteExpired(*this, time));
185  slot->erase(remove(slot->begin(), slot->end(),
186  static_cast<HTTPCacheTable::CacheEntry *>(0)), slot->end());
187  }
188  }
189 }
190 
197 class DeleteByHits : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
198  HTTPCacheTable &d_table;
199  int d_hits;
200 
201 public:
202  DeleteByHits(HTTPCacheTable &table, int hits) :
203  d_table(table), d_hits(hits) {
204  }
205 
206  void operator()(HTTPCacheTable::CacheEntry *&e) {
207  if (e && !e->readers && e->hits <= d_hits) {
208  DBG(cerr << "Deleting cache entry: " << e->url << endl);
209  d_table.remove_cache_entry(e);
210  delete e; e = 0;
211  }
212  }
213 };
214 
215 void
217  for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
218  if (get_cache_table()[cnt]) {
219  HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
220  for_each(slot->begin(), slot->end(), DeleteByHits(*this, hits));
221  slot->erase(remove(slot->begin(), slot->end(),
222  static_cast<HTTPCacheTable::CacheEntry*>(0)),
223  slot->end());
224 
225  }
226  }
227 }
228 
233 class DeleteBySize : public unary_function<HTTPCacheTable::CacheEntry *&, void> {
234  HTTPCacheTable &d_table;
235  unsigned int d_size;
236 
237 public:
238  DeleteBySize(HTTPCacheTable &table, unsigned int size) :
239  d_table(table), d_size(size) {
240  }
241 
242  void operator()(HTTPCacheTable::CacheEntry *&e) {
243  if (e && !e->readers && e->size > d_size) {
244  DBG(cerr << "Deleting cache entry: " << e->url << endl);
245  d_table.remove_cache_entry(e);
246  delete e; e = 0;
247  }
248  }
249 };
250 
251 void HTTPCacheTable::delete_by_size(unsigned int size) {
252  for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
253  if (get_cache_table()[cnt]) {
254  HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
255  for_each(slot->begin(), slot->end(), DeleteBySize(*this, size));
256  slot->erase(remove(slot->begin(), slot->end(),
257  static_cast<HTTPCacheTable::CacheEntry*>(0)),
258  slot->end());
259 
260  }
261  }
262 }
263 
270 
277 bool
279 {
280  d_new_entries = 0;
281 
282  return (REMOVE(d_cache_index.c_str()) == 0);
283 }
284 
293 bool
295 {
296  FILE *fp = fopen(d_cache_index.c_str(), "r");
297  // If the cache index can't be opened that's OK; start with an empty
298  // cache. 09/05/02 jhrg
299  if (!fp) {
300  return false;
301  }
302 
303  char line[1024];
304  while (!feof(fp) && fgets(line, 1024, fp)) {
306  DBG2(cerr << line << endl);
307  }
308 
309  int res = fclose(fp) ;
310  if (res) {
311  DBG(cerr << "HTTPCache::cache_index_read - Failed to close " << (void *)fp << endl);
312  }
313 
314  d_new_entries = 0;
315 
316  return true;
317 }
318 
328 {
329  // Read the line and create the cache object
331  istringstream iss(line);
332  iss >> entry->url;
333  iss >> entry->cachename;
334 
335  iss >> entry->etag;
336  if (entry->etag == CACHE_EMPTY_ETAG)
337  entry->etag = "";
338 
339  iss >> entry->lm;
340  iss >> entry->expires;
341  iss >> entry->size;
342  iss >> entry->range; // range is not used. 10/02/02 jhrg
343 
344  iss >> entry->hash;
345  iss >> entry->hits;
346  iss >> entry->freshness_lifetime;
347  iss >> entry->response_time;
348  iss >> entry->corrected_initial_age;
349 
350  iss >> entry->must_revalidate;
351 
352  return entry;
353 }
354 
357 class WriteOneCacheEntry :
358  public unary_function<HTTPCacheTable::CacheEntry *, void>
359 {
360 
361  FILE *d_fp;
362 
363 public:
364  WriteOneCacheEntry(FILE *fp) : d_fp(fp)
365  {}
366 
367  void operator()(HTTPCacheTable::CacheEntry *e)
368  {
369  if (e && fprintf(d_fp,
370  "%s %s %s %ld %ld %ld %c %d %d %ld %ld %ld %c\r\n",
371  e->url.c_str(),
372  e->cachename.c_str(),
373  e->etag == "" ? CACHE_EMPTY_ETAG : e->etag.c_str(),
374  (long)(e->lm),
375  (long)(e->expires),
376  e->size,
377  e->range ? '1' : '0', // not used. 10/02/02 jhrg
378  e->hash,
379  e->hits,
380  (long)(e->freshness_lifetime),
381  (long)(e->response_time),
382  (long)(e->corrected_initial_age),
383  e->must_revalidate ? '1' : '0') < 0)
384  throw Error("Cache Index. Error writing cache index\n");
385  }
386 };
387 
397 void
399 {
400  DBG(cerr << "Cache Index. Writing index " << d_cache_index << endl);
401 
402  // Open the file for writing.
403  FILE * fp = NULL;
404  if ((fp = fopen(d_cache_index.c_str(), "wb")) == NULL) {
405  throw Error(string("Cache Index. Can't open `") + d_cache_index
406  + string("' for writing"));
407  }
408 
409  // Walk through the list and write it out. The format is really
410  // simple as we keep it all in ASCII.
411 
412  for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
413  HTTPCacheTable::CacheEntries *cp = get_cache_table()[cnt];
414  if (cp)
415  for_each(cp->begin(), cp->end(), WriteOneCacheEntry(fp));
416  }
417 
418  /* Done writing */
419  int res = fclose(fp);
420  if (res) {
421  DBG(cerr << "HTTPCache::cache_index_write - Failed to close "
422  << (void *)fp << endl);
423  }
424 
425  d_new_entries = 0;
426 }
427 
429 
442 string
444 {
445  struct stat stat_info;
446  ostringstream path;
447 
448  path << d_cache_root << hash;
449  string p = path.str();
450 
451  if (stat(p.c_str(), &stat_info) == -1) {
452  DBG2(cerr << "Cache....... Create dir " << p << endl);
453  if (MKDIR(p.c_str(), 0777) < 0) {
454  DBG2(cerr << "Cache....... Can't create..." << endl);
455  throw Error("Could not create cache slot to hold response! Check the write permissions on your disk cache directory. Cache root: " + d_cache_root + ".");
456  }
457  }
458  else {
459  DBG2(cerr << "Cache....... Directory " << p << " already exists"
460  << endl);
461  }
462 
463  return p;
464 }
465 
480 void
482 {
483  string hash_dir = create_hash_directory(entry->hash);
484 #ifdef WIN32
485  hash_dir += "\\dodsXXXXXX";
486 #else
487  hash_dir += "/dodsXXXXXX"; // mkstemp uses six characters.
488 #endif
489 
490  // mkstemp uses the storage passed to it; must be writable and local.
491  char *templat = new char[hash_dir.size() + 1];
492  strcpy(templat, hash_dir.c_str());
493 
494  // Open truncated for update. NB: mkstemp() returns a file descriptor.
495  // man mkstemp says "... The file is opened with the O_EXCL flag,
496  // guaranteeing that when mkstemp returns successfully we are the only
497  // user." 09/19/02 jhrg
498  int fd = MKSTEMP(templat); // fd mode is 666 or 600 (Unix)
499  if (fd < 0) {
500  delete[] templat; templat = 0;
501  close(fd);
502  throw Error("The HTTP Cache could not create a file to hold the response; it will not be cached.");
503  }
504 
505  entry->cachename = templat;
506  delete[] templat; templat = 0;
507  close(fd);
508 }
509 
510 
512 static inline int
513 entry_disk_space(int size, unsigned int block_size)
514 {
515  unsigned int num_of_blocks = (size + block_size) / block_size;
516 
517  DBG(cerr << "size: " << size << ", block_size: " << block_size
518  << ", num_of_blocks: " << num_of_blocks << endl);
519 
520  return num_of_blocks * block_size;
521 }
522 
526 
532 void
534 {
535  int hash = entry->hash;
536 
537  if (!d_cache_table[hash])
538  d_cache_table[hash] = new CacheEntries;
539 
540  d_cache_table[hash]->push_back(entry);
541 
542  DBG(cerr << "add_entry_to_cache_table, current_size: " << d_current_size
543  << ", entry->size: " << entry->size << ", block size: " << d_block_size
544  << endl);
545 
546  d_current_size += entry_disk_space(entry->size, d_block_size);
547 
548  DBG(cerr << "add_entry_to_cache_table, current_size: " << d_current_size << endl);
549 
551 }
552 
557 HTTPCacheTable::get_locked_entry_from_cache_table(const string &url) /*const*/
558 {
559  return get_locked_entry_from_cache_table(get_hash(url), url);
560 }
561 
570 HTTPCacheTable::get_locked_entry_from_cache_table(int hash, const string &url) /*const*/
571 {
572  DBG(cerr << "url: " << url << "; hash: " << hash << endl);
573  DBG(cerr << "d_cache_table: " << hex << d_cache_table << dec << endl);
574  if (d_cache_table[hash]) {
575  CacheEntries *cp = d_cache_table[hash];
576  for (CacheEntriesIter i = cp->begin(); i != cp->end(); ++i) {
577  // Must test *i because perform_garbage_collection may have
578  // removed this entry; the CacheEntry will then be null.
579  if ((*i) && (*i)->url == url) {
580  (*i)->lock_read_response(); // Lock the response
581  return *i;
582  }
583  }
584  }
585 
586  return 0;
587 }
588 
597 {
598  int hash = get_hash(url);
599  if (d_cache_table[hash]) {
600  CacheEntries *cp = d_cache_table[hash];
601  for (CacheEntriesIter i = cp->begin(); i != cp->end(); ++i) {
602  // Must test *i because perform_garbage_collection may have
603  // removed this entry; the CacheEntry will then be null.
604  if ((*i) && (*i)->url == url) {
605  (*i)->lock_write_response(); // Lock the response
606  return *i;
607  }
608  }
609  }
610 
611  return 0;
612 }
613 
621 void
623 {
624  // This should never happen; all calls to this method are protected by
625  // the caller, hence the InternalErr.
626  if (entry->readers)
627  throw InternalErr(__FILE__, __LINE__, "Tried to delete a cache entry that is in use.");
628 
629  REMOVE(entry->cachename.c_str());
630  REMOVE(string(entry->cachename + CACHE_META).c_str());
631 
632  DBG(cerr << "remove_cache_entry, current_size: " << get_current_size() << endl);
633 
634  unsigned int eds = entry_disk_space(entry->size, get_block_size());
635  set_current_size((eds > get_current_size()) ? 0 : get_current_size() - eds);
636 
637  DBG(cerr << "remove_cache_entry, current_size: " << get_current_size() << endl);
638 }
639 
642 class DeleteCacheEntry: public unary_function<HTTPCacheTable::CacheEntry *&, void>
643 {
644  string d_url;
645  HTTPCacheTable *d_cache_table;
646 
647 public:
648  DeleteCacheEntry(HTTPCacheTable *c, const string &url)
649  : d_url(url), d_cache_table(c)
650  {}
651 
652  void operator()(HTTPCacheTable::CacheEntry *&e)
653  {
654  if (e && e->url == d_url) {
655  e->lock_write_response();
656  d_cache_table->remove_cache_entry(e);
658  delete e; e = 0;
659  }
660  }
661 };
662 
669 void
671 {
672  int hash = get_hash(url);
673  if (d_cache_table[hash]) {
674  CacheEntries *cp = d_cache_table[hash];
675  for_each(cp->begin(), cp->end(), DeleteCacheEntry(this, url));
676  cp->erase(remove(cp->begin(), cp->end(), static_cast<HTTPCacheTable::CacheEntry*>(0)),
677  cp->end());
678  }
679 }
680 
683 class DeleteUnlockedCacheEntry :
684  public unary_function<HTTPCacheTable::CacheEntry *&, void> {
685  HTTPCacheTable &d_table;
686 
687 public:
688  DeleteUnlockedCacheEntry(HTTPCacheTable &t) :
689  d_table(t) {
690  }
691  void operator()(HTTPCacheTable::CacheEntry *&e) {
692  if (e) {
693  d_table.remove_cache_entry(e);
694  delete e; e = 0;
695  }
696  }
697 };
698 
700  // Walk through the cache table and, for every entry in the cache, delete
701  // it on disk and in the cache table.
702  for (int cnt = 0; cnt < CACHE_TABLE_SIZE; cnt++) {
703  HTTPCacheTable::CacheEntries *slot = get_cache_table()[cnt];
704  if (slot) {
705  for_each(slot->begin(), slot->end(), DeleteUnlockedCacheEntry(*this));
706  slot->erase(remove(slot->begin(), slot->end(), static_cast<HTTPCacheTable::CacheEntry *>(0)),
707  slot->end());
708  }
709  }
710 
712 }
713 
727 void
728 HTTPCacheTable::calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time)
729 {
730  entry->response_time = time(NULL);
731  time_t apparent_age = max(0, static_cast<int>(entry->response_time - entry->date));
732  time_t corrected_received_age = max(apparent_age, entry->age);
733  time_t response_delay = entry->response_time - request_time;
734  entry->corrected_initial_age = corrected_received_age + response_delay;
735 
736  // Estimate an expires time using the max-age and expires time. If we
737  // don't have an explicit expires time then set it to 10% of the LM date
738  // (although max 24 h). If no LM date is available then use 24 hours.
739  time_t freshness_lifetime = entry->max_age;
740  if (freshness_lifetime < 0) {
741  if (entry->expires < 0) {
742  if (entry->lm < 0) {
743  freshness_lifetime = default_expiration;
744  }
745  else {
746  freshness_lifetime = LM_EXPIRATION(entry->date - entry->lm);
747  }
748  }
749  else
750  freshness_lifetime = entry->expires - entry->date;
751  }
752 
753  entry->freshness_lifetime = max(0, static_cast<int>(freshness_lifetime));
754 
755  DBG2(cerr << "Cache....... Received Age " << entry->age
756  << ", corrected " << entry->corrected_initial_age
757  << ", freshness lifetime " << entry->freshness_lifetime << endl);
758 }
759 
772  unsigned long max_entry_size, const vector<string> &headers) {
773  vector<string>::const_iterator i;
774  for (i = headers.begin(); i != headers.end(); ++i) {
775  // skip a blank header.
776  if ((*i).empty())
777  continue;
778 
779  string::size_type colon = (*i).find(':');
780 
781  // skip a header with no colon in it.
782  if (colon == string::npos)
783  continue;
784 
785  string header = (*i).substr(0, (*i).find(':'));
786  string value = (*i).substr((*i).find(": ") + 2);
787  DBG2(cerr << "Header: " << header << endl);DBG2(cerr << "Value: " << value << endl);
788 
789  if (header == "ETag") {
790  entry->etag = value;
791  } else if (header == "Last-Modified") {
792  entry->lm = parse_time(value.c_str());
793  } else if (header == "Expires") {
794  entry->expires = parse_time(value.c_str());
795  } else if (header == "Date") {
796  entry->date = parse_time(value.c_str());
797  } else if (header == "Age") {
798  entry->age = parse_time(value.c_str());
799  } else if (header == "Content-Length") {
800  unsigned long clength = strtoul(value.c_str(), 0, 0);
801  if (clength > max_entry_size)
802  entry->set_no_cache(true);
803  } else if (header == "Cache-Control") {
804  // Ignored Cache-Control values: public, private, no-transform,
805  // proxy-revalidate, s-max-age. These are used by shared caches.
806  // See section 14.9 of RFC 2612. 10/02/02 jhrg
807  if (value == "no-cache" || value == "no-store")
808  // Note that we *can* store a 'no-store' response in volatile
809  // memory according to RFC 2616 (section 14.9.2) but those
810  // will be rare coming from DAP servers. 10/02/02 jhrg
811  entry->set_no_cache(true);
812  else if (value == "must-revalidate")
813  entry->must_revalidate = true;
814  else if (value.find("max-age") != string::npos) {
815  string max_age = value.substr(value.find("=" + 1));
816  entry->max_age = parse_time(max_age.c_str());
817  }
818  }
819  }
820 }
821 
823 
824 // @TODO Change name to record locked response
826  entry->hits++; // Mark hit
827  d_locked_entries[body] = entry; // record lock, see release_cached_r...
828 }
829 
831  HTTPCacheTable::CacheEntry *entry = d_locked_entries[body];
832  if (!entry)
833  throw InternalErr("There is no cache entry for the response given.");
834 
835  d_locked_entries.erase(body);
836  entry->unlock_read_response();
837 
838  if (entry->readers < 0)
839  throw InternalErr("An unlocked entry was released");
840 }
841 
843  return !d_locked_entries.empty();
844 }
845 
846 } // namespace libdap
void remove_cache_entry(HTTPCacheTable::CacheEntry *entry)
const int CACHE_TABLE_SIZE
time_t parse_time(const char *str, bool expand)
Definition: util_mit.cc:132
void create_location(CacheEntry *entry)
void parse_headers(HTTPCacheTable::CacheEntry *entry, unsigned long max_entry_size, const vector< string > &headers)
void add_entry_to_cache_table(CacheEntry *entry)
vector< CacheEntry * > CacheEntries
void calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time)
void delete_by_size(unsigned int size)
CacheEntries::iterator CacheEntriesIter
#define DBG2(x)
Definition: debug.h:73
A class for software fault reporting.
Definition: InternalErr.h:64
#define DBG(x)
Definition: debug.h:58
void set_current_size(unsigned long sz)
unsigned int get_block_size() const
int get_hash(const string &url)
#define CACHE_INDEX
#define MKDIR(a, b)
void bind_entry_to_data(CacheEntry *entry, FILE *body)
#define MKSTEMP(a)
void delete_expired_entries(time_t time=0)
#define LM_EXPIRATION(t)
unsigned long get_current_size() const
CacheEntry * cache_index_parse_line(const char *line)
void remove_entry_from_cache_table(const string &url)
CacheEntry * get_write_locked_entry_from_cache_table(const string &url)
#define CACHE_EMPTY_ETAG
void delete_by_hits(int hits)
A class for error processing.
Definition: Error.h:90
string create_hash_directory(int hash)
#define CACHE_META
#define REMOVE(a)
void uncouple_entry_from_data(FILE *body)