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