2 +----------------------------------------------------------------------+
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>
21 #include <sys/param.h>
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"
35 ///////////////////////////////////////////////////////////////////////////////
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
<< ", ";
51 os
<< "blksize=" << buf
->st_blksize
<< ", ";
52 os
<< "blocks=" << buf
->st_blocks
<< ", ";
54 os
<< "atime=" << buf
->st_atime
<< ", ";
55 os
<< "mtime=" << buf
->st_mtime
<< ", ";
56 os
<< "ctime=" << buf
->st_ctime
;
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
71 && stA
->st_blksize
== stB
->st_blksize
72 && stA
->st_blocks
== stB
->st_blocks
74 /* Intentionally omitted:
75 && stA->st_atime == stB->st_atime
77 && stA
->st_mtime
== stB
->st_mtime
78 && stA
->st_ctime
== stB
->st_ctime
);
82 UNUSED
static std::string
eventToString(const struct inotify_event
* ie
) {
84 std::ostringstream os
;
85 os
<< "struct inotify_event {wd=" << ie
->wd
<< ", mask=(";
86 #define EVENT(e) do { \
99 EVENT(IN_CLOSE_WRITE
);
100 EVENT(IN_CLOSE_NOWRITE
);
102 EVENT(IN_MOVED_FROM
);
106 EVENT(IN_DELETE_SELF
);
109 EVENT(IN_Q_OVERFLOW
);
114 if (ie
->cookie
!= 0) os
<< ", cookie=" << ie
->cookie
;
115 if (ie
->len
!= 0) os
<< ", name='" << ie
->name
<< "'";
121 static int statSyscall(const std::string
& path
, struct stat
* buf
) {
122 int ret
= ::stat(path
.c_str(), buf
);
124 TRACE(5, "StatCache: stat '%s' %s\n",
125 path
.c_str(), statToString(buf
).c_str());
127 TRACE(5, "StatCache: stat '%s' --> error\n", path
.c_str());
132 static int lstatSyscall(const std::string
& path
, struct stat
* buf
) {
133 int ret
= ::lstat(path
.c_str(), buf
);
135 TRACE(5, "StatCache: lstat '%s' %s\n",
136 path
.c_str(), statToString(buf
).c_str());
138 TRACE(5, "StatCache: lstat '%s' --> error\n", path
.c_str());
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);
147 TRACE(5, "StatCache: readlink('%s') --> error\n", path
.c_str());
151 TRACE(5, "StatCache: readlink('%s') --> '%s'\n", path
.c_str(), lbuf
);
155 static std::string
realpathLibc(const char* path
) {
159 if (!::realpath(path
, buf
)) {
160 TRACE(5, "StatCache: realpath('%s') --> error\n", path
);
163 TRACE(5, "StatCache: realpath('%s') --> '%s'\n", path
, buf
);
168 //==============================================================================
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() {
178 m_statCache
.removeWatch(m_wd
);
182 TRACE(1, "StatCache: delete node '%s'\n", m_path
.c_str());
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();
196 if (invalidate
&& m_valid
) {
197 TRACE(1, "StatCache: invalidate path '%s'\n", it
->first
.c_str());
198 HPHP::invalidatePath(it
->first
);
201 m_statCache
.removePath(it
->first
, this);
204 for (NameMap::const_iterator it
= m_lpaths
.begin(); it
!= m_lpaths
.end();
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
);
215 m_statCache
.removeLPath(it
->first
, this);
227 void StatCache::Node::touch(bool invalidate
/* = true */) {
228 SimpleLock
lock(m_lock
);
229 touchLocked
<false>(invalidate
);
232 void StatCache::Node::detachLocked() {
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.
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.
255 m_inExpirePaths
= true;
258 for (NameNodeMap::const_iterator it
= children
.begin(); it
!= children
.end();
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
) {
278 if (statSyscall(path
, &m_stat
) == -1) {
279 TRACE(4, "StatCache: stat '%s' --> error (node=%p)\n",
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",
290 TRACE(4, "StatCache: lstat '%s' %s (node=%p)\n",
291 path
.c_str(), statToString(&m_lstat
).c_str(), this);
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);
303 void StatCache::Node::sanityCheck(const std::string
& path
, bool isStat
,
304 const struct stat
* buf
, time_t lastRefresh
) {
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
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",
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 */) {
333 SimpleLock
lock(m_lock
);
334 if (validate(path
, cached
)) {
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
);
346 int StatCache::Node::lstat(const std::string
& path
, struct stat
* buf
,
347 time_t lastRefresh
/* = 0 */) {
350 SimpleLock
lock(m_lock
);
351 if (validate(path
, cached
)) {
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
);
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 */) {
379 SimpleLock
lock(m_lock
);
380 if (validate(path
, cached
) || !isLinkLocked()) {
383 if (debug
&& cached
) {
384 memcpy(&buf
, &m_lstat
, sizeof(struct stat
));
386 if (m_link
.size() == 0) {
387 m_link
= readlinkSyscall(path
);
391 if (debug
&& cached
) {
392 sanityCheck(path
, false, &buf
, lastRefresh
);
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
,
416 return folly::get_default(follow
? m_children
: m_lChildren
, childName
);
419 //==============================================================================
422 StatCache::StatCache()
423 : m_lock(false /*reentrant*/, RankStatCache
), m_ifd(-1),
424 m_lastRefresh(time(nullptr)) {
427 StatCache::~StatCache() {
431 bool StatCache::init() {
432 // inotify_init1() directly supports the fcntl() settings, but it's only
433 // available starting in Linux 2.6.27.
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) {
448 void StatCache::clear() {
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
461 m_root
->expirePaths();
464 assertx(m_path2Node
.size() == 0);
465 assertx(m_lpath2Node
.size() == 0);
468 void StatCache::reset() {
473 StatCache::NodePtr
StatCache::getNode(const std::string
& path
, bool follow
) {
475 int wd
= inotify_add_watch(m_ifd
, path
.c_str(),
483 | (follow
? 0 : IN_DONT_FOLLOW
)
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);
492 node
= folly::get_default(m_watch2Node
, wd
);
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
);
501 TRACE(3, "StatCache: getNode('%s', follow=%s) --> alias %p (wd=%d)\n",
502 path
.c_str(), follow
? "true" : "false", node
.get(), wd
);
505 node
= new Node(*this);
506 TRACE(3, "StatCache: getNode('%s', follow=%s) --> %p\n",
507 path
.c_str(), follow
? "true" : "false", node
.get());
512 return NodePtr(nullptr);
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()) {
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
531 bool curFollow
= (follow
|| i
+ 1 < pvec
.size());
533 NodePtr child
= curNode
->getChild(pvec
[i
], curFollow
);
534 if (child
.get() == nullptr) {
535 child
= getNode(curPath
, curFollow
);
536 if (child
.get() == nullptr) {
539 curNode
->insertChild(pvec
[i
], child
, curFollow
);
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");
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");
562 assertx(event
->wd
!= -1);
563 NodePtr node
= folly::get_default(m_watch2Node
, event
->wd
);
565 TRACE(1, "StatCache: inotify event (obsolete) %s\n",
566 eventToString(event
).c_str());
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
);
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
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();
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());
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
620 m_watch2Node
.erase(event
->wd
);
626 void StatCache::removeWatch(int wd
) {
628 inotify_rm_watch(m_ifd
, wd
);
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() {
650 SimpleLock
lock(m_lock
);
657 int nread
= read(m_ifd
, m_readBuf
, kReadBufSize
);
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
);
669 for (char* p
= m_readBuf
; p
< m_readBuf
+ nread
;) {
670 struct inotify_event
* event
= (struct inotify_event
*) p
;
671 if (handleEvent(event
)) {
674 p
+= sizeof(struct inotify_event
) + event
->len
;
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
);
694 NameNodeMap::const_accessor acc
;
695 if (m_path2Node
.find(acc
, path
)) {
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
);
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
);
725 NameNodeMap::const_accessor acc
;
726 if (m_lpath2Node
.find(acc
, path
)) {
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
);
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
);
756 NameNodeMap::const_accessor acc
;
757 if (m_lpath2Node
.find(acc
, path
)) {
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
);
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
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
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
810 __FBSDID("$FreeBSD: src/lib/libc/stdlib/realpath.c,v 1.24 2011/11/04 19:56:34 ed Exp $");
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
);
823 std::string left
, next_token
, symlink
;
828 if (path
[1] == '\0') {
829 TRACE(4, "StatCache: realpath('%s') --> '%s'\n", path
, resolved
.c_str());
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
) {
845 if (resolved
[resolved
.size() - 1] != '/') {
848 if (next_token
.size() == 0) {
850 } else if (next_token
== ".") {
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);
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());
870 TRACE(4, "StatCache: realpath('%s') --> error\n", path
);
873 if (S_ISLNK(sb
.st_mode
)) {
874 if (symlinks
++ > MAXSYMLINKS
) {
875 TRACE(4, "StatCache: realpath('%s') --> error\n", path
);
878 symlink
= readlinkImpl(resolved
);
879 if (symlink
.size() == 0) {
880 TRACE(4, "StatCache: realpath('%s') --> error\n", path
);
883 if (symlink
[0] == '/') {
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] != '/') {
897 symlink
+= left
.substr(left_pos
);
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());
913 StatCache
StatCache::s_sc
;
915 void StatCache::requestInit() {
916 if (!RuntimeOption::ServerStatCache
) return;
917 TRACE(5, "StatCache: requestInit 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
);
948 ///////////////////////////////////////////////////////////////////////////////