1 /* Copyright 2012-present Facebook, Inc.
2  * Licensed under the Apache License, Version 2.0 */
3 
4 #include "watchman.h"
5 #include "make_unique.h"
try_client_mode_command(const json_ref & cmd,bool pretty)6 
7 static int proc_pid;
8 static uint64_t proc_start_time;
9 
10 void ClockSpec::init() {
11   struct timeval tv;
12 
13   proc_pid = (int)getpid();
14   if (gettimeofday(&tv, NULL) == -1) {
15     w_log(W_LOG_FATAL, "gettimeofday failed: %s\n", strerror(errno));
16   }
17   proc_start_time = (uint64_t)tv.tv_sec;
18 }
19 
20 ClockSpec::ClockSpec(const json_ref& value) {
21   auto parseClockString = [=](const char* str) {
22     uint64_t start_time;
23     int pid;
24     uint32_t root_number;
25     uint32_t ticks;
26     // Parse a >= 2.8.2 version clock string
27     if (sscanf(
28             str,
29             "c:%" PRIu64 ":%d:%" PRIu32 ":%" PRIu32,
30             &start_time,
31             &pid,
32             &root_number,
33             &ticks) == 4) {
34       tag = w_cs_clock;
35       clock.start_time = start_time;
36       clock.pid = pid;
37       clock.position.rootNumber = root_number;
38       clock.position.ticks = ticks;
39       return true;
40     }
41 
42     if (sscanf(str, "c:%d:%" PRIu32, &pid, &ticks) == 2) {
43       // old-style clock value (<= 2.8.2) -- by setting clock time and root
44       // number to 0 we guarantee that this is treated as a fresh instance
45       tag = w_cs_clock;
46       clock.start_time = 0;
47       clock.pid = pid;
48       clock.position.rootNumber = root_number;
49       clock.position.ticks = ticks;
50       return true;
51     }
52 
53     return false;
54   };
55 
56   switch (json_typeof(value)) {
57     case JSON_INTEGER:
58       tag = w_cs_timestamp;
59       timestamp = (time_t)json_integer_value(value);
60       return;
61 
62     case JSON_OBJECT: {
63       auto clockStr = value.get_default("clock");
64       if (clockStr) {
65         if (!parseClockString(json_string_value(clockStr))) {
66           throw std::domain_error("invalid clockspec");
67         }
68       } else {
69         tag = w_cs_clock;
70         clock.start_time = 0;
71         clock.pid = 0;
72         clock.position.rootNumber = 0;
73         clock.position.ticks = 0;
74       }
75 
76       auto scm = value.get_default("scm");
77       if (scm) {
78         scmMergeBase = json_to_w_string(
79             scm.get_default("mergebase", w_string_to_json("")));
80         scmMergeBaseWith = json_to_w_string(scm.get("mergebase-with"));
81       }
82 
83       return;
84     }
85 
86     case JSON_STRING: {
87       auto str = json_string_value(value);
88 
89       if (str[0] == 'n' && str[1] == ':') {
90         tag = w_cs_named_cursor;
91         named_cursor.cursor = json_to_w_string(value);
92         return;
93       }
94 
95       if (parseClockString(str)) {
96         return;
97       }
98 
99       /* fall through to default case and throw error.
100        * The redundant looking comment below is a hint to
101        * gcc that it is ok to fall through. */
102     }
103     /* fall through */
104 
105     default:
106       throw std::domain_error("invalid clockspec");
107   }
108 }
109 
110 std::unique_ptr<ClockSpec> ClockSpec::parseOptionalClockSpec(
111     const json_ref& value) {
112   if (json_is_null(value)) {
113     return nullptr;
114   }
115   return watchman::make_unique<ClockSpec>(value);
116 }
117 
118 ClockSpec::ClockSpec() : tag(w_cs_timestamp), timestamp(0) {}
119 
120 ClockSpec::ClockSpec(const ClockPosition& position)
121     : tag(w_cs_clock), clock{proc_start_time, proc_pid, position} {}
122 
123 w_query_since ClockSpec::evaluate(
124     const ClockPosition& position,
125     const uint32_t lastAgeOutTick,
126     watchman::Synchronized<std::unordered_map<w_string, uint32_t>>* cursorMap)
127     const {
128   w_query_since since;
129 
130   switch (tag) {
131     case w_cs_timestamp:
132       // just copy the values over
133       since.is_timestamp = true;
134       since.timestamp = timestamp;
135       return since;
136 
137     case w_cs_named_cursor: {
138       if (!cursorMap) {
139         // This is checked for and handled at parse time in SinceExpr::parse,
140         // so this should be impossible to hit.
141         throw std::runtime_error(
142             "illegal to use a named cursor in this context");
143       }
144 
145       {
146         auto wlock = cursorMap->wlock();
147         auto& cursors = *wlock;
148         auto it = cursors.find(named_cursor.cursor);
149 
150         if (it == cursors.end()) {
151           since.clock.is_fresh_instance = true;
152           since.clock.ticks = 0;
153         } else {
154           since.clock.ticks = it->second;
155           since.clock.is_fresh_instance = since.clock.ticks < lastAgeOutTick;
156         }
157 
158         // record the current tick value against the cursor so that we use that
159         // as the basis for a subsequent query.
160         cursors[named_cursor.cursor] = position.ticks;
161       }
162 
163       watchman::log(
164           watchman::DBG,
165           "resolved cursor ",
166           named_cursor.cursor,
167           " -> ",
168           since.clock.ticks,
169           "\n");
170 
171       return since;
172     }
173 
174     case w_cs_clock: {
175       if (clock.start_time == proc_start_time && clock.pid == proc_pid &&
176           clock.position.rootNumber == position.rootNumber) {
177         since.clock.is_fresh_instance = clock.position.ticks < lastAgeOutTick;
178         if (since.clock.is_fresh_instance) {
179           since.clock.ticks = 0;
180         } else {
181           since.clock.ticks = clock.position.ticks;
182         }
183       } else {
184         // If the pid, start time or root number don't match, they asked a
185         // different incarnation of the server or a different instance of this
186         // root, so we treat them as having never spoken to us before.
187         since.clock.is_fresh_instance = true;
188         since.clock.ticks = 0;
189       }
190       return since;
191     }
192 
193     default:
194       throw std::runtime_error("impossible case in ClockSpec::evaluate");
195   }
196 }
197 
198 bool clock_id_string(uint32_t root_number, uint32_t ticks, char *buf,
199                      size_t bufsize) {
200   int res = snprintf(buf, bufsize, "c:%" PRIu64 ":%d:%u:%" PRIu32,
201                      proc_start_time, proc_pid, root_number, ticks);
202 
203   if (res == -1) {
204     return false;
205   }
206   return (size_t)res < bufsize;
207 }
208 
209 w_string ClockPosition::toClockString() const {
210   char clockbuf[128];
211   if (!clock_id_string(rootNumber, ticks, clockbuf, sizeof(clockbuf))) {
212     throw std::runtime_error("clock is too big for clockbuf");
213   }
214   return w_string(clockbuf, W_STRING_UNICODE);
215 }
216 
217 /* Add the current clock value to the response */
218 void annotate_with_clock(
219     const std::shared_ptr<w_root_t>& root,
220     json_ref& resp) {
221   resp.set("clock", w_string_to_json(root->view()->getCurrentClockString()));
222 }
223 
224 json_ref ClockSpec::toJson() const {
225   if (hasScmParams()) {
226     return json_object(
227         {{"clock", w_string_to_json(position().toClockString())},
228          {"scm",
229           json_object(
230               {{"mergebase", w_string_to_json(scmMergeBase)},
231                {"mergebase-with", w_string_to_json(scmMergeBaseWith)}})}});
232   }
233   return w_string_to_json(position().toClockString());
234 }
235 
236 bool ClockSpec::hasScmParams() const {
237   return scmMergeBase;
238 }
239 
240 /* vim:ts=2:sw=2:et:
241  */
242