Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / stat-cache.cpp
blob4e285bc24bc0cc20abefbef2a0d1f9cd0e4e4e56
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 <vector>
24 #include <folly/MapUtil.h>
25 #include <folly/portability/Unistd.h>
27 #include "hphp/util/trace.h"
28 #include "hphp/runtime/base/runtime-option.h"
29 #include "hphp/runtime/vm/jit/hooks.h"
30 #include "hphp/util/text-util.h"
31 #include "hphp/runtime/base/file-util.h"
33 namespace HPHP {
34 ///////////////////////////////////////////////////////////////////////////////
36 TRACE_SET_MOD(stat);
38 UNUSED static std::string statToString(const struct stat* buf) {
39 std::ostringstream os;
40 os << "struct stat {";
41 os << "dev=" << buf->st_dev << ", ";
42 os << "ino=" << buf->st_ino << ", ";
43 os << "mode=0" << std::oct << buf->st_mode << std::dec << ", ";
44 os << "nlink=" << buf->st_nlink << ", ";
45 os << "uid=" << buf->st_uid << ", ";
46 os << "gid=" << buf->st_gid << ", ";
47 os << "rdev=" << buf->st_rdev << ", ";
48 os << "size=" << buf->st_size << ", ";
49 #ifndef _MSC_VER
50 os << "blksize=" << buf->st_blksize << ", ";
51 os << "blocks=" << buf->st_blocks << ", ";
52 #endif
53 os << "atime=" << buf->st_atime << ", ";
54 os << "mtime=" << buf->st_mtime << ", ";
55 os << "ctime=" << buf->st_ctime;
56 os << "}";
57 return os.str();
60 UNUSED static bool statEquiv(const struct stat* stA, const struct stat* stB) {
61 return (stA->st_dev == stB->st_dev
62 && stA->st_ino == stB->st_ino
63 && stA->st_mode == stB->st_mode
64 && stA->st_nlink == stB->st_nlink
65 && stA->st_uid == stB->st_uid
66 && stA->st_gid == stB->st_gid
67 && stA->st_rdev == stB->st_rdev
68 && stA->st_size == stB->st_size
69 #ifndef _MSC_VER
70 && stA->st_blksize == stB->st_blksize
71 && stA->st_blocks == stB->st_blocks
72 #endif
73 /* Intentionally omitted:
74 && stA->st_atime == stB->st_atime
76 && stA->st_mtime == stB->st_mtime
77 && stA->st_ctime == stB->st_ctime);
80 #ifdef __linux__
81 UNUSED static std::string eventToString(const struct inotify_event* ie) {
82 bool first = true;
83 std::ostringstream os;
84 os << "struct inotify_event {wd=" << ie->wd << ", mask=(";
85 #define EVENT(e) do { \
86 if (ie->mask & e) { \
87 if (first) { \
88 first = false; \
89 } else { \
90 os << "|"; \
91 } \
92 os << #e; \
93 } \
94 } while (0)
95 EVENT(IN_ACCESS);
96 EVENT(IN_MODIFY);
97 EVENT(IN_ATTRIB);
98 EVENT(IN_CLOSE_WRITE);
99 EVENT(IN_CLOSE_NOWRITE);
100 EVENT(IN_OPEN);
101 EVENT(IN_MOVED_FROM);
102 EVENT(IN_MOVED_TO);
103 EVENT(IN_CREATE);
104 EVENT(IN_DELETE);
105 EVENT(IN_DELETE_SELF);
106 EVENT(IN_MOVE_SELF);
107 EVENT(IN_UNMOUNT);
108 EVENT(IN_Q_OVERFLOW);
109 EVENT(IN_IGNORED);
110 EVENT(IN_ISDIR);
111 #undef EVENT
112 os << ")";
113 if (ie->cookie != 0) os << ", cookie=" << ie->cookie;
114 if (ie->len != 0) os << ", name='" << ie->name << "'";
115 os << "}";
116 return os.str();
118 #endif
120 static int statSyscall(const std::string& path, struct stat* buf) {
121 int ret = ::stat(path.c_str(), buf);
122 if (ret == 0) {
123 TRACE(5, "StatCache: stat '%s' %s\n",
124 path.c_str(), statToString(buf).c_str());
125 } else {
126 TRACE(5, "StatCache: stat '%s' --> error\n", path.c_str());
128 return ret;
131 static int lstatSyscall(const std::string& path, struct stat* buf) {
132 int ret = ::lstat(path.c_str(), buf);
133 if (ret == 0) {
134 TRACE(5, "StatCache: lstat '%s' %s\n",
135 path.c_str(), statToString(buf).c_str());
136 } else {
137 TRACE(5, "StatCache: lstat '%s' --> error\n", path.c_str());
139 return ret;
142 static std::string readlinkSyscall(const std::string& path) {
143 char lbuf[PATH_MAX + 1];
144 ssize_t llen = ::readlink(path.c_str(), lbuf, sizeof(lbuf) - 1);
145 if (llen == -1) {
146 TRACE(5, "StatCache: readlink('%s') --> error\n", path.c_str());
147 return "";
149 lbuf[llen] = '\0';
150 TRACE(5, "StatCache: readlink('%s') --> '%s'\n", path.c_str(), lbuf);
151 return lbuf;
154 static std::string realpathLibc(const char* path) {
155 char buf[PATH_MAX];
157 std::string ret;
158 if (!::realpath(path, buf)) {
159 TRACE(5, "StatCache: realpath('%s') --> error\n", path);
160 return ret;
162 TRACE(5, "StatCache: realpath('%s') --> '%s'\n", path, buf);
163 ret = buf;
164 return ret;
167 //==============================================================================
168 // StatCache::Node.
170 StatCache::Node::Node(StatCache& statCache, int wd /* = -1 */)
171 : m_statCache(statCache),
172 m_lock(false /*reentrant*/, RankStatCacheNode),
173 m_wd(wd), m_valid(false), m_inExpirePaths(false) { }
175 void StatCache::Node::atomicRelease() {
176 if (m_wd != -1) {
177 m_statCache.removeWatch(m_wd);
179 touchLocked<true>();
180 detachLocked();
181 TRACE(1, "StatCache: delete node '%s'\n", m_path.c_str());
182 delete this;
185 template <bool removePaths>
186 void StatCache::Node::touchLocked(bool invalidate /* = true */) {
187 TRACE(1, "StatCache: touch %snode '%s'%s\n",
188 m_valid ? "" : "invalid ",
189 m_path.c_str(), removePaths ? " (remove paths)" : "");
190 if ((invalidate && m_valid) || removePaths) {
191 // Call path invalidation callback once for each path associated with this
192 // node and/or remove paths.
193 for (NameMap::const_iterator it = m_paths.begin(); it != m_paths.end();
194 ++it) {
195 if (invalidate && m_valid) {
196 TRACE(1, "StatCache: invalidate path '%s'\n", it->first.c_str());
197 HPHP::invalidatePath(it->first);
199 if (removePaths) {
200 m_statCache.removePath(it->first, this);
203 for (NameMap::const_iterator it = m_lpaths.begin(); it != m_lpaths.end();
204 ++it) {
205 if (invalidate && m_valid) {
206 // Avoid duplicate invalidations.
207 NameMap::const_iterator it2 = m_paths.find(it->first);
208 if (it2 == m_paths.end()) {
209 TRACE(1, "StatCache: invalidate link path '%s'\n", it->first.c_str());
210 HPHP::invalidatePath(it->first);
213 if (removePaths) {
214 m_statCache.removeLPath(it->first, this);
217 if (removePaths) {
218 m_paths.clear();
219 m_lpaths.clear();
222 m_link.clear();
223 m_valid = false;
226 void StatCache::Node::touch(bool invalidate /* = true */) {
227 SimpleLock lock(m_lock);
228 touchLocked<false>(invalidate);
231 void StatCache::Node::detachLocked() {
232 m_children.clear();
233 m_lChildren.clear();
236 void StatCache::Node::expirePaths(bool invalidate /* = true */) {
237 NameNodeMap children, lChildren;
240 SimpleLock lock(m_lock);
242 if (m_inExpirePaths) {
243 // Terminate loop in recursion.
244 return;
247 touchLocked<true>(invalidate);
248 children = m_children;
249 lChildren = m_lChildren;
250 // expirePaths() is only called in situations where the entire subtree
251 // needs to be completely invalidated. If there were call for a 'touch'
252 // operation, then the detachLocked() call would need to be omitted.
253 detachLocked();
254 m_inExpirePaths = true;
257 for (NameNodeMap::const_iterator it = children.begin(); it != children.end();
258 ++it) {
259 it->second->expirePaths(invalidate);
261 for (NameNodeMap::const_iterator it = lChildren.begin();
262 it != lChildren.end(); ++it) {
263 // Only recurse if this node differs from the equivalent one in children,
264 // in order to keep recursion from being of exponential complexity.
265 NameNodeMap::const_iterator it2 = children.find(it->first);
266 if (it2 == children.end() || it->second.get() != it2->second.get()) {
267 it->second->expirePaths(invalidate);
271 SimpleLock lock(m_lock);
272 m_inExpirePaths = false;
275 bool StatCache::Node::validate(const std::string& path, bool& cached) {
276 if (!m_valid) {
277 if (statSyscall(path, &m_stat) == -1) {
278 TRACE(4, "StatCache: stat '%s' --> error (node=%p)\n",
279 path.c_str(), this);
280 return true;
282 TRACE(4, "StatCache: stat '%s' %s (node=%p)\n",
283 path.c_str(), statToString(&m_stat).c_str(), this);
284 if (lstatSyscall(path, &m_lstat) == -1) {
285 TRACE(4, "StatCache: lstat '%s' --> error (node=%p)\n",
286 path.c_str(), this);
287 return true;
289 TRACE(4, "StatCache: lstat '%s' %s (node=%p)\n",
290 path.c_str(), statToString(&m_lstat).c_str(), this);
291 m_valid = true;
292 cached = false;
293 } else {
294 TRACE(4, "StatCache: stat '%s' (node=%p, cached)\n", path.c_str(), this);
295 TRACE(4, "StatCache: lstat '%s' (node=%p, cached)\n", path.c_str(), this);
296 cached = true;
298 setPath(path);
299 return false;
302 void StatCache::Node::sanityCheck(const std::string& path, bool isStat,
303 const struct stat* buf, time_t lastRefresh) {
304 struct stat tbuf;
305 int err = isStat ? statSyscall(path, &tbuf) : lstatSyscall(path, &tbuf);
306 if (err != -1 && !statEquiv(buf, &tbuf)) {
307 if (lastRefresh == 0) {
308 lastRefresh = m_statCache.lastRefresh();
310 // stat info has changed since it was cached. If the changes were all made
311 // prior to the most recent refresh (excluding atime, since IN_ACCESS
312 // events aren't being processed), then they generally should have been
313 // merged into the cache during the refresh. Reality is a bit messier
314 // because inotify is asynchronous; the delay between a filesystem
315 // modification and the availability of a corresponding inotify event makes
316 // it possible for the most recent refresh to have missed in-flight
317 // notifications.
318 if (tbuf.st_mtime < lastRefresh && tbuf.st_ctime < lastRefresh) {
319 TRACE(0, "StatCache: suspect cached %s '%s' %s (node=%p);"
320 " actual %s; last refresh %lu\n", isStat ? "stat" : "lstat",
321 path.c_str(),
322 statToString(buf).c_str(), this,
323 statToString(&tbuf).c_str(), (unsigned long)lastRefresh);
328 int StatCache::Node::stat(const std::string& path, struct stat* buf,
329 time_t lastRefresh /* = 0 */) {
330 bool cached;
332 SimpleLock lock(m_lock);
333 if (validate(path, cached)) {
334 return -1;
336 m_paths.insert(std::make_pair(path, this));
337 memcpy(buf, &m_stat, sizeof(struct stat));
339 if (debug && cached) {
340 sanityCheck(path, true, buf, lastRefresh);
342 return 0;
345 int StatCache::Node::lstat(const std::string& path, struct stat* buf,
346 time_t lastRefresh /* = 0 */) {
347 bool cached;
349 SimpleLock lock(m_lock);
350 if (validate(path, cached)) {
351 return -1;
353 m_lpaths.insert(std::make_pair(path, this));
354 memcpy(buf, &m_lstat, sizeof(struct stat));
356 if (debug && cached) {
357 sanityCheck(path, false, buf, lastRefresh);
359 return 0;
362 bool StatCache::Node::isLinkLocked() {
363 m_lock.assertOwnedBySelf();
364 return S_ISLNK(m_lstat.st_mode);
367 bool StatCache::Node::isLink() {
368 SimpleLock lock(m_lock);
369 return isLinkLocked();
372 std::string StatCache::Node::readlink(const std::string& path,
373 time_t lastRefresh /* = 0 */) {
374 std::string link;
375 bool cached;
376 struct stat buf;
378 SimpleLock lock(m_lock);
379 if (validate(path, cached) || !isLinkLocked()) {
380 return "";
382 if (debug && cached) {
383 memcpy(&buf, &m_lstat, sizeof(struct stat));
385 if (m_link.size() == 0) {
386 m_link = readlinkSyscall(path);
388 link = m_link;
390 if (debug && cached) {
391 sanityCheck(path, false, &buf, lastRefresh);
393 return link;
396 void StatCache::Node::insertChild(const std::string& childName,
397 StatCache::NodePtr child, bool follow) {
398 auto& map = follow ? m_children : m_lChildren;
399 if (!map.insert(std::make_pair(childName, child)).second) {
400 assert(0); // should not already exist in the map here.
404 void StatCache::Node::removeChild(const std::string& childName) {
405 if (m_children.count(childName)) {
406 m_children.erase(childName);
408 if (m_lChildren.count(childName)) {
409 m_lChildren.erase(childName);
413 StatCache::NodePtr StatCache::Node::getChild(const std::string& childName,
414 bool follow) {
415 return folly::get_default(follow ? m_children : m_lChildren, childName);
418 //==============================================================================
419 // StatCache.
421 StatCache::StatCache()
422 : m_lock(false /*reentrant*/, RankStatCache), m_ifd(-1),
423 m_lastRefresh(time(nullptr)) {
426 StatCache::~StatCache() {
427 clear();
430 bool StatCache::init() {
431 // inotify_init1() directly supports the fcntl() settings, but it's only
432 // available starting in Linux 2.6.27.
433 #ifdef __linux__
434 if ((m_ifd = inotify_init()) == -1
435 || fcntl(m_ifd, F_SETFD, FD_CLOEXEC) == -1
436 || fcntl(m_ifd, F_SETFL, O_NONBLOCK) == -1
437 || (m_root = getNode("/", false)).get() == nullptr) {
438 clear();
439 return true;
441 return false;
442 #else
443 return true;
444 #endif
447 void StatCache::clear() {
448 if (m_ifd != -1) {
449 close(m_ifd);
450 m_ifd = -1;
452 m_watch2Node.clear();
453 // It's unsafe to reset() m_path2Node / m_lpath2Node while concurrent
454 // accessors might be touching it. Recursively letting them remove
455 // themselves via expiry will remove them one by one via erase(). The call
456 // to expirePaths() cannot be safely omitted, because it would otherwise be
457 // possible for a symlink-induced cycle to keep some or all of the node tree
458 // alive.
459 if (m_root.get()) {
460 m_root->expirePaths();
462 m_root = nullptr;
463 assert(m_path2Node.size() == 0);
464 assert(m_lpath2Node.size() == 0);
467 void StatCache::reset() {
468 clear();
469 init();
472 StatCache::NodePtr StatCache::getNode(const std::string& path, bool follow) {
473 #ifdef __linux__
474 int wd = inotify_add_watch(m_ifd, path.c_str(),
476 | IN_MODIFY
477 | IN_ATTRIB
478 | IN_MOVED_FROM
479 | IN_MOVED_TO
480 | IN_CREATE
481 | IN_DELETE
482 | (follow ? 0 : IN_DONT_FOLLOW)
483 | IN_ONLYDIR);
484 if (wd == -1 && errno != ENOTDIR) {
485 TRACE(2, "StatCache: getNode('%s', follow=%s) failed\n",
486 path.c_str(), follow ? "true" : "false");
487 return NodePtr(nullptr);
489 NodePtr node;
490 if (wd != -1) {
491 node = folly::get_default(m_watch2Node, wd);
492 if (!node.get()) {
493 node = new Node(*this, wd);
494 if (!m_watch2Node.insert(std::make_pair(wd, node)).second) {
495 assert(0); // should not already exist in the map
497 TRACE(2, "StatCache: getNode('%s', follow=%s) --> %p (wd=%d)\n",
498 path.c_str(), follow ? "true" : "false", node.get(), wd);
499 } else {
500 TRACE(3, "StatCache: getNode('%s', follow=%s) --> alias %p (wd=%d)\n",
501 path.c_str(), follow ? "true" : "false", node.get(), wd);
503 } else {
504 node = new Node(*this);
505 TRACE(3, "StatCache: getNode('%s', follow=%s) --> %p\n",
506 path.c_str(), follow ? "true" : "false", node.get());
508 node->setPath(path);
509 return node;
510 #else
511 return NodePtr(nullptr);
512 #endif
515 bool StatCache::mergePath(const std::string& path, bool follow) {
516 String canonicalPath = FileUtil::canonicalize(path);
517 std::vector<std::string> pvec;
518 folly::split('/', canonicalPath.slice(), pvec);
519 assert((pvec[0].size() == 0)); // path should be absolute.
520 // Lazily initialize so that if StatCache never gets used, no kernel
521 // resources are consumed.
522 if (m_ifd == -1 && init()) {
523 return true;
525 NodePtr curNode = m_root;
526 std::string curPath = "/";
527 for (unsigned i = 1; i < pvec.size(); ++i) {
528 // Follow links unless 'follow' is false and this is the last path
529 // component.
530 bool curFollow = (follow || i + 1 < pvec.size());
531 curPath += pvec[i];
532 NodePtr child = curNode->getChild(pvec[i], curFollow);
533 if (child.get() == nullptr) {
534 child = getNode(curPath, curFollow);
535 if (child.get() == nullptr) {
536 return true;
538 curNode->insertChild(pvec[i], child, curFollow);
540 curNode = child;
541 curPath += "/";
543 NameNodeMap::accessor acc;
544 NameNodeMap& p2n = follow ? m_path2Node : m_lpath2Node;
545 if (p2n.insert(acc, path)) {
546 acc->second = curNode;
547 TRACE(1, "StatCache: merge '%s' --> %p (follow=%s)\n",
548 path.c_str(), curNode.get(), follow ? "true" : "false");
550 return false;
553 #ifdef __linux__
554 bool StatCache::handleEvent(const struct inotify_event* event) {
555 if (event->mask & IN_Q_OVERFLOW) {
556 // The event queue overflowed, so all bets are off. Start over.
557 TRACE(0, "StatCache: event queue overflowed\n");
558 reset();
559 return true;
561 assert(event->wd != -1);
562 NodePtr node = folly::get_default(m_watch2Node, event->wd);
563 if (!node.get()) {
564 TRACE(1, "StatCache: inotify event (obsolete) %s\n",
565 eventToString(event).c_str());
566 return false;
568 TRACE(1, "StatCache: inotify event for '%s': %s\n",
569 node->path().c_str(), eventToString(event).c_str());
571 if (event->mask & (IN_MODIFY|IN_ATTRIB)) {
572 bool touched = false;
573 NodePtr child = node->getChild(event->name, true);
574 if (child.get() != nullptr) {
575 if ((event->mask & IN_MODIFY) && child->isLink()) {
576 // A modified link is logically equivalent to IN_MOVED_FROM.
577 child->expirePaths();
578 node->removeChild(event->name);
579 } else {
580 child->touch();
582 touched = true;
584 child = node->getChild(event->name, false);
585 if (child.get() != nullptr) {
586 // The follow=false child is equivalent to the follow=true child unless
587 // it's a link. Avoid duplicate invalidations for non-links.
588 child->touch(!touched || child->isLink());
591 if (event->mask & (IN_MOVED_FROM|IN_MOVED_TO|IN_CREATE|IN_DELETE)) {
592 // The directory itself was modified, so invalidate its cached stat
593 // structure.
594 node->touch();
595 // Recursively invalidate the cached paths rooted at "node/name".
596 bool expired = false;
597 NodePtr child = node->getChild(event->name, true);
598 if (child.get() != nullptr) {
599 child->expirePaths();
600 expired = true;
602 child = node->getChild(event->name, false);
603 if (child.get() != nullptr) {
604 // The follow=false child is equivalent to the follow=true child unless
605 // it's a link. Avoid duplicate invalidations for non-links.
606 child->expirePaths(!expired || child->isLink());
607 expired = true;
609 if (expired) {
610 node->removeChild(event->name);
613 if (event->mask & IN_IGNORED) {
614 // The kernel removed the directory watch, either as a side effect of
615 // directory deletion, or because inotify_rm_watch() was explicitly called
616 // during Node destruction. Delete the corresponding entry from
617 // m_watch2Node. Removal should always succeed here because no other code
618 // performs removal.
619 m_watch2Node.erase(event->wd);
621 return false;
623 #endif
625 void StatCache::removeWatch(int wd) {
626 #ifdef __linux__
627 inotify_rm_watch(m_ifd, wd);
628 #endif
631 void StatCache::removePath(const std::string& path, Node* node) {
632 NameNodeMap::accessor acc;
633 if (m_path2Node.find(acc, path) && acc->second.get() == node) {
634 TRACE(1, "StatCache: remove path '%s'\n", path.c_str());
635 m_path2Node.erase(acc);
639 void StatCache::removeLPath(const std::string& path, Node* node) {
640 NameNodeMap::accessor acc;
641 if (m_lpath2Node.find(acc, path) && acc->second.get() == node) {
642 TRACE(1, "StatCache: remove link path '%s'\n", path.c_str());
643 m_lpath2Node.erase(acc);
647 void StatCache::refresh() {
648 #ifdef __linux__
649 SimpleLock lock(m_lock);
651 if (m_ifd == -1) {
652 return;
655 while (true) {
656 int nread = read(m_ifd, m_readBuf, kReadBufSize);
657 if (nread == -1) {
658 // No pending events.
659 assert(errno == EAGAIN);
660 // Record the last refresh time *after* processing the event queue, in
661 // order to assure that once the event queue has been merged into the
662 // cache state, all cached values have timestamps older than
663 // m_lastRefresh (assuming no timestamps are ever set into the future).
664 m_lastRefresh = time(nullptr);
665 TRACE(1, "StatCache: refresh time %lu\n", (unsigned long)m_lastRefresh);
666 return;
668 for (char* p = m_readBuf; p < m_readBuf + nread;) {
669 struct inotify_event* event = (struct inotify_event*) p;
670 if (handleEvent(event)) {
671 return;
673 p += sizeof(struct inotify_event) + event->len;
676 #endif
679 time_t StatCache::lastRefresh() {
680 SimpleLock lock(m_lock);
682 return m_lastRefresh;
685 int StatCache::statImpl(const std::string& path, struct stat* buf) {
686 // Punt if path is relative.
687 if (path.size() == 0 || path[0] != '/') {
688 return statSyscall(path, buf);
691 NodePtr p;
693 NameNodeMap::const_accessor acc;
694 if (m_path2Node.find(acc, path)) {
695 p = acc->second;
698 if (p) {
699 return p->stat(path, buf);
702 SimpleLock lock(m_lock);
703 if (mergePath(path, true)) {
704 return statSyscall(path, buf);
707 NameNodeMap::const_accessor acc;
708 if (m_path2Node.find(acc, path)) {
709 return acc->second->stat(path, buf, m_lastRefresh);
713 not_reached();
716 int StatCache::lstatImpl(const std::string& path, struct stat* buf) {
717 // Punt if path is relative.
718 if (path.size() == 0 || path[0] != '/') {
719 return statSyscall(path, buf);
722 NodePtr p;
724 NameNodeMap::const_accessor acc;
725 if (m_lpath2Node.find(acc, path)) {
726 p = acc->second;
729 if (p) {
730 return p->lstat(path, buf);
733 SimpleLock lock(m_lock);
734 if (mergePath(path, false)) {
735 return lstatSyscall(path, buf);
738 NameNodeMap::const_accessor acc;
739 if (m_lpath2Node.find(acc, path)) {
740 return acc->second->lstat(path, buf, m_lastRefresh);
744 not_reached();
747 std::string StatCache::readlinkImpl(const std::string& path) {
748 // Punt if path is relative.
749 if (path.size() == 0 || path[0] != '/') {
750 return readlinkSyscall(path);
753 NodePtr p;
755 NameNodeMap::const_accessor acc;
756 if (m_lpath2Node.find(acc, path)) {
757 p = acc->second;
760 if (p) {
761 return p->readlink(path);
764 SimpleLock lock(m_lock);
765 if (mergePath(path, false)) {
766 return readlinkSyscall(path);
769 NameNodeMap::const_accessor acc;
770 if (m_lpath2Node.find(acc, path)) {
771 return acc->second->readlink(path, m_lastRefresh);
775 not_reached();
778 // StatCache::realpath() is based on the realpath(3) implementation that is
779 // part of FreeBSD's libc. The following license applies:
782 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
784 * Redistribution and use in source and binary forms, with or without
785 * modification, are permitted provided that the following conditions
786 * are met:
787 * 1. Redistributions of source code must retain the above copyright
788 * notice, this list of conditions and the following disclaimer.
789 * 2. Redistributions in binary form must reproduce the above copyright
790 * notice, this list of conditions and the following disclaimer in the
791 * documentation and/or other materials provided with the distribution.
792 * 3. The names of the authors may not be used to endorse or promote
793 * products derived from this software without specific prior written
794 * permission.
796 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
797 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
798 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
799 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
800 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
801 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
802 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
803 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
804 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
805 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
806 * SUCH DAMAGE.
808 #if 0
809 __FBSDID("$FreeBSD: src/lib/libc/stdlib/realpath.c,v 1.24 2011/11/04 19:56:34 ed Exp $");
810 #endif
812 // Find the real name of path, by removing all ".", ".." and symlink
813 // components. Returns the resolved path on success, or "" on failure,
814 std::string StatCache::realpathImpl(const char* path) {
815 std::string resolved;
816 assert(path != nullptr);
817 if (path[0] != '/') {
818 return realpathLibc(path);
820 struct stat sb;
821 unsigned symlinks;
822 std::string left, next_token, symlink;
823 size_t left_pos;
825 symlinks = 0;
826 resolved += "/";
827 if (path[1] == '\0') {
828 TRACE(4, "StatCache: realpath('%s') --> '%s'\n", path, resolved.c_str());
829 return resolved;
831 left = path;
832 left_pos = 0;
834 // Iterate over path components in `left'.
835 while (left.size() - left_pos != 0) {
836 // Extract the next path component and adjust `left' and its length.
837 size_t pos = left.find_first_of('/', left_pos);
838 next_token = left.substr(left_pos, pos - left_pos);
839 left_pos += next_token.size();
840 if (pos != std::string::npos) {
841 ++left_pos;
844 if (resolved[resolved.size() - 1] != '/') {
845 resolved += "/";
847 if (next_token.size() == 0) {
848 continue;
849 } else if (next_token.compare(".") == 0) {
850 continue;
851 } else if (next_token.compare("..") == 0) {
852 // Strip the last path component except when we have single "/".
853 if (resolved.size() > 1) {
854 resolved.erase(resolved.size() - 1);
855 resolved.erase(resolved.find_last_of('/') + 1);
857 continue;
860 // Append the next path component and lstat() it. If lstat() fails we still
861 // can return successfully if there are no more path components left.
862 resolved += next_token;
863 if (lstatImpl(resolved, &sb) != 0) {
864 if (errno == ENOENT && pos == std::string::npos) {
865 TRACE(4, "StatCache: realpath('%s') --> '%s'\n",
866 path, resolved.c_str());
867 return resolved;
869 TRACE(4, "StatCache: realpath('%s') --> error\n", path);
870 return "";
872 if (S_ISLNK(sb.st_mode)) {
873 if (symlinks++ > MAXSYMLINKS) {
874 TRACE(4, "StatCache: realpath('%s') --> error\n", path);
875 return "";
877 symlink = readlinkImpl(resolved);
878 if (symlink.size() == 0) {
879 TRACE(4, "StatCache: realpath('%s') --> error\n", path);
880 return "";
882 if (symlink[0] == '/') {
883 resolved.erase(1);
884 } else if (resolved.size() > 1) {
885 // Strip the last path component.
886 resolved.erase(resolved.size() - 1);
887 resolved.erase(resolved.find_last_of('/') + 1);
890 // If there are any path components left, then append them to symlink.
891 // The result is placed in `left'.
892 if (pos != std::string::npos) {
893 if (symlink[symlink.size() - 1] != '/') {
894 symlink += "/";
896 symlink += left.substr(left_pos);
898 left = symlink;
899 left_pos = 0;
903 // Remove trailing slash except when the resolved pathname is a single "/".
904 if (resolved.size() > 1 && resolved[resolved.size() - 1] == '/') {
905 resolved.erase(resolved.size() - 1);
908 TRACE(4, "StatCache: realpath('%s') --> '%s'\n", path, resolved.c_str());
909 return resolved;
912 StatCache StatCache::s_sc;
914 void StatCache::requestInit() {
915 if (!RuntimeOption::ServerStatCache) return;
916 s_sc.refresh();
919 int StatCache::stat(const std::string& path, struct stat* buf) {
920 if (!RuntimeOption::ServerStatCache) return statSyscall(path, buf);
921 return s_sc.statImpl(path, buf);
924 int StatCache::lstat(const std::string& path, struct stat* buf) {
925 if (!RuntimeOption::ServerStatCache) return lstatSyscall(path, buf);
926 return s_sc.lstatImpl(path, buf);
929 std::string StatCache::readlink(const std::string& path) {
930 if (!RuntimeOption::ServerStatCache) return readlinkSyscall(path);
931 return s_sc.readlinkImpl(path);
934 std::string StatCache::realpath(const char* path) {
935 if (!RuntimeOption::ServerStatCache) return realpathLibc(path);
936 return s_sc.realpathImpl(path);
939 ///////////////////////////////////////////////////////////////////////////////