1 /* Copyright 2012-present Facebook, Inc.
2 * Licensed under the Apache License, Version 2.0 */
3
4 #include "watchman_system.h"
5 #include <exception>
6 #include "watchman.h"
7
8 namespace watchman {
9
Cookie(w_string name)10 CookieSync::Cookie::Cookie(w_string name) : fileName(name) {}
11
~Cookie()12 CookieSync::Cookie::~Cookie() {
13 // The file may not exist at this point; we're just taking this
14 // opportunity to remove it if nothing else has done so already.
15 // We don't care about the return code; best effort is fine.
16 unlink(fileName.c_str());
17 }
18
CookieSync(const w_string & dir)19 CookieSync::CookieSync(const w_string& dir) {
20 setCookieDir(dir);
21 }
22
~CookieSync()23 CookieSync::~CookieSync() {
24 // Wake up anyone that might have been waiting on us
25 abortAllCookies();
26 }
27
setCookieDir(const w_string & dir)28 void CookieSync::setCookieDir(const w_string& dir) {
29 cookieDir_ = dir;
30
31 char hostname[256];
32 gethostname(hostname, sizeof(hostname));
33 hostname[sizeof(hostname) - 1] = '\0';
34
35 cookiePrefix_ = w_string::printf(
36 "%.*s/" WATCHMAN_COOKIE_PREFIX "%s-%d-",
37 int(cookieDir_.size()),
38 cookieDir_.data(),
39 hostname,
40 int(getpid()));
41 }
42
sync()43 Future<Unit> CookieSync::sync() {
44 /* generate a cookie name: cookie prefix + id */
45 auto path_str = w_string::printf(
46 "%.*s%" PRIu32,
47 int(cookiePrefix_.size()),
48 cookiePrefix_.data(),
49 serial_++);
50
51 auto cookie = make_unique<Cookie>(path_str);
52 auto future = cookie->promise.getFuture();
53
54 /* insert our cookie in the map */
55 {
56 auto wlock = cookies_.wlock();
57 auto& map = *wlock;
58 map[path_str] = std::move(cookie);
59 }
60
61 /* then touch the file */
62 auto file = w_stm_open(
63 path_str.c_str(), O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0700);
64 if (!file) {
65 auto errcode = errno;
66 // The erase will unlink the file
67 cookies_.wlock()->erase(path_str);
68
69 throw std::system_error(
70 errcode,
71 std::generic_category(),
72 to<std::string>(
73 "sync: creat(", path_str, ") failed: ", strerror(errcode)));
74 }
75 log(DBG, "sync created cookie file ", path_str, "\n");
76 return future;
77 }
78
syncToNow(std::chrono::milliseconds timeout)79 bool CookieSync::syncToNow(std::chrono::milliseconds timeout) {
80 /* compute deadline */
81 using namespace std::chrono;
82 auto deadline = system_clock::now() + timeout;
83
84 while (true) {
85 auto cookie = sync();
86
87 if (!cookie.wait_for(timeout)) {
88 log(ERR,
89 "syncToNow: timed out waiting for cookie file to be "
90 "observed by watcher within ",
91 timeout.count(),
92 " milliseconds\n");
93 errno = ETIMEDOUT;
94 return false;
95 }
96
97 if (cookie.result().hasError()) {
98 // Sync was aborted by a recrawl; recompute the timeout
99 // and wait again if we still have time
100 timeout = duration_cast<milliseconds>(deadline - system_clock::now());
101 if (timeout.count() <= 0) {
102 errno = ETIMEDOUT;
103 return false;
104 }
105
106 // wait again
107 continue;
108 }
109
110 // Success!
111 return true;
112 }
113 }
114
abortAllCookies()115 void CookieSync::abortAllCookies() {
116 std::unordered_map<w_string, std::unique_ptr<Cookie>> cookies;
117
118 {
119 auto map = cookies_.wlock();
120 std::swap(*map, cookies);
121 }
122
123 for (auto& it : cookies) {
124 log(ERR, "syncToNow: aborting cookie ", it.first, "\n");
125 it.second->promise.setException(
126 std::make_exception_ptr(CookieSyncAborted()));
127 }
128 }
129
notifyCookie(const w_string & path)130 void CookieSync::notifyCookie(const w_string& path) {
131 std::unique_ptr<Cookie> cookie;
132
133 {
134 auto map = cookies_.wlock();
135 auto cookie_iter = map->find(path);
136 log(DBG,
137 "cookie for ",
138 path,
139 "? ",
140 cookie_iter != map->end() ? "yes" : "no",
141 "\n");
142
143 if (cookie_iter != map->end()) {
144 cookie = std::move(cookie_iter->second);
145 map->erase(cookie_iter);
146 }
147 }
148
149 if (cookie) {
150 cookie->promise.setValue(Unit{});
151 // cookie file will be unlinked when we exit this scope
152 }
153 }
154 }
155