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