Fix reflection default arg evaluation for $this
[hiphop-php.git] / hphp / runtime / base / stat-cache.cpp
blob9576c898635c39c05831d67cd4f0fd8bc4d06cd5
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/stat-cache.h"
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fcntl.h>
21 #include <sys/param.h>
22 #include <sstream>
23 #include <vector>
25 #include <folly/MapUtil.h>
26 #include <folly/portability/Unistd.h>
28 #include "hphp/util/trace.h"
29 #include "hphp/runtime/base/runtime-option.h"
30 #include "hphp/runtime/vm/jit/hooks.h"
31 #include "hphp/util/text-util.h"
32 #include "hphp/runtime/base/file-util.h"
34 namespace HPHP {
35 ///////////////////////////////////////////////////////////////////////////////
37 TRACE_SET_MOD(stat);
39 UNUSED static std::string statToString(const struct stat* buf) {
40 std::ostringstream os;
41 os << "struct stat {";
42 os << "dev=" << buf->st_dev << ", ";
43 os << "ino=" << buf->st_ino << ", ";
44 os << "mode=0" << std::oct << buf->st_mode << std::dec << ", ";
45 os << "nlink=" << buf->st_nlink << ", ";
46 os << "uid=" << buf->st_uid << ", ";
47 os << "gid=" << buf->st_gid << ", ";
48 os << "rdev=" << buf->st_rdev << ", ";
49 os << "size=" << buf->st_size << ", ";
50 #ifndef _MSC_VER
51 os << "blksize=" << buf->st_blksize << ", ";
52 os << "blocks=" << buf->st_blocks << ", ";
53 #endif
54 os << "atime=" << buf->st_atime << ", ";
55 os << "mtime=" << buf->st_mtime << ", ";
56 os << "ctime=" << buf->st_ctime;
57 os << "}";
58 return os.str();
61 UNUSED static bool statEquiv(const struct stat* stA, const struct stat* stB) {
62 return (stA->st_dev == stB->st_dev
63 && stA->st_ino == stB->st_ino
64 && stA->st_mode == stB->st_mode
65 && stA->st_nlink == stB->st_nlink
66 && stA->st_uid == stB->st_uid
67 && stA->st_gid == stB->st_gid
68 && stA->st_rdev == stB->st_rdev
69 && stA->st_size == stB->st_size
70 #ifndef _MSC_VER
71 && stA->st_blksize == stB->st_blksize
72 && stA->st_blocks == stB->st_blocks
73 #endif
74 /* Intentionally omitted:
75 && stA->st_atime == stB->st_atime
77 && stA->st_mtime == stB->st_mtime
78 && stA->st_ctime == stB->st_ctime);
81 #ifdef __linux__
82 UNUSED static std::string eventToString(const struct inotify_event* ie) {
83 bool first = true;
84 std::ostringstream os;
85 os << "struct inotify_event {wd=" << ie->wd << ", mask=(";
86 #define EVENT(e) do { \
87 if (ie->mask & e) { \
88 if (first) { \
89 first = false; \
90 } else { \
91 os << "|"; \
92 } \
93 os << #e; \
94 } \
95 } while (0)
96 EVENT(IN_ACCESS);
97 EVENT(IN_MODIFY);
98 EVENT(IN_ATTRIB);
99 EVENT(IN_CLOSE_WRITE);
100 EVENT(IN_CLOSE_NOWRITE);
101 EVENT(IN_OPEN);
102 EVENT(IN_MOVED_FROM);
103 EVENT(IN_MOVED_TO);
104 EVENT(IN_CREATE);
105 EVENT(IN_DELETE);
106 EVENT(IN_DELETE_SELF);
107 EVENT(IN_MOVE_SELF);
108 EVENT(IN_UNMOUNT);
109 EVENT(IN_Q_OVERFLOW);
110 EVENT(IN_IGNORED);
111 EVENT(IN_ISDIR);
112 #undef EVENT
113 os << ")";
114 if (ie->cookie != 0) os << ", cookie=" << ie->cookie;
115 if (ie->len != 0) os << ", name='" << ie->name << "'";
116 os << "}";
117 return os.str();
119 #endif
121 static int statSyscall(const std::string& path, struct stat* buf) {
122 int ret = ::stat(path.c_str(), buf);
123 if (ret == 0) {
124 TRACE(5, "StatCache: stat '%s' %s\n",
125 path.c_str(), statToString(buf).c_str());
126 } else {
127 TRACE(5, "StatCache: stat '%s' --> error\n", path.c_str());
129 return ret;
132 static int lstatSyscall(const std::string& path, struct stat* buf) {
133 int ret = ::lstat(path.c_str(), buf);
134 if (ret == 0) {
135 TRACE(5, "StatCache: lstat '%s' %s\n",
136 path.c_str(), statToString(buf).c_str());
137 } else {
138 TRACE(5, "StatCache: lstat '%s' --> error\n", path.c_str());
140 return ret;
143 static std::string readlinkSyscall(const std::string& path) {
144 char lbuf[PATH_MAX + 1];
145 ssize_t llen = ::readlink(path.c_str(), lbuf, sizeof(lbuf) - 1);
146 if (llen == -1) {
147 TRACE(5, "StatCache: readlink('%s') --> error\n", path.c_str());
148 return "";
150 lbuf[llen] = '\0';
151 TRACE(5, "StatCache: readlink('%s') --> '%s'\n", path.c_str(), lbuf);
152 return lbuf;
155 static std::string realpathLibc(const char* path) {
156 char buf[PATH_MAX];
158 std::string ret;
159 if (!::realpath(path, buf)) {
160 TRACE(5, "StatCache: realpath('%s') --> error\n", path);
161 return ret;
163 TRACE(5, "StatCache: realpath('%s') --> '%s'\n", path, buf);
164 ret = buf;
165 return ret;
168 //==============================================================================
169 // StatCache::Node.
171 StatCache::Node::Node(StatCache& statCache, int wd /* = -1 */)
172 : m_statCache(statCache),
173 m_lock(false /*reentrant*/, RankStatCacheNode),
174 m_wd(wd), m_valid(false), m_inExpirePaths(false) { }
176 void StatCache::Node::atomicRelease() {
177 if (m_wd != -1) {
178 m_statCache.removeWatch(m_wd);
180 touchLocked<true>();
181 detachLocked();
182 TRACE(1, "StatCache: delete node '%s'\n", m_path.c_str());
183 delete this;
186 template <bool removePaths>
187 void StatCache::Node::touchLocked(bool invalidate /* = true */) {
188 TRACE(1, "StatCache: touch %snode '%s'%s\n",
189 m_valid ? "" : "invalid ",
190 m_path.c_str(), removePaths ? " (remove paths)" : "");
191 if ((invalidate && m_valid) || removePaths) {
192 // Call path invalidation callback once for each path associated with this
193 // node and/or remove paths.
194 for (NameMap::const_iterator it = m_paths.begin(); it != m_paths.end();
195 ++it) {
196 if (invalidate && m_valid) {
197 TRACE(1, "StatCache: invalidate path '%s'\n", it->first.c_str());
198 HPHP::invalidatePath(it->first);
200 if (removePaths) {
201 m_statCache.removePath(it->first, this);
204 for (NameMap::const_iterator it = m_lpaths.begin(); it != m_lpaths.end();
205 ++it) {
206 if (invalidate && m_valid) {
207 // Avoid duplicate invalidations.
208 NameMap::const_iterator it2 = m_paths.find(it->first);
209 if (it2 == m_paths.end()) {
210 TRACE(1, "StatCache: invalidate link path '%s'\n", it->first.c_str());
211 HPHP::invalidatePath(it->first);
214 if (removePaths) {
215 m_statCache.removeLPath(it->first, this);
218 if (removePaths) {
219 m_paths.clear();
220 m_lpaths.clear();
223 m_link.clear();
224 m_valid = false;
227 void StatCache::Node::touch(bool invalidate /* = true */) {
228 SimpleLock lock(m_lock);
229 touchLocked<false>(invalidate);
232 void StatCache::Node::detachLocked() {
233 m_children.clear();
234 m_lChildren.clear();
237 void StatCache::Node::expirePaths(bool invalidate /* = true */) {
238 NameNodeMap children, lChildren;
241 SimpleLock lock(m_lock);
243 if (m_inExpirePaths) {
244 // Terminate loop in recursion.
245 return;
248 touchLocked<true>(invalidate);
249 children = m_children;
250 lChildren = m_lChildren;
251 // expirePaths() is only called in situations where the entire subtree
252 // needs to be completely invalidated. If there were call for a 'touch'
253 // operation, then the detachLocked() call would need to be omitted.
254 detachLocked();
255 m_inExpirePaths = true;
258 for (NameNodeMap::const_iterator it = children.begin(); it != children.end();
259 ++it) {
260 it->second->expirePaths(invalidate);
262 for (NameNodeMap::const_iterator it = lChildren.begin();
263 it != lChildren.end(); ++it) {
264 // Only recurse if this node differs from the equivalent one in children,
265 // in order to keep recursion from being of exponential complexity.
266 NameNodeMap::const_iterator it2 = children.find(it->first);
267 if (it2 == children.end() || it->second.get() != it2->second.get()) {
268 it->second->expirePaths(invalidate);
272 SimpleLock lock(m_lock);
273 m_inExpirePaths = false;
276 bool StatCache::Node::validate(const std::string& path, bool& cached) {
277 if (!m_valid) {
278 if (statSyscall(path, &m_stat) == -1) {
279 TRACE(4, "StatCache: stat '%s' --> error (node=%p)\n",
280 path.c_str(), this);
281 return true;
283 TRACE(4, "StatCache: stat '%s' %s (node=%p)\n",
284 path.c_str(), statToString(&m_stat).c_str(), this);
285 if (lstatSyscall(path, &m_lstat) == -1) {
286 TRACE(4, "StatCache: lstat '%s' --> error (node=%p)\n",
287 path.c_str(), this);
288 return true;
290 TRACE(4, "StatCache: lstat '%s' %s (node=%p)\n",
291 path.c_str(), statToString(&m_lstat).c_str(), this);
292 m_valid = true;
293 cached = false;
294 } else {
295 TRACE(4, "StatCache: stat '%s' (node=%p, cached)\n", path.c_str(), this);
296 TRACE(4, "StatCache: lstat '%s' (node=%p, cached)\n", path.c_str(), this);
297 cached = true;
299 setPath(path);
300 return false;
303 void StatCache::Node::sanityCheck(const std::string& path, bool isStat,
304 const struct stat* buf, time_t lastRefresh) {
305 struct stat tbuf;
306 int err = isStat ? statSyscall(path, &tbuf) : lstatSyscall(path, &tbuf);
307 if (err != -1 && !statEquiv(buf, &tbuf)) {
308 if (lastRefresh == 0) {
309 lastRefresh = m_statCache.lastRefresh();
311 // stat info has changed since it was cached. If the changes were all made
312 // prior to the most recent refresh (excluding atime, since IN_ACCESS
313 // events aren't being processed), then they generally should have been
314 // merged into the cache during the refresh. Reality is a bit messier
315 // because inotify is asynchronous; the delay between a filesystem
316 // modification and the availability of a corresponding inotify event makes
317 // it possible for the most recent refresh to have missed in-flight
318 // notifications.
319 if (tbuf.st_mtime < lastRefresh && tbuf.st_ctime < lastRefresh) {
320 TRACE(0, "StatCache: suspect cached %s '%s' %s (node=%p);"
321 " actual %s; last refresh %lu\n", isStat ? "stat" : "lstat",
322 path.c_str(),
323 statToString(buf).c_str(), this,
324 statToString(&tbuf).c_str(), (unsigned long)lastRefresh);
329 int StatCache::Node::stat(const std::string& path, struct stat* buf,
330 time_t lastRefresh /* = 0 */) {
331 bool cached;
333 SimpleLock lock(m_lock);
334 if (validate(path, cached)) {
335 return -1;
337 m_paths.insert(std::make_pair(path, this));
338 memcpy(buf, &m_stat, sizeof(struct stat));
340 if (debug && cached) {
341 sanityCheck(path, true, buf, lastRefresh);
343 return 0;
346 int StatCache::Node::lstat(const std::string& path, struct stat* buf,
347 time_t lastRefresh /* = 0 */) {
348 bool cached;
350 SimpleLock lock(m_lock);
351 if (validate(path, cached)) {
352 return -1;
354 m_lpaths.insert(std::make_pair(path, this));
355 memcpy(buf, &m_lstat, sizeof(struct stat));
357 if (debug && cached) {
358 sanityCheck(path, false, buf, lastRefresh);
360 return 0;
363 bool StatCache::Node::isLinkLocked() {
364 m_lock.assertOwnedBySelf();
365 return S_ISLNK(m_lstat.st_mode);
368 bool StatCache::Node::isLink() {
369 SimpleLock lock(m_lock);
370 return isLinkLocked();
373 std::string StatCache::Node::readlink(const std::string& path,
374 time_t lastRefresh /* = 0 */) {
375 std::string link;
376 bool cached;
377 struct stat buf;
379 SimpleLock lock(m_lock);
380 if (validate(path, cached) || !isLinkLocked()) {
381 return "";
383 if (debug && cached) {
384 memcpy(&buf, &m_lstat, sizeof(struct stat));
386 if (m_link.size() == 0) {
387 m_link = readlinkSyscall(path);
389 link = m_link;
391 if (debug && cached) {
392 sanityCheck(path, false, &buf, lastRefresh);
394 return link;
397 void StatCache::Node::insertChild(const std::string& childName,
398 StatCache::NodePtr child, bool follow) {
399 auto& map = follow ? m_children : m_lChildren;
400 if (!map.insert(std::make_pair(childName, child)).second) {
401 assertx(0); // should not already exist in the map here.
405 void StatCache::Node::removeChild(const std::string& childName) {
406 if (m_children.count(childName)) {
407 m_children.erase(childName);
409 if (m_lChildren.count(childName)) {
410 m_lChildren.erase(childName);
414 StatCache::NodePtr StatCache::Node::getChild(const std::string& childName,
415 bool follow) {
416 return folly::get_default(follow ? m_children : m_lChildren, childName);
419 //==============================================================================
420 // StatCache.
422 StatCache::StatCache()
423 : m_lock(false /*reentrant*/, RankStatCache), m_ifd(-1),
424 m_lastRefresh(time(nullptr)) {
427 StatCache::~StatCache() {
428 clear();
431 bool StatCache::init() {
432 // inotify_init1() directly supports the fcntl() settings, but it's only
433 // available starting in Linux 2.6.27.
434 #ifdef __linux__
435 if ((m_ifd = inotify_init()) == -1
436 || fcntl(m_ifd, F_SETFD, FD_CLOEXEC) == -1
437 || fcntl(m_ifd, F_SETFL, O_NONBLOCK) == -1
438 || (m_root = getNode("/", false)).get() == nullptr) {
439 clear();
440 return true;
442 return false;
443 #else
444 return true;
445 #endif
448 void StatCache::clear() {
449 if (m_ifd != -1) {
450 close(m_ifd);
451 m_ifd = -1;
453 m_watch2Node.clear();
454 // It's unsafe to reset() m_path2Node / m_lpath2Node while concurrent
455 // accessors might be touching it. Recursively letting them remove
456 // themselves via expiry will remove them one by one via erase(). The call
457 // to expirePaths() cannot be safely omitted, because it would otherwise be
458 // possible for a symlink-induced cycle to keep some or all of the node tree
459 // alive.
460 if (m_root.get()) {
461 m_root->expirePaths();
463 m_root = nullptr;
464 assertx(m_path2Node.size() == 0);
465 assertx(m_lpath2Node.size() == 0);
468 void StatCache::reset() {
469 clear();
470 init();
473 StatCache::NodePtr StatCache::getNode(const std::string& path, bool follow) {
474 #ifdef __linux__
475 int wd = inotify_add_watch(m_ifd, path.c_str(),
477 | IN_MODIFY
478 | IN_ATTRIB
479 | IN_MOVED_FROM
480 | IN_MOVED_TO
481 | IN_CREATE
482 | IN_DELETE
483 | (follow ? 0 : IN_DONT_FOLLOW)
484 | IN_ONLYDIR);
485 if (wd == -1 && errno != ENOTDIR) {
486 TRACE(2, "StatCache: getNode('%s', follow=%s) failed\n",
487 path.c_str(), follow ? "true" : "false");
488 return NodePtr(nullptr);
490 NodePtr node;
491 if (wd != -1) {
492 node = folly::get_default(m_watch2Node, wd);
493 if (!node.get()) {
494 node = new Node(*this, wd);
495 if (!m_watch2Node.insert(std::make_pair(wd, node)).second) {
496 assertx(0); // should not already exist in the map
498 TRACE(2, "StatCache: getNode('%s', follow=%s) --> %p (wd=%d)\n",
499 path.c_str(), follow ? "true" : "false", node.get(), wd);
500 } else {
501 TRACE(3, "StatCache: getNode('%s', follow=%s) --> alias %p (wd=%d)\n",
502 path.c_str(), follow ? "true" : "false", node.get(), wd);
504 } else {
505 node = new Node(*this);
506 TRACE(3, "StatCache: getNode('%s', follow=%s) --> %p\n",
507 path.c_str(), follow ? "true" : "false", node.get());
509 node->setPath(path);
510 return node;
511 #else
512 return NodePtr(nullptr);
513 #endif
516 bool StatCache::mergePath(const std::string& path, bool follow) {
517 String canonicalPath = FileUtil::canonicalize(path);
518 std::vector<std::string> pvec;
519 folly::split('/', canonicalPath.slice(), pvec);
520 assertx((pvec[0].size() == 0)); // path should be absolute.
521 // Lazily initialize so that if StatCache never gets used, no kernel
522 // resources are consumed.
523 if (m_ifd == -1 && init()) {
524 return true;
526 NodePtr curNode = m_root;
527 std::string curPath = "/";
528 for (unsigned i = 1; i < pvec.size(); ++i) {
529 // Follow links unless 'follow' is false and this is the last path
530 // component.
531 bool curFollow = (follow || i + 1 < pvec.size());
532 curPath += pvec[i];
533 NodePtr child = curNode->getChild(pvec[i], curFollow);
534 if (child.get() == nullptr) {
535 child = getNode(curPath, curFollow);
536 if (child.get() == nullptr) {
537 return true;
539 curNode->insertChild(pvec[i], child, curFollow);
541 curNode = child;
542 curPath += "/";
544 NameNodeMap::accessor acc;
545 NameNodeMap& p2n = follow ? m_path2Node : m_lpath2Node;
546 if (p2n.insert(acc, path)) {
547 acc->second = curNode;
548 TRACE(1, "StatCache: merge '%s' --> %p (follow=%s)\n",
549 path.c_str(), curNode.get(), follow ? "true" : "false");
551 return false;
554 #ifdef __linux__
555 bool StatCache::handleEvent(const struct inotify_event* event) {
556 if (event->mask & IN_Q_OVERFLOW) {
557 // The event queue overflowed, so all bets are off. Start over.
558 TRACE(0, "StatCache: event queue overflowed\n");
559 reset();
560 return true;
562 assertx(event->wd != -1);
563 NodePtr node = folly::get_default(m_watch2Node, event->wd);
564 if (!node.get()) {
565 TRACE(1, "StatCache: inotify event (obsolete) %s\n",
566 eventToString(event).c_str());
567 return false;
569 TRACE(1, "StatCache: inotify event for '%s': %s\n",
570 node->path().c_str(), eventToString(event).c_str());
572 if (event->mask & (IN_MODIFY|IN_ATTRIB)) {
573 bool touched = false;
574 NodePtr child = node->getChild(event->name, true);
575 if (child.get() != nullptr) {
576 if ((event->mask & IN_MODIFY) && child->isLink()) {
577 // A modified link is logically equivalent to IN_MOVED_FROM.
578 child->expirePaths();
579 node->removeChild(event->name);
580 } else {
581 child->touch();
583 touched = true;
585 child = node->getChild(event->name, false);
586 if (child.get() != nullptr) {
587 // The follow=false child is equivalent to the follow=true child unless
588 // it's a link. Avoid duplicate invalidations for non-links.
589 child->touch(!touched || child->isLink());
592 if (event->mask & (IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE)) {
593 // The directory itself was modified, so invalidate its cached stat
594 // structure.
595 node->touch();
596 // Recursively invalidate the cached paths rooted at "node/name".
597 bool expired = false;
598 NodePtr child = node->getChild(event->name, true);
599 if (child.get() != nullptr) {
600 child->expirePaths();
601 expired = true;
603 child = node->getChild(event->name, false);
604 if (child.get() != nullptr) {
605 // The follow=false child is equivalent to the follow=true child unless
606 // it's a link. Avoid duplicate invalidations for non-links.
607 child->expirePaths(!expired || child->isLink());
608 expired = true;
610 if (expired) {
611 node->removeChild(event->name);
614 if (event->mask & IN_IGNORED) {
615 // The kernel removed the directory watch, either as a side effect of
616 // directory deletion, or because inotify_rm_watch() was explicitly called
617 // during Node destruction. Delete the corresponding entry from
618 // m_watch2Node. Removal should always succeed here because no other code
619 // performs removal.
620 m_watch2Node.erase(event->wd);
622 return false;
624 #endif
626 void StatCache::removeWatch(int wd) {
627 #ifdef __linux__
628 inotify_rm_watch(m_ifd, wd);
629 #endif
632 void StatCache::removePath(const std::string& path, Node* node) {
633 NameNodeMap::accessor acc;
634 if (m_path2Node.find(acc, path) && acc->second.get() == node) {
635 TRACE(1, "StatCache: remove path '%s'\n", path.c_str());
636 m_path2Node.erase(acc);
640 void StatCache::removeLPath(const std::string& path, Node* node) {
641 NameNodeMap::accessor acc;
642 if (m_lpath2Node.find(acc, path) && acc->second.get() == node) {
643 TRACE(1, "StatCache: remove link path '%s'\n", path.c_str());
644 m_lpath2Node.erase(acc);
648 void StatCache::refresh() {
649 #ifdef __linux__
650 SimpleLock lock(m_lock);
652 if (m_ifd == -1) {
653 return;
656 while (true) {
657 int nread = read(m_ifd, m_readBuf, kReadBufSize);
658 if (nread == -1) {
659 // No pending events.
660 assertx(errno == EAGAIN);
661 // Record the last refresh time *after* processing the event queue, in
662 // order to assure that once the event queue has been merged into the
663 // cache state, all cached values have timestamps older than
664 // m_lastRefresh (assuming no timestamps are ever set into the future).
665 m_lastRefresh = time(nullptr);
666 TRACE(1, "StatCache: refresh time %lu\n", (unsigned long)m_lastRefresh);
667 return;
669 for (char* p = m_readBuf; p < m_readBuf + nread;) {
670 struct inotify_event* event = (struct inotify_event*) p;
671 if (handleEvent(event)) {
672 return;
674 p += sizeof(struct inotify_event) + event->len;
677 #endif
680 time_t StatCache::lastRefresh() {
681 SimpleLock lock(m_lock);
683 return m_lastRefresh;
686 int StatCache::statImpl(const std::string& path, struct stat* buf) {
687 // Punt if path is relative.
688 if (path.size() == 0 || path[0] != '/') {
689 return statSyscall(path, buf);
692 NodePtr p;
694 NameNodeMap::const_accessor acc;
695 if (m_path2Node.find(acc, path)) {
696 p = acc->second;
699 if (p) {
700 return p->stat(path, buf);
703 SimpleLock lock(m_lock);
704 if (mergePath(path, true)) {
705 return statSyscall(path, buf);
708 NameNodeMap::const_accessor acc;
709 if (m_path2Node.find(acc, path)) {
710 return acc->second->stat(path, buf, m_lastRefresh);
714 not_reached();
717 int StatCache::lstatImpl(const std::string& path, struct stat* buf) {
718 // Punt if path is relative.
719 if (path.size() == 0 || path[0] != '/') {
720 return statSyscall(path, buf);
723 NodePtr p;
725 NameNodeMap::const_accessor acc;
726 if (m_lpath2Node.find(acc, path)) {
727 p = acc->second;
730 if (p) {
731 return p->lstat(path, buf);
734 SimpleLock lock(m_lock);
735 if (mergePath(path, false)) {
736 return lstatSyscall(path, buf);
739 NameNodeMap::const_accessor acc;
740 if (m_lpath2Node.find(acc, path)) {
741 return acc->second->lstat(path, buf, m_lastRefresh);
745 not_reached();
748 std::string StatCache::readlinkImpl(const std::string& path) {
749 // Punt if path is relative.
750 if (path.size() == 0 || path[0] != '/') {
751 return readlinkSyscall(path);
754 NodePtr p;
756 NameNodeMap::const_accessor acc;
757 if (m_lpath2Node.find(acc, path)) {
758 p = acc->second;
761 if (p) {
762 return p->readlink(path);
765 SimpleLock lock(m_lock);
766 if (mergePath(path, false)) {
767 return readlinkSyscall(path);
770 NameNodeMap::const_accessor acc;
771 if (m_lpath2Node.find(acc, path)) {
772 return acc->second->readlink(path, m_lastRefresh);
776 not_reached();
779 // StatCache::realpath() is based on the realpath(3) implementation that is
780 // part of FreeBSD's libc. The following license applies:
783 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
785 * Redistribution and use in source and binary forms, with or without
786 * modification, are permitted provided that the following conditions
787 * are met:
788 * 1. Redistributions of source code must retain the above copyright
789 * notice, this list of conditions and the following disclaimer.
790 * 2. Redistributions in binary form must reproduce the above copyright
791 * notice, this list of conditions and the following disclaimer in the
792 * documentation and/or other materials provided with the distribution.
793 * 3. The names of the authors may not be used to endorse or promote
794 * products derived from this software without specific prior written
795 * permission.
797 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
798 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
799 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
800 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
801 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
802 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
803 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
804 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
805 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
806 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
807 * SUCH DAMAGE.
809 #if 0
810 __FBSDID("$FreeBSD: src/lib/libc/stdlib/realpath.c,v 1.24 2011/11/04 19:56:34 ed Exp $");
811 #endif
813 // Find the real name of path, by removing all ".", ".." and symlink
814 // components. Returns the resolved path on success, or "" on failure,
815 std::string StatCache::realpathImpl(const char* path) {
816 std::string resolved;
817 assertx(path != nullptr);
818 if (path[0] != '/') {
819 return realpathLibc(path);
821 struct stat sb;
822 unsigned symlinks;
823 std::string left, next_token, symlink;
824 size_t left_pos;
826 symlinks = 0;
827 resolved += "/";
828 if (path[1] == '\0') {
829 TRACE(4, "StatCache: realpath('%s') --> '%s'\n", path, resolved.c_str());
830 return resolved;
832 left = path;
833 left_pos = 0;
835 // Iterate over path components in `left'.
836 while (left.size() - left_pos != 0) {
837 // Extract the next path component and adjust `left' and its length.
838 size_t pos = left.find_first_of('/', left_pos);
839 next_token = left.substr(left_pos, pos - left_pos);
840 left_pos += next_token.size();
841 if (pos != std::string::npos) {
842 ++left_pos;
845 if (resolved[resolved.size() - 1] != '/') {
846 resolved += "/";
848 if (next_token.size() == 0) {
849 continue;
850 } else if (next_token == ".") {
851 continue;
852 } else if (next_token == "..") {
853 // Strip the last path component except when we have single "/".
854 if (resolved.size() > 1) {
855 resolved.erase(resolved.size() - 1);
856 resolved.erase(resolved.find_last_of('/') + 1);
858 continue;
861 // Append the next path component and lstat() it. If lstat() fails we still
862 // can return successfully if there are no more path components left.
863 resolved += next_token;
864 if (lstatImpl(resolved, &sb) != 0) {
865 if (errno == ENOENT && pos == std::string::npos) {
866 TRACE(4, "StatCache: realpath('%s') --> '%s'\n",
867 path, resolved.c_str());
868 return resolved;
870 TRACE(4, "StatCache: realpath('%s') --> error\n", path);
871 return "";
873 if (S_ISLNK(sb.st_mode)) {
874 if (symlinks++ > MAXSYMLINKS) {
875 TRACE(4, "StatCache: realpath('%s') --> error\n", path);
876 return "";
878 symlink = readlinkImpl(resolved);
879 if (symlink.size() == 0) {
880 TRACE(4, "StatCache: realpath('%s') --> error\n", path);
881 return "";
883 if (symlink[0] == '/') {
884 resolved.erase(1);
885 } else if (resolved.size() > 1) {
886 // Strip the last path component.
887 resolved.erase(resolved.size() - 1);
888 resolved.erase(resolved.find_last_of('/') + 1);
891 // If there are any path components left, then append them to symlink.
892 // The result is placed in `left'.
893 if (pos != std::string::npos) {
894 if (symlink[symlink.size() - 1] != '/') {
895 symlink += "/";
897 symlink += left.substr(left_pos);
899 left = symlink;
900 left_pos = 0;
904 // Remove trailing slash except when the resolved pathname is a single "/".
905 if (resolved.size() > 1 && resolved[resolved.size() - 1] == '/') {
906 resolved.erase(resolved.size() - 1);
909 TRACE(4, "StatCache: realpath('%s') --> '%s'\n", path, resolved.c_str());
910 return resolved;
913 StatCache StatCache::s_sc;
915 void StatCache::requestInit() {
916 if (!RuntimeOption::ServerStatCache) return;
917 TRACE(5, "StatCache: requestInit refresh");
918 s_sc.refresh();
921 int StatCache::stat(const std::string& path, struct stat* buf) {
922 if (!RuntimeOption::ServerStatCache) return statSyscall(path, buf);
923 return s_sc.statImpl(path, buf);
926 int StatCache::lstat(const std::string& path, struct stat* buf) {
927 if (!RuntimeOption::ServerStatCache) return lstatSyscall(path, buf);
928 return s_sc.lstatImpl(path, buf);
931 std::string StatCache::readlink(const std::string& path) {
932 if (!RuntimeOption::ServerStatCache) return readlinkSyscall(path);
933 return s_sc.readlinkImpl(path);
936 std::string StatCache::realpath(const char* path) {
937 if (!RuntimeOption::ServerStatCache) return realpathLibc(path);
938 return s_sc.realpathImpl(path);
941 void StatCache::clearCache() {
942 if (!RuntimeOption::ServerStatCache) return;
944 SimpleLock lock(s_sc.m_lock);
945 s_sc.clear();
948 ///////////////////////////////////////////////////////////////////////////////