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