Skip to content

Commit

Permalink
add .eden "magic" dir
Browse files Browse the repository at this point in the history
Summary:
It's not really magic because we don't have a virtual directory
inode base any more.  Instead, we mkdir and populate it at mount time.

What is slightly magical about it is that we give it some special powers:

* We know the inode number of the eden dir and prevent unlink operations
  on it or inside it.
* The .eden dir is present in the contents of the root inode and will
  show up when that directory is `readdir`'d
* When resolving a child of a TreeInode by name, we know to return the
  magic `.eden` inode number.  This means that it is possible to `stat`
  and consume the `.eden` directory from any directory inside the eden
  mount, even though it won't show up in `readdir` for those child dirs.

The contents of the `.eden` dir are:

* `socket` - a symlink back to the unix domain socket that our thrift
  server is listening on.  This means that it is a simple
  `readlink(".eden/socket")` operation to discover both whether a directory
  is part of an eden mount and how to talk to the server.

* `root` - a symlink back to the root of this eden mount.  This allows
  using `readlink(".eden/root")` as a simple 1-step operation to find
  the root of an eden mount, and avoids needing to walk up directory
  by directory as is the common pattern for locating `.hg` or `.git`
  dirs.

Reviewed By: simpkins

Differential Revision: D4637285

fbshipit-source-id: 0eabf98b29144acccef5c83bd367493399dc55bb
  • Loading branch information
wez authored and facebook-github-bot committed Mar 25, 2017
1 parent 658a284 commit 616d9fb
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 13 deletions.
9 changes: 6 additions & 3 deletions tests/integration/test_eden_pathgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def populate(repo):

res = self.watchmanCommand('watch', root)
self.assertEqual('eden', res['watcher'])
self.assertFileList(root, ['adir', 'adir/file', 'bdir',
self.assertFileList(root, ['.eden', '.eden/root', '.eden/socket',
'adir', 'adir/file', 'bdir',
'bdir/noexec.sh', 'bdir/test.sh', 'b*ir',
'b*ir/star', 'b\\*ir', 'b\\*ir/foo',
'hello', 'slink'])
Expand All @@ -43,7 +44,8 @@ def populate(repo):
res = self.watchmanCommand('query', root, {
'expression': ['type', 'l'],
'fields': ['name']})
self.assertFileListsEqual(res['files'], ['slink'])
self.assertFileListsEqual(res['files'],
['.eden/root', '.eden/socket', 'slink'])

res = self.watchmanCommand('query', root, {
'expression': ['type', 'f'],
Expand Down Expand Up @@ -77,7 +79,8 @@ def populate(repo):
'path': [''],
'fields': ['name']})
self.assertFileListsEqual(res['files'],
['adir', 'adir/file', 'b*ir', 'b*ir/star',
['.eden', '.eden/root', '.eden/socket',
'adir', 'adir/file', 'b*ir', 'b*ir/star',
'bdir', 'bdir/noexec.sh', 'bdir/test.sh',
'b\\*ir', 'b\\*ir/foo', 'hello',
'slink'])
Expand Down
61 changes: 51 additions & 10 deletions watcher/eden.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,46 @@ folly::SocketAddress getEdenServerSocketAddress() {
return addr;
}

std::string readLink(const std::string& path) {
struct stat st;
if (lstat(path.c_str(), &st)) {
throw std::system_error(
errno,
std::generic_category(),
watchman::to<std::string>("lstat(", path, ") failed"));
}
std::string result(st.st_size + 1, '\0');
auto len = ::readlink(path.c_str(), &result[0], result.size());
if (len >= 0) {
result.resize(len);
return result;
}

throw std::system_error(
errno,
std::generic_category(),
watchman::to<std::string>("readlink(", path, ") failed"));
}

/** Create a thrift client that will connect to the eden server associated
* with the current user. */
std::unique_ptr<StreamingEdenServiceAsyncClient> getEdenClient(
w_string_piece rootPath,
folly::EventBase* eb = folly::EventBaseManager::get()->getEventBase()) {
// Resolve the eden socket; we use the .eden dir that is present in
// every dir of an eden mount.
folly::SocketAddress addr;
auto path = watchman::to<std::string>(rootPath, "/.eden/socket");

// It is important to resolve the link because the path in the eden mount
// may exceed the maximum permitted unix domain socket path length.
// This is actually how things our in our integration test environment.
auto socketPath = readLink(path);
addr.setFromPath(socketPath);

return std::make_unique<StreamingEdenServiceAsyncClient>(
apache::thrift::HeaderClientChannel::newChannel(
apache::thrift::async::TAsyncSocket::newSocket(
eb, getEdenServerSocketAddress())));
apache::thrift::async::TAsyncSocket::newSocket(eb, addr)));
}

class EdenFileResult : public FileResult {
Expand Down Expand Up @@ -173,7 +205,7 @@ class EdenView : public QueryableView {
}

void timeGenerator(w_query* query, struct w_query_ctx* ctx) const override {
auto client = getEdenClient();
auto client = getEdenClient(root_path_);
auto mountPoint = to<std::string>(ctx->root->root_path);

FileDelta delta;
Expand Down Expand Up @@ -304,7 +336,7 @@ class EdenView : public QueryableView {
const std::vector<std::string>& globStrings,
w_query* query,
struct w_query_ctx* ctx) const {
auto client = getEdenClient();
auto client = getEdenClient(ctx->root->root_path);
auto mountPoint = to<std::string>(ctx->root->root_path);

std::vector<std::string> fileNames;
Expand Down Expand Up @@ -423,7 +455,7 @@ class EdenView : public QueryableView {
}

ClockPosition getMostRecentRootNumberAndTickValue() const override {
auto client = getEdenClient();
auto client = getEdenClient(root_path_);
JournalPosition position;
auto mountPoint = to<std::string>(root_path_);
client->sync_getCurrentJournalPosition(position, mountPoint);
Expand Down Expand Up @@ -488,7 +520,7 @@ class EdenView : public QueryableView {
std::chrono::milliseconds settleTimeout(root->trigger_settle);

// Connect up the client
auto client = getEdenClient(&subscriberEventBase_);
auto client = getEdenClient(root->root_path, &subscriberEventBase_);

// This is called each time we get pushed an update by the eden server
auto onUpdate = [&](apache::thrift::ClientReceiveState&& state) mutable {
Expand Down Expand Up @@ -564,11 +596,20 @@ std::shared_ptr<watchman::QueryableView> detectEden(w_root_t* root) {
folly::call_once(
reg_, [] { folly::SingletonVault::singleton()->registrationComplete(); });

// This is mildly ghetto, but the way we figure out if the intended path
// is on an eden mount is to ask eden to stat the root of that mount;
// if it throws then it is not an eden mount.
auto client = getEdenClient();
auto edenRoot =
readLink(watchman::to<std::string>(root->root_path, "/.eden/root"));
if (w_string_piece(edenRoot) != root->root_path) {
// We aren't at the root of the eden mount
return nullptr;
}

auto client = getEdenClient(root->root_path);

// We don't strictly need to do this, since we just verified that the root
// matches our expectations, but it can't hurt to attempt to talk to the
// daemon directly, just in case it is broken for some reason, or in
// case someone is trolling us with a directory structure that looks
// like an eden mount.
std::vector<FileInformationOrError> info;
static const std::vector<std::string> paths{""};
client->sync_getFileInformation(
Expand Down

0 comments on commit 616d9fb

Please sign in to comment.