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>
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"
34 ///////////////////////////////////////////////////////////////////////////////
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
<< ", ";
50 os
<< "blksize=" << buf
->st_blksize
<< ", ";
51 os
<< "blocks=" << buf
->st_blocks
<< ", ";
53 os
<< "atime=" << buf
->st_atime
<< ", ";
54 os
<< "mtime=" << buf
->st_mtime
<< ", ";
55 os
<< "ctime=" << buf
->st_ctime
;
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
70 && stA
->st_blksize
== stB
->st_blksize
71 && stA
->st_blocks
== stB
->st_blocks
73 /* Intentionally omitted:
74 && stA->st_atime == stB->st_atime
76 && stA
->st_mtime
== stB
->st_mtime
77 && stA
->st_ctime
== stB
->st_ctime
);
81 UNUSED
static std::string
eventToString(const struct inotify_event
* ie
) {
83 std::ostringstream os
;
84 os
<< "struct inotify_event {wd=" << ie
->wd
<< ", mask=(";
85 #define EVENT(e) do { \
98 EVENT(IN_CLOSE_WRITE
);
99 EVENT(IN_CLOSE_NOWRITE
);
101 EVENT(IN_MOVED_FROM
);
105 EVENT(IN_DELETE_SELF
);
108 EVENT(IN_Q_OVERFLOW
);
113 if (ie
->cookie
!= 0) os
<< ", cookie=" << ie
->cookie
;
114 if (ie
->len
!= 0) os
<< ", name='" << ie
->name
<< "'";
120 static int statSyscall(const std::string
& path
, struct stat
* buf
) {
121 int ret
= ::stat(path
.c_str(), buf
);
123 TRACE(5, "StatCache: stat '%s' %s\n",
124 path
.c_str(), statToString(buf
).c_str());
126 TRACE(5, "StatCache: stat '%s' --> error\n", path
.c_str());
131 static int lstatSyscall(const std::string
& path
, struct stat
* buf
) {
132 int ret
= ::lstat(path
.c_str(), buf
);
134 TRACE(5, "StatCache: lstat '%s' %s\n",
135 path
.c_str(), statToString(buf
).c_str());
137 TRACE(5, "StatCache: lstat '%s' --> error\n", path
.c_str());
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);
146 TRACE(5, "StatCache: readlink('%s') --> error\n", path
.c_str());
150 TRACE(5, "StatCache: readlink('%s') --> '%s'\n", path
.c_str(), lbuf
);
154 static std::string
realpathLibc(const char* path
) {
158 if (!::realpath(path
, buf
)) {
159 TRACE(5, "StatCache: realpath('%s') --> error\n", path
);
162 TRACE(5, "StatCache: realpath('%s') --> '%s'\n", path
, buf
);
167 //==============================================================================
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() {
177 m_statCache
.removeWatch(m_wd
);
181 TRACE(1, "StatCache: delete node '%s'\n", m_path
.c_str());
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();
195 if (invalidate
&& m_valid
) {
196 TRACE(1, "StatCache: invalidate path '%s'\n", it
->first
.c_str());
197 HPHP::invalidatePath(it
->first
);
200 m_statCache
.removePath(it
->first
, this);
203 for (NameMap::const_iterator it
= m_lpaths
.begin(); it
!= m_lpaths
.end();
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
);
214 m_statCache
.removeLPath(it
->first
, this);
226 void StatCache::Node::touch(bool invalidate
/* = true */) {
227 SimpleLock
lock(m_lock
);
228 touchLocked
<false>(invalidate
);
231 void StatCache::Node::detachLocked() {
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.
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.
254 m_inExpirePaths
= true;
257 for (NameNodeMap::const_iterator it
= children
.begin(); it
!= children
.end();
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
) {
277 if (statSyscall(path
, &m_stat
) == -1) {
278 TRACE(4, "StatCache: stat '%s' --> error (node=%p)\n",
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",
289 TRACE(4, "StatCache: lstat '%s' %s (node=%p)\n",
290 path
.c_str(), statToString(&m_lstat
).c_str(), this);
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);
302 void StatCache::Node::sanityCheck(const std::string
& path
, bool isStat
,
303 const struct stat
* buf
, time_t lastRefresh
) {
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
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",
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 */) {
332 SimpleLock
lock(m_lock
);
333 if (validate(path
, cached
)) {
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
);
345 int StatCache::Node::lstat(const std::string
& path
, struct stat
* buf
,
346 time_t lastRefresh
/* = 0 */) {
349 SimpleLock
lock(m_lock
);
350 if (validate(path
, cached
)) {
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
);
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 */) {
378 SimpleLock
lock(m_lock
);
379 if (validate(path
, cached
) || !isLinkLocked()) {
382 if (debug
&& cached
) {
383 memcpy(&buf
, &m_lstat
, sizeof(struct stat
));
385 if (m_link
.size() == 0) {
386 m_link
= readlinkSyscall(path
);
390 if (debug
&& cached
) {
391 sanityCheck(path
, false, &buf
, lastRefresh
);
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
,
415 return folly::get_default(follow
? m_children
: m_lChildren
, childName
);
418 //==============================================================================
421 StatCache::StatCache()
422 : m_lock(false /*reentrant*/, RankStatCache
), m_ifd(-1),
423 m_lastRefresh(time(nullptr)) {
426 StatCache::~StatCache() {
430 bool StatCache::init() {
431 // inotify_init1() directly supports the fcntl() settings, but it's only
432 // available starting in Linux 2.6.27.
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) {
447 void StatCache::clear() {
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
460 m_root
->expirePaths();
463 assert(m_path2Node
.size() == 0);
464 assert(m_lpath2Node
.size() == 0);
467 void StatCache::reset() {
472 StatCache::NodePtr
StatCache::getNode(const std::string
& path
, bool follow
) {
474 int wd
= inotify_add_watch(m_ifd
, path
.c_str(),
482 | (follow
? 0 : IN_DONT_FOLLOW
)
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);
491 node
= folly::get_default(m_watch2Node
, wd
);
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
);
500 TRACE(3, "StatCache: getNode('%s', follow=%s) --> alias %p (wd=%d)\n",
501 path
.c_str(), follow
? "true" : "false", node
.get(), wd
);
504 node
= new Node(*this);
505 TRACE(3, "StatCache: getNode('%s', follow=%s) --> %p\n",
506 path
.c_str(), follow
? "true" : "false", node
.get());
511 return NodePtr(nullptr);
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()) {
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
530 bool curFollow
= (follow
|| i
+ 1 < pvec
.size());
532 NodePtr child
= curNode
->getChild(pvec
[i
], curFollow
);
533 if (child
.get() == nullptr) {
534 child
= getNode(curPath
, curFollow
);
535 if (child
.get() == nullptr) {
538 curNode
->insertChild(pvec
[i
], child
, curFollow
);
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");
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");
561 assert(event
->wd
!= -1);
562 NodePtr node
= folly::get_default(m_watch2Node
, event
->wd
);
564 TRACE(1, "StatCache: inotify event (obsolete) %s\n",
565 eventToString(event
).c_str());
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
);
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
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();
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());
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
619 m_watch2Node
.erase(event
->wd
);
625 void StatCache::removeWatch(int wd
) {
627 inotify_rm_watch(m_ifd
, wd
);
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() {
649 SimpleLock
lock(m_lock
);
656 int nread
= read(m_ifd
, m_readBuf
, kReadBufSize
);
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
);
668 for (char* p
= m_readBuf
; p
< m_readBuf
+ nread
;) {
669 struct inotify_event
* event
= (struct inotify_event
*) p
;
670 if (handleEvent(event
)) {
673 p
+= sizeof(struct inotify_event
) + event
->len
;
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
);
693 NameNodeMap::const_accessor acc
;
694 if (m_path2Node
.find(acc
, path
)) {
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
);
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
);
724 NameNodeMap::const_accessor acc
;
725 if (m_lpath2Node
.find(acc
, path
)) {
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
);
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
);
755 NameNodeMap::const_accessor acc
;
756 if (m_lpath2Node
.find(acc
, path
)) {
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
);
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
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
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
809 __FBSDID("$FreeBSD: src/lib/libc/stdlib/realpath.c,v 1.24 2011/11/04 19:56:34 ed Exp $");
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
);
822 std::string left
, next_token
, symlink
;
827 if (path
[1] == '\0') {
828 TRACE(4, "StatCache: realpath('%s') --> '%s'\n", path
, resolved
.c_str());
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
) {
844 if (resolved
[resolved
.size() - 1] != '/') {
847 if (next_token
.size() == 0) {
849 } else if (next_token
.compare(".") == 0) {
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);
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());
869 TRACE(4, "StatCache: realpath('%s') --> error\n", path
);
872 if (S_ISLNK(sb
.st_mode
)) {
873 if (symlinks
++ > MAXSYMLINKS
) {
874 TRACE(4, "StatCache: realpath('%s') --> error\n", path
);
877 symlink
= readlinkImpl(resolved
);
878 if (symlink
.size() == 0) {
879 TRACE(4, "StatCache: realpath('%s') --> error\n", path
);
882 if (symlink
[0] == '/') {
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] != '/') {
896 symlink
+= left
.substr(left_pos
);
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());
912 StatCache
StatCache::s_sc
;
914 void StatCache::requestInit() {
915 if (!RuntimeOption::ServerStatCache
) return;
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 ///////////////////////////////////////////////////////////////////////////////