1 // Copyright (c) 2009, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 // ---
31 
32 #include <config.h>
33 #include "base/mutex.h"  // This must go first so we get _XOPEN_SOURCE
34 #include <ctemplate/template_cache.h>
35 #include <assert.h>      // for assert()
36 #include <errno.h>
37 #include <stddef.h>      // for size_t
38 #include <stdlib.h>      // for strerror()
39 #include <sys/stat.h>
40 #ifdef HAVE_UNISTD_H
41 # include <unistd.h>
42 #endif      // for getcwd()
43 #include HASH_MAP_H      // for hash_map<>::iterator, hash_map<>, etc
44 #include <utility>       // for pair<>, make_pair()
45 #include <vector>        // for vector<>::size_type, vector<>, etc
46 #include "base/thread_annotations.h"  // for GUARDED_BY
47 #include <ctemplate/find_ptr.h>
48 #include <ctemplate/template.h>  // for Template, TemplateState
49 #include <ctemplate/template_enums.h>  // for Strip, DO_NOT_STRIP
50 #include <ctemplate/template_pathops.h>  // for PathJoin(), IsAbspath(), etc
51 #include <ctemplate/template_string.h>  // for StringHash
52 #include "base/fileutil.h"
53 #include <iostream>      // for cerr
54 
55 #ifndef PATH_MAX
56 #ifdef MAXPATHLEN
57 #define PATH_MAX        MAXPATHLEN
58 #else
59 #define PATH_MAX        4096         // seems conservative for max filename len!
60 #endif
61 #endif
62 
63 using std::endl;
64 using std::string;
65 using std::vector;
66 using std::pair;
67 using std::make_pair;
68 using HASH_NAMESPACE::unordered_map;
69 
70 static int kVerbosity = 0;   // you can change this by hand to get vlogs
71 #define LOG(level)   std::cerr << #level ": "
72 #define PLOG(level)   std::cerr << #level ": [" << strerror(errno) << "] "
73 #define VLOG(level)  if (kVerbosity >= level)  std::cerr << "V" #level ": "
74 
75 namespace ctemplate {
76 
77 // ----------------------------------------------------------------------
78 // TemplateCache::RefcountedTemplate
79 //    A simple refcounting class to keep track of templates, which
80 //    might be shared between caches.  It also owns the pointer to
81 //    the template itself.
82 // ----------------------------------------------------------------------
83 
84 class TemplateCache::RefcountedTemplate {
85  public:
RefcountedTemplate(const Template * ptr)86   explicit RefcountedTemplate(const Template* ptr) : ptr_(ptr), refcount_(1) { }
IncRef()87   void IncRef() {
88     MutexLock ml(&mutex_);
89     assert(refcount_ > 0);
90     ++refcount_;
91   }
DecRefN(int n)92   void DecRefN(int n) {
93     bool refcount_is_zero;
94     {
95       MutexLock ml(&mutex_);
96       assert(refcount_ >= n);
97       refcount_ -= n;
98       refcount_is_zero = (refcount_ == 0);
99     }
100     // We can't delete this within the MutexLock, because when the
101     // MutexLock tries to unlock Mutex at function-exit, the mutex
102     // will have been deleted!  This is just as safe as doing the
103     // delete within the lock -- in either case, if anyone tried to do
104     // anything to this class after the refcount got to 0, bad things
105     // would happen.
106     if (refcount_is_zero)
107       delete this;
108   }
DecRef()109   void DecRef() {
110     DecRefN(1);
111   }
refcount() const112   int refcount() const {
113     MutexLock ml(&mutex_);   // could be ReaderMutexLock, but whatever
114     return refcount_;
115   }
tpl() const116   const Template* tpl() const { return ptr_; }
117 
118  private:
~RefcountedTemplate()119   ~RefcountedTemplate() { delete ptr_; }
120   const Template* const ptr_;
121   int refcount_  GUARDED_BY(mutex_);
122   mutable Mutex mutex_;
123 };
124 
125 // ----------------------------------------------------------------------
126 // TemplateCache::RefTplPtrHash
127 // TemplateCache::TemplateCacheHash
128 // TemplateCache::CachedTemplate
129 //    These are used for the cache-map.  CachedTemplate is what is
130 //    actually stored in the map: the Template* and some information
131 //    about it (whether we need to reload it, etc.).  Refcount is
132 //    a simple refcounting class, used to keep track of templates.
133 // ----------------------------------------------------------------------
134 
135 // This is needed just because many STLs (eg FreeBSD's) are unable to
136 // hash pointers by default.
137 class TemplateCache::RefTplPtrHash {
138  public:
operator ()(const RefcountedTemplate * p) const139   size_t operator()(const RefcountedTemplate* p) const {
140     return reinterpret_cast<size_t>(p);
141   }
142   // Less operator for MSVC's hash containers.
operator ()(const RefcountedTemplate * a,const RefcountedTemplate * b) const143   bool operator()(const RefcountedTemplate* a,
144                   const RefcountedTemplate* b) const {
145     return a < b;
146   }
147   // These two public members are required by msvc.  4 and 8 are defaults.
148   static const size_t bucket_size = 4;
149   static const size_t min_buckets = 8;
150 };
151 
152 class TemplateCache::TemplateCacheHash {
153  public:
operator ()(const TemplateCacheKey & p) const154   size_t operator()(const TemplateCacheKey& p) const {
155     // Using + here is silly, but should work ok in practice.
156     return p.first + p.second;
157 }
158   // Less operator for MSVC's hash containers.
operator ()(const TemplateCacheKey & a,const TemplateCacheKey & b) const159   bool operator()(const TemplateCacheKey& a,
160                   const TemplateCacheKey& b) const {
161     return (a.first == b.first
162             ? a.second < b.second
163             : a.first < b.first);
164   }
165   // These two public members are required by msvc.  4 and 8 are defaults.
166   static const size_t bucket_size = 4;
167   static const size_t min_buckets = 8;
168 };
169 
170 struct TemplateCache::CachedTemplate {
171   enum TemplateType { UNUSED, FILE_BASED, STRING_BASED };
CachedTemplatectemplate::TemplateCache::CachedTemplate172   CachedTemplate()
173       : refcounted_tpl(NULL),
174         should_reload(false),
175         template_type(UNUSED) {
176   }
CachedTemplatectemplate::TemplateCache::CachedTemplate177   CachedTemplate(const Template* tpl_ptr, TemplateType type)
178       : refcounted_tpl(new TemplateCache::RefcountedTemplate(tpl_ptr)),
179         should_reload(false),
180         template_type(type) {
181   }
182 
183   // we won't remove the template from the cache until refcount drops to 0
184   TemplateCache::RefcountedTemplate* refcounted_tpl;   // shared across Clone()
185   // reload status
186   bool should_reload;
187   // indicates if the template is string-based or file-based
188   TemplateType template_type;
189 };
190 
191 
192 // ----------------------------------------------------------------------
193 // TemplateCache::TemplateCache()
194 // TemplateCache::~TemplateCache()
195 // ----------------------------------------------------------------------
196 
TemplateCache()197 TemplateCache::TemplateCache()
198     : parsed_template_cache_(new TemplateMap),
199       is_frozen_(false),
200       search_path_(),
201       get_template_calls_(new TemplateCallMap),
202       mutex_(new Mutex),
203       search_path_mutex_(new Mutex) {
204 }
205 
~TemplateCache()206 TemplateCache::~TemplateCache() {
207   ClearCache();
208   delete parsed_template_cache_;
209   delete get_template_calls_;
210   delete mutex_;
211   delete search_path_mutex_;
212 }
213 
214 
215 // ----------------------------------------------------------------------
216 // HasTemplateChangedOnDisk
217 //    Indicates whether the template has changed, based on the
218 //    backing file's last modtime.
219 // ----------------------------------------------------------------------
220 
HasTemplateChangedOnDisk(const char * resolved_filename,time_t mtime,FileStat * statbuf)221 bool HasTemplateChangedOnDisk(const char* resolved_filename,
222                               time_t mtime,
223                               FileStat* statbuf) {
224   if (!File::Stat(resolved_filename, statbuf)) {
225     LOG(WARNING) << "Unable to stat file " << resolved_filename << endl;
226     // If we can't Stat the file then the file may have been deleted,
227     // so reload the template.
228     return true;
229   }
230   if (statbuf->mtime == mtime && mtime > 0) {
231     // No need to reload yet.
232     return false;
233   }
234   return true;
235 }
236 
237 
238 // ----------------------------------------------------------------------
239 // TemplateCache::LoadTemplate()
240 // TemplateCache::GetTemplate()
241 // TemplateCache::GetTemplateLocked()
242 // TemplateCache::StringToTemplateCache()
243 //    The routines for adding a template to the cache.  LoadTemplate
244 //    loads the template into the cache and returns true if the
245 //    template was successfully loaded or if it already exists in the
246 //    cache.  GetTemplate loads the template into the cache from disk
247 //    and returns the parsed template.  StringToTemplateCache parses
248 //    and loads the template from the given string into the parsed
249 //    cache, or returns false if an older version already exists in
250 //    the cache.
251 // ----------------------------------------------------------------------
252 
LoadTemplate(const TemplateString & filename,Strip strip)253 bool TemplateCache::LoadTemplate(const TemplateString& filename, Strip strip) {
254   TemplateCacheKey cache_key = TemplateCacheKey(filename.GetGlobalId(), strip);
255   WriterMutexLock ml(mutex_);
256   return GetTemplateLocked(filename, strip, cache_key) != NULL;
257 }
258 
GetTemplate(const TemplateString & filename,Strip strip)259 const Template *TemplateCache::GetTemplate(const TemplateString& filename,
260                                            Strip strip) {
261   // No need to have the cache-mutex acquired for this step
262   TemplateCacheKey cache_key = TemplateCacheKey(filename.GetGlobalId(), strip);
263   CachedTemplate retval;
264   WriterMutexLock ml(mutex_);
265   RefcountedTemplate* refcounted_tpl =
266       GetTemplateLocked(filename, strip, cache_key);
267   if (!refcounted_tpl)
268     return NULL;
269 
270   refcounted_tpl->IncRef();   // DecRef() is in DoneWithGetTemplatePtrs()
271   (*get_template_calls_)[refcounted_tpl]++;   // set up for DoneWith...()
272   return refcounted_tpl->tpl();
273 }
274 
GetTemplateLocked(const TemplateString & filename,Strip strip,const TemplateCacheKey & template_cache_key)275 TemplateCache::RefcountedTemplate* TemplateCache::GetTemplateLocked(
276     const TemplateString& filename,
277     Strip strip,
278     const TemplateCacheKey& template_cache_key) {
279   // NOTE: A write-lock must be held on mutex_ when this method is called.
280   CachedTemplate* it = find_ptr(*parsed_template_cache_, template_cache_key);
281   if (!it) {
282     // If the cache is frozen and the template doesn't already exist in cache,
283     // do not load the template, return NULL.
284     if (is_frozen_) {
285       return NULL;
286     }
287     // TODO(panicker): Validate the filename here, and if the file can't be
288     // resolved then insert a NULL in the cache.
289     // If validation succeeds then pass in resolved filename, mtime &
290     // file length (from statbuf) to the constructor.
291     const Template* tpl = new Template(filename, strip, this);
292     it = &(*parsed_template_cache_)[template_cache_key];
293     *it = CachedTemplate(tpl, CachedTemplate::FILE_BASED);
294     assert(it);
295   }
296   if (it->should_reload) {
297     // check if the template has changed on disk or if a new template with the
298     // same name has been added earlier in the search path:
299     const string resolved = FindTemplateFilename(
300         it->refcounted_tpl->tpl()->original_filename());
301     FileStat statbuf;
302     if (it->template_type == CachedTemplate::FILE_BASED &&
303         (resolved != it->refcounted_tpl->tpl()->template_file() ||
304          HasTemplateChangedOnDisk(
305              it->refcounted_tpl->tpl()->template_file(),
306              it->refcounted_tpl->tpl()->mtime(),
307              &statbuf))) {
308       // Create a new template, insert it into the cache under
309       // template_cache_key, and DecRef() the old one to indicate
310       // the cache no longer has a reference to it.
311       const Template* tpl = new Template(filename, strip, this);
312       // DecRef after creating the new template since DecRef may free up
313       // the storage for filename,
314       it->refcounted_tpl->DecRef();
315       *it = CachedTemplate(tpl, CachedTemplate::FILE_BASED);
316     }
317     it->should_reload = false;
318   }
319 
320   // If the state is TS_ERROR, we leave the state as is, but return
321   // NULL.  We won't try to load the template file again until the
322   // reload status is set to true by another call to ReloadAllIfChanged.
323   return it->refcounted_tpl->tpl()->state() == TS_READY ? it->refcounted_tpl : NULL;
324 }
325 
StringToTemplateCache(const TemplateString & key,const TemplateString & content,Strip strip)326 bool TemplateCache::StringToTemplateCache(const TemplateString& key,
327                                           const TemplateString& content,
328                                           Strip strip) {
329   TemplateCacheKey template_cache_key = TemplateCacheKey(key.GetGlobalId(), strip);
330   {
331     ReaderMutexLock ml(mutex_);
332     if (is_frozen_) {
333       return false;
334     }
335     // If the key is already in the parsed-cache, we just return false.
336     CachedTemplate* it = find_ptr(*parsed_template_cache_, template_cache_key);
337     if (it && it->refcounted_tpl->tpl()->state() != TS_ERROR) {
338       return false;
339     }
340   }
341   Template* tpl = Template::StringToTemplate(content, strip);
342   if (tpl == NULL) {
343     return false;
344   }
345   if (tpl->state() != TS_READY) {
346     delete tpl;
347     return false;
348   }
349 
350   WriterMutexLock ml(mutex_);
351   // Double-check it wasn't just inserted.
352   CachedTemplate* it = find_ptr(*parsed_template_cache_, template_cache_key);
353   if (it) {
354     if (it->refcounted_tpl->tpl()->state() == TS_ERROR) {
355       // replace the old entry with the new one
356       it->refcounted_tpl->DecRef();
357     } else {
358       delete tpl;
359       return false;
360     }
361   }
362   // Insert into cache.
363   (*parsed_template_cache_)[template_cache_key] =
364       CachedTemplate(tpl, CachedTemplate::STRING_BASED);
365   return true;
366 }
367 
368 // ----------------------------------------------------------------------
369 // TemplateCache::ExpandWithData()
370 // TemplateCache::ExpandFrozen()
371 // TemplateCache::ExpandLocked()
372 //    ExpandWithData gets the template from the parsed-cache, possibly
373 //    loading the template on-demand, and then expands the template.
374 //    ExpandFrozen is for frozen caches only -- if the filename isn't
375 //    in the cache, the routine fails (returns false) rather than trying
376 //    to fetch the template.  ExpandLocked is used for recursive
377 //    sub-template includes, and just tells template.cc it doesn't
378 //    need to recursively acquire any locks.
379 // ----------------------------------------------------------------------
380 
ExpandWithData(const TemplateString & filename,Strip strip,const TemplateDictionaryInterface * dict,PerExpandData * per_expand_data,ExpandEmitter * expand_emitter)381 bool TemplateCache::ExpandWithData(const TemplateString& filename,
382                                    Strip strip,
383                                    const TemplateDictionaryInterface *dict,
384                                    PerExpandData *per_expand_data,
385                                    ExpandEmitter *expand_emitter) {
386   TemplateCacheKey template_cache_key(filename.GetGlobalId(), strip);
387   // We make a local copy of this struct so we don't have to worry about
388   // what happens to our cache while we don't hold the lock (during Expand).
389   RefcountedTemplate* refcounted_tpl = NULL;
390   {
391     WriterMutexLock ml(mutex_);
392     // Optionally load the template (depending on whether the cache is frozen,
393     // the reload bit is set etc.)
394     refcounted_tpl = GetTemplateLocked(filename, strip, template_cache_key);
395     if (!refcounted_tpl)
396       return false;
397     refcounted_tpl->IncRef();
398   }
399   const bool result = refcounted_tpl->tpl()->ExpandWithDataAndCache(
400       expand_emitter, dict, per_expand_data, this);
401   {
402     WriterMutexLock ml(mutex_);
403     refcounted_tpl->DecRef();
404   }
405   return result;
406 }
407 
ExpandNoLoad(const TemplateString & filename,Strip strip,const TemplateDictionaryInterface * dict,PerExpandData * per_expand_data,ExpandEmitter * expand_emitter) const408 bool TemplateCache::ExpandNoLoad(
409     const TemplateString& filename,
410     Strip strip,
411     const TemplateDictionaryInterface *dict,
412     PerExpandData *per_expand_data,
413     ExpandEmitter *expand_emitter) const {
414   TemplateCacheKey template_cache_key(filename.GetGlobalId(), strip);
415   CachedTemplate cached_tpl;
416   {
417     ReaderMutexLock ml(mutex_);
418     if (!is_frozen_) {
419       LOG(DFATAL) << ": ExpandNoLoad() only works on frozen caches.";
420       return false;
421     }
422     CachedTemplate* it = find_ptr(*parsed_template_cache_, template_cache_key);
423     if (!it) {
424       return false;
425     }
426     cached_tpl = *it;
427     cached_tpl.refcounted_tpl->IncRef();
428   }
429   const bool result = cached_tpl.refcounted_tpl->tpl()->ExpandWithDataAndCache(
430       expand_emitter, dict, per_expand_data, this);
431   {
432     WriterMutexLock ml(mutex_);
433     cached_tpl.refcounted_tpl->DecRef();
434   }
435   return result;
436 }
437 
438 // Note: "Locked" in this name refers to the template object, not to
439 // use; we still need to acquire our locks as per normal.
ExpandLocked(const TemplateString & filename,Strip strip,ExpandEmitter * expand_emitter,const TemplateDictionaryInterface * dict,PerExpandData * per_expand_data)440 bool TemplateCache::ExpandLocked(const TemplateString& filename,
441                                  Strip strip,
442                                  ExpandEmitter *expand_emitter,
443                                  const TemplateDictionaryInterface *dict,
444                                  PerExpandData *per_expand_data) {
445   TemplateCacheKey template_cache_key(filename.GetGlobalId(), strip);
446   RefcountedTemplate* refcounted_tpl = NULL;
447   {
448     WriterMutexLock ml(mutex_);
449     refcounted_tpl = GetTemplateLocked(filename, strip, template_cache_key);
450     if (!refcounted_tpl)
451       return false;
452     refcounted_tpl->IncRef();
453   }
454   const bool result = refcounted_tpl->tpl()->ExpandLocked(
455       expand_emitter, dict, per_expand_data, this);
456   {
457     WriterMutexLock ml(mutex_);
458     refcounted_tpl->DecRef();
459   }
460   return result;
461 }
462 
463 // ----------------------------------------------------------------------
464 // TemplateCache::SetTemplateRootDirectory()
465 // TemplateCache::AddAlternateTemplateRootDirectory()
466 // TemplateCache::template_root_directory()
467 // TemplateCache::FindTemplateFilename()
468 //    The template-root-directory is where we look for template
469 //    files (in GetTemplate and include templates) when they're
470 //    given with a relative rather than absolute name.  You can
471 //    set a 'main' root directory (where we look first), as well
472 //    as alternates.
473 // ----------------------------------------------------------------------
474 
AddAlternateTemplateRootDirectoryHelper(const string & directory,bool clear_template_search_path)475 bool TemplateCache::AddAlternateTemplateRootDirectoryHelper(
476     const string& directory,
477     bool clear_template_search_path) {
478   {
479     ReaderMutexLock ml(mutex_);
480     if (is_frozen_) {  // Cannot set root-directory on a frozen cache.
481       return false;
482     }
483   }
484   string normalized = directory;
485   // make sure it ends with '/'
486   NormalizeDirectory(&normalized);
487   // Make the directory absolute if it isn't already.  This makes code
488   // safer if client later does a chdir.
489   if (!IsAbspath(normalized)) {
490     char* cwdbuf = new char[PATH_MAX];   // new to avoid stack overflow
491     const char* cwd = getcwd(cwdbuf, PATH_MAX);
492     if (!cwd) {   // probably not possible, but best to be defensive
493       PLOG(WARNING) << "Unable to convert '" << normalized
494                     << "' to an absolute path, with cwd=" << cwdbuf;
495     } else {
496       normalized = PathJoin(cwd, normalized);
497     }
498     delete[] cwdbuf;
499   }
500 
501   VLOG(2) << "Setting Template directory to " << normalized << endl;
502   {
503     WriterMutexLock ml(search_path_mutex_);
504     if (clear_template_search_path) {
505       search_path_.clear();
506     }
507     search_path_.push_back(normalized);
508   }
509 
510   // NOTE(williasr): The template root is not part of the template
511   // cache key, so we need to invalidate the cache contents.
512   ReloadAllIfChanged(LAZY_RELOAD);
513   return true;
514 }
515 
SetTemplateRootDirectory(const string & directory)516 bool TemplateCache::SetTemplateRootDirectory(const string& directory) {
517   return AddAlternateTemplateRootDirectoryHelper(directory, true);
518 }
519 
AddAlternateTemplateRootDirectory(const string & directory)520 bool TemplateCache::AddAlternateTemplateRootDirectory(
521     const string& directory) {
522   return AddAlternateTemplateRootDirectoryHelper(directory, false);
523 }
524 
template_root_directory() const525 string TemplateCache::template_root_directory() const {
526   ReaderMutexLock ml(search_path_mutex_);
527   if (search_path_.empty()) {
528     return kCWD;
529   }
530   return search_path_[0];
531 }
532 
533 // Given an unresolved filename, look through the template search path
534 // to see if the template can be found. If so, resolved contains the
535 // resolved filename, statbuf contains the stat structure for the file
536 // (to avoid double-statting the file), and the function returns
537 // true. Otherwise, the function returns false.
ResolveTemplateFilename(const string & unresolved,string * resolved,FileStat * statbuf) const538 bool TemplateCache::ResolveTemplateFilename(const string& unresolved,
539                                             string* resolved,
540                                             FileStat* statbuf) const {
541   ReaderMutexLock ml(search_path_mutex_);
542   if (search_path_.empty() || IsAbspath(unresolved)) {
543     *resolved = unresolved;
544     if (File::Stat(*resolved, statbuf)) {
545       VLOG(1) << "Resolved " << unresolved << " to " << *resolved << endl;
546       return true;
547     }
548   } else {
549     for (TemplateSearchPath::const_iterator path = search_path_.begin();
550          path != search_path_.end();
551          ++path) {
552       *resolved = PathJoin(*path, unresolved);
553       if (File::Stat(*resolved, statbuf)) {
554         VLOG(1) << "Resolved " << unresolved << " to " << *resolved << endl;
555         return true;
556       }
557     }
558   }
559 
560   resolved->clear();
561   return false;
562 }
563 
FindTemplateFilename(const string & unresolved) const564 string TemplateCache::FindTemplateFilename(const string& unresolved)
565     const {
566   string resolved;
567   FileStat statbuf;
568   if (!ResolveTemplateFilename(unresolved, &resolved, &statbuf))
569     resolved.clear();
570   return resolved;
571 }
572 
573 
574 // ----------------------------------------------------------------------
575 // TemplateCache::Delete()
576 // TemplateCache::ClearCache()
577 //    Delete deletes one entry from the cache.
578 // ----------------------------------------------------------------------
579 
Delete(const TemplateString & key)580 bool TemplateCache::Delete(const TemplateString& key) {
581   WriterMutexLock ml(mutex_);
582   if (is_frozen_) {  // Cannot delete from a frozen cache.
583     return false;
584   }
585   vector<TemplateCacheKey> to_erase;
586   const TemplateId key_id = key.GetGlobalId();
587   for (TemplateMap::iterator it = parsed_template_cache_->begin();
588        it != parsed_template_cache_->end();  ++it) {
589     if (it->first.first == key_id) {
590       // We'll delete the content pointed to by the entry here, since
591       // it's handy, but we won't delete the entry itself quite yet.
592       it->second.refcounted_tpl->DecRef();
593       to_erase.push_back(it->first);
594     }
595   }
596   for (vector<TemplateCacheKey>::iterator it = to_erase.begin();
597        it != to_erase.end(); ++it) {
598     parsed_template_cache_->erase(*it);
599   }
600   return !to_erase.empty();
601 }
602 
ClearCache()603 void TemplateCache::ClearCache() {
604   // NOTE: We allow a frozen cache to be cleared with this method, although
605   // no other changes can be made to the cache.
606   // We clear the cache by swapping it with an empty cache.  This lets
607   // us delete the items in the cache at our leisure without needing
608   // to hold mutex_.
609   TemplateMap tmp_cache;
610   {
611     WriterMutexLock ml(mutex_);
612     parsed_template_cache_->swap(tmp_cache);
613     is_frozen_ = false;
614   }
615   for (TemplateMap::iterator it = tmp_cache.begin();
616        it != tmp_cache.end();
617        ++it) {
618     it->second.refcounted_tpl->DecRef();
619   }
620 
621   // Do a decref for all templates ever returned by GetTemplate().
622   DoneWithGetTemplatePtrs();
623 }
624 
625 // ----------------------------------------------------------------------
626 // TemplateCache::DoneWithGetTemplatePtrs()
627 //    DoneWithGetTemplatePtrs() DecRefs every template in the
628 //    get_template_calls_ list.  This is because the user of
629 //    GetTemplate() didn't have a pointer to the refcounted Template
630 //    to do this themselves.  Note we only provide this as a batch
631 //    operation, so the user should be careful to only call this when
632 //    they are no longer using *any* template ever retrieved by
633 //    this cache's GetTemplate().
634 // ----------------------------------------------------------------------
635 
DoneWithGetTemplatePtrs()636 void TemplateCache::DoneWithGetTemplatePtrs() {
637   WriterMutexLock ml(mutex_);
638   for (TemplateCallMap::iterator it = get_template_calls_->begin();
639        it != get_template_calls_->end(); ++it) {
640     it->first->DecRefN(it->second);  // it.second: # of times GetTpl was called
641   }
642   get_template_calls_->clear();
643 }
644 
645 // ----------------------------------------------------------------------
646 // TemplateCache::ReloadAllIfChanged()
647 //    IMMEDIATE_RELOAD attempts to immediately reload and parse
648 //    all templates if the corresponding template files have changed.
649 //    LAZY_RELOAD just sets the reload bit in the cache so that the next
650 //    GetTemplate will reload and parse the template, if it changed.
651 
652 //    NOTE: Suppose the search path is "dira:dirb", and a template is
653 //    created with name "foo", which resolves to "dirb/foo" because
654 //    dira/foo does not exist.  Then suppose dira/foo is created and then
655 //    ReloadAllIfChanged() is called. Then ReloadAllIfChanged() will replace
656 //    the contents of the template with dira/foo, *not* dirb/foo, even if
657 //    dirb/foo hasn't changed.
658 // ----------------------------------------------------------------------
659 
ReloadAllIfChanged(ReloadType reload_type)660 void TemplateCache::ReloadAllIfChanged(ReloadType reload_type) {
661   WriterMutexLock ml(mutex_);
662   if (is_frozen_) {  // do not reload a frozen cache.
663     return;
664   }
665   for (TemplateMap::iterator it = parsed_template_cache_->begin();
666        it != parsed_template_cache_->end();
667        ++it) {
668     it->second.should_reload = true;
669     if (reload_type == IMMEDIATE_RELOAD) {
670       const Template* tpl = it->second.refcounted_tpl->tpl();
671       // Reload should always use the original filename.
672       // For instance on reload, we may replace an existing template with a
673       // new one that came earlier on the search path.
674       GetTemplateLocked(tpl->original_filename(), tpl->strip(), it->first);
675     }
676   }
677 }
678 
679 // ----------------------------------------------------------------------
680 // TemplateCache::Freeze()
681 //    This method marks the cache as 'frozen'. After this method is called,
682 //    the cache is immutable, and cannot be modified. New templates cannot be
683 //    loaded and existing templates cannot be reloaded.
684 // ----------------------------------------------------------------------
685 
Freeze()686 void TemplateCache::Freeze() {
687   {
688     ReaderMutexLock ml(mutex_);
689     if (is_frozen_) {  // if already frozen, then this is a no-op.
690       return;
691     }
692   }
693   // A final reload before freezing the cache.
694   ReloadAllIfChanged(IMMEDIATE_RELOAD);
695   {
696     WriterMutexLock ml(mutex_);
697     is_frozen_ = true;
698   }
699 }
700 
701 // ----------------------------------------------------------------------
702 // TemplateCache::Clone()
703 //    Clone makes a shallow copy of the parsed cache by incrementing
704 //    templates' refcount.
705 //    The caller is responsible for deallocating the returned TemplateCache.
706 // ----------------------------------------------------------------------
707 
Clone() const708 TemplateCache* TemplateCache::Clone() const {
709   ReaderMutexLock ml(mutex_);
710   TemplateCache* new_cache = new TemplateCache();
711   *(new_cache->parsed_template_cache_) = *parsed_template_cache_;
712   for (TemplateMap::iterator it = parsed_template_cache_->begin();
713        it != parsed_template_cache_->end(); ++it) {
714     it->second.refcounted_tpl->IncRef();
715   }
716 
717   return new_cache;
718 }
719 
720 // ----------------------------------------------------------------------
721 // TemplateCache::Refcount()
722 //    This routine is DEBUG-only. It returns the refcount of a template,
723 //    given the TemplateCacheKey.
724 // ----------------------------------------------------------------------
725 
Refcount(const TemplateCacheKey template_cache_key) const726 int TemplateCache::Refcount(const TemplateCacheKey template_cache_key) const {
727   ReaderMutexLock ml(mutex_);
728   CachedTemplate* it = find_ptr(*parsed_template_cache_, template_cache_key);
729   return it ? it->refcounted_tpl->refcount() : 0;
730 }
731 
732 // ----------------------------------------------------------------------
733 // TemplateCache::TemplateIsCached()
734 //    This routine is for testing only -- is says whether a given
735 //    template is already in the cache or not.
736 // ----------------------------------------------------------------------
737 
TemplateIsCached(const TemplateCacheKey template_cache_key) const738 bool TemplateCache::TemplateIsCached(const TemplateCacheKey template_cache_key)
739     const {
740   ReaderMutexLock ml(mutex_);
741   return parsed_template_cache_->count(template_cache_key);
742 }
743 
744 // ----------------------------------------------------------------------
745 // TemplateCache::ValidTemplateFilename
746 //    Validates the filename before constructing the template.
747 // ----------------------------------------------------------------------
748 
IsValidTemplateFilename(const string & filename,string * resolved_filename,FileStat * statbuf) const749 bool TemplateCache::IsValidTemplateFilename(const string& filename,
750                                             string* resolved_filename,
751                                             FileStat* statbuf) const {
752   if (!ResolveTemplateFilename(filename,
753                                resolved_filename,
754                                statbuf)) {
755     LOG(WARNING) << "Unable to locate file " << filename << endl;
756     return false;
757   }
758   if (statbuf->IsDirectory()) {
759     LOG(WARNING) << *resolved_filename
760                  << "is a directory and thus not readable" << endl;
761     return false;
762   }
763   return true;
764 }
765 
766 }
767