blob: a1cc614e9ac64995ce2214e8645967e975e2b710 [file] [log] [blame]
John Park398c74a2018-10-20 21:17:39 -07001#include "aos/configuration.h"
Brian Silverman66f079a2013-08-26 16:24:30 -07002
3#include <string.h>
Brian Silverman66f079a2013-08-26 16:24:30 -07004#include <stdlib.h>
5#include <sys/types.h>
6#include <netinet/in.h>
7#include <arpa/inet.h>
8#include <ifaddrs.h>
9#include <unistd.h>
10
Austin Schuhcb108412019-10-13 16:09:54 -070011#include "absl/container/btree_set.h"
12#include "absl/strings/string_view.h"
13#include "aos/configuration_generated.h"
14#include "aos/flatbuffer_merge.h"
15#include "aos/json_to_flatbuffer.h"
Sabina Davis2ed5ea22017-09-26 22:27:42 -070016#include "aos/once.h"
Austin Schuhcb108412019-10-13 16:09:54 -070017#include "aos/unique_malloc_ptr.h"
18#include "aos/util/file.h"
19#include "glog/logging.h"
Brian Silverman66f079a2013-08-26 16:24:30 -070020
21namespace aos {
Austin Schuhcb108412019-10-13 16:09:54 -070022
23// Define the compare and equal operators for Location and Application so we can
24// insert them in the btree below.
25//
26// These are not in headers because they are only comparing part of the
27// flatbuffer, and it seems weird to expose that as *the* compare operator. And
28// I can't put them in an anonymous namespace because they wouldn't be found
29// that way by the btree.
30bool operator<(const Flatbuffer<Location> &lhs,
31 const Flatbuffer<Location> &rhs) {
32 int name_compare = lhs.message().name()->string_view().compare(
33 rhs.message().name()->string_view());
34 if (name_compare == 0) {
35 return lhs.message().type()->string_view() <
36 rhs.message().type()->string_view();
37 } else if (name_compare < 0) {
38 return true;
39 } else {
40 return false;
41 }
42}
43
44bool operator==(const Flatbuffer<Location> &lhs,
45 const Flatbuffer<Location> &rhs) {
46 return lhs.message().name()->string_view() ==
47 rhs.message().name()->string_view() &&
48 lhs.message().type()->string_view() ==
49 rhs.message().type()->string_view();
50}
51
52bool operator==(const Flatbuffer<Application> &lhs,
53 const Flatbuffer<Application> &rhs) {
54 return lhs.message().name()->string_view() ==
55 rhs.message().name()->string_view();
56}
57
58bool operator<(const Flatbuffer<Application> &lhs,
59 const Flatbuffer<Application> &rhs) {
60 return lhs.message().name()->string_view() <
61 rhs.message().name()->string_view();
62}
63
Brian Silverman66f079a2013-08-26 16:24:30 -070064namespace configuration {
65namespace {
66
Austin Schuh629821e2014-02-10 21:18:27 -080067// TODO(brians): This shouldn't be necesary for running tests. Provide a way to
68// set the IP address when running tests from the test.
Brian Silverman14fd0fb2014-01-14 21:42:01 -080069const char *const kLinuxNetInterface = "eth0";
Brian Silverman66f079a2013-08-26 16:24:30 -070070const in_addr *DoGetOwnIPAddress() {
Brian Silverman01be0002014-05-10 15:44:38 -070071 static const char *kOverrideVariable = "FRC971_IP_OVERRIDE";
72 const char *override_ip = getenv(kOverrideVariable);
73 if (override_ip != NULL) {
Austin Schuhcb108412019-10-13 16:09:54 -070074 LOG(INFO) << "Override IP is " << override_ip;
Brian Silverman01be0002014-05-10 15:44:38 -070075 static in_addr r;
76 if (inet_aton(override_ip, &r) != 0) {
77 return &r;
joe3779d0c2014-02-15 19:41:22 -080078 } else {
Austin Schuhcb108412019-10-13 16:09:54 -070079 LOG(WARNING) << "error parsing " << kOverrideVariable << " value '"
80 << override_ip << "'";
joe3779d0c2014-02-15 19:41:22 -080081 }
Brian Silverman01be0002014-05-10 15:44:38 -070082 } else {
Austin Schuhcb108412019-10-13 16:09:54 -070083 LOG(INFO) << "Couldn't get environmental variable.";
Brian Silverman01be0002014-05-10 15:44:38 -070084 }
joe3779d0c2014-02-15 19:41:22 -080085
Brian Silverman66f079a2013-08-26 16:24:30 -070086 ifaddrs *addrs;
87 if (getifaddrs(&addrs) != 0) {
Austin Schuhcb108412019-10-13 16:09:54 -070088 PLOG(FATAL) << "getifaddrs(" << &addrs << ") failed";
Brian Silverman66f079a2013-08-26 16:24:30 -070089 }
90 // Smart pointers don't work very well for iterating through a linked list,
91 // but it does do a very nice job of making sure that addrs gets freed.
92 unique_c_ptr<ifaddrs, freeifaddrs> addrs_deleter(addrs);
93
Austin Schuh629821e2014-02-10 21:18:27 -080094 for (; addrs != nullptr; addrs = addrs->ifa_next) {
Brian Silverman6da04272014-05-18 18:47:48 -070095 // ifa_addr tends to be nullptr on CAN interfaces.
Austin Schuh629821e2014-02-10 21:18:27 -080096 if (addrs->ifa_addr != nullptr && addrs->ifa_addr->sa_family == AF_INET) {
Brian Silverman14fd0fb2014-01-14 21:42:01 -080097 if (strcmp(kLinuxNetInterface, addrs->ifa_name) == 0) {
Brian Silverman66f079a2013-08-26 16:24:30 -070098 static const in_addr r =
Brian Silverman63cf2412013-11-17 05:44:36 -080099 reinterpret_cast<sockaddr_in *>(__builtin_assume_aligned(
100 addrs->ifa_addr, alignof(sockaddr_in)))->sin_addr;
Brian Silverman66f079a2013-08-26 16:24:30 -0700101 return &r;
102 }
103 }
104 }
Austin Schuhcb108412019-10-13 16:09:54 -0700105 LOG(FATAL) << "couldn't find an AF_INET interface named \""
106 << kLinuxNetInterface << "\"";
107 return nullptr;
Brian Silverman66f079a2013-08-26 16:24:30 -0700108}
109
110const char *DoGetRootDirectory() {
111 ssize_t size = 0;
112 char *r = NULL;
113 while (true) {
114 if (r != NULL) delete r;
115 size += 256;
116 r = new char[size];
117
118 ssize_t ret = readlink("/proc/self/exe", r, size);
119 if (ret < 0) {
120 if (ret != -1) {
Austin Schuhcb108412019-10-13 16:09:54 -0700121 LOG(WARNING) << "it returned " << ret << ", not -1";
Brian Silverman66f079a2013-08-26 16:24:30 -0700122 }
Austin Schuhcb108412019-10-13 16:09:54 -0700123 PLOG(FATAL) << "readlink(\"/proc/self/exe\", " << r << ", " << size
124 << ") failed";
Brian Silverman66f079a2013-08-26 16:24:30 -0700125 }
126 if (ret < size) {
Brian Silvermane6335e42014-02-20 20:53:06 -0800127 void *last_slash = memrchr(r, '/', ret);
Brian Silverman66f079a2013-08-26 16:24:30 -0700128 if (last_slash == NULL) {
129 r[ret] = '\0';
Austin Schuhcb108412019-10-13 16:09:54 -0700130 LOG(FATAL) << "couldn't find a '/' in \"" << r << "\"";
Brian Silverman66f079a2013-08-26 16:24:30 -0700131 }
132 *static_cast<char *>(last_slash) = '\0';
Austin Schuhcb108412019-10-13 16:09:54 -0700133 LOG(INFO) << "got a root dir of \"" << r << "\"";
Brian Silverman66f079a2013-08-26 16:24:30 -0700134 return r;
135 }
136 }
137}
138
139const char *DoGetLoggingDirectory() {
140 static const char kSuffix[] = "/../../tmp/robot_logs";
141 const char *root = GetRootDirectory();
142 char *r = new char[strlen(root) + sizeof(kSuffix)];
143 strcpy(r, root);
144 strcat(r, kSuffix);
145 return r;
146}
147
Austin Schuhcb108412019-10-13 16:09:54 -0700148// Extracts the folder part of a path. Returns ./ if there is no path.
149absl::string_view ExtractFolder(const absl::string_view filename) {
150 auto last_slash_pos = filename.find_last_of("/\\");
151
152 return last_slash_pos == absl::string_view::npos
153 ? absl::string_view("./")
154 : filename.substr(0, last_slash_pos + 1);
155}
156
157Flatbuffer<Configuration> ReadConfig(
158 const absl::string_view path, absl::btree_set<std::string> *visited_paths) {
159 Flatbuffer<Configuration> config(JsonToFlatbuffer(
160 util::ReadFileToStringOrDie(path), ConfigurationTypeTable()));
161 // Depth first. Take the following example:
162 //
163 // config1.json:
164 // {
165 // "locations": [
166 // {
167 // "name": "/foo",
168 // "type": ".aos.bar",
169 // "max_size": 5
170 // }
171 // ],
172 // "imports": [
173 // "config2.json",
174 // ]
175 // }
176 //
177 // config2.json:
178 // {
179 // "locations": [
180 // {
181 // "name": "/foo",
182 // "type": ".aos.bar",
183 // "max_size": 7
184 // }
185 // ],
186 // }
187 //
188 // We want the main config (config1.json) to be able to override the imported
189 // config. That means that it needs to be merged into the imported configs,
190 // not the other way around.
191
192 // Track that we have seen this file before recursing.
193 visited_paths->insert(::std::string(path));
194
195 if (config.message().has_imports()) {
196 // Capture the imports.
197 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *v =
198 config.message().imports();
199
200 // And then wipe them. This gets GCed when we merge later.
201 config.mutable_message()->clear_imports();
202
203 // Start with an empty configuration to merge into.
204 Flatbuffer<Configuration> merged_config =
205 Flatbuffer<Configuration>::Empty();
206
207 const ::std::string folder(ExtractFolder(path));
208
209 for (const flatbuffers::String *str : *v) {
210 const ::std::string included_config = folder + str->c_str();
211 // Abort on any paths we have already seen.
212 CHECK(visited_paths->find(included_config) == visited_paths->end())
213 << ": Found duplicate file " << included_config << " while reading "
214 << path;
215
216 // And them merge everything in.
217 merged_config = MergeFlatBuffers(
218 merged_config, ReadConfig(included_config, visited_paths));
219 }
220
221 // Finally, merge this file in.
222 config = MergeFlatBuffers(merged_config, config);
223 }
224 return config;
225}
226
227// Remove duplicate entries, and handle overrides.
228Flatbuffer<Configuration> MergeConfiguration(
229 const Flatbuffer<Configuration> &config) {
230 // Store all the locations in a sorted set. This lets us track locations we
231 // have seen before and merge the updates in.
232 absl::btree_set<Flatbuffer<Location>> locations;
233
234 if (config.message().has_locations()) {
235 for (const Location *l : *config.message().locations()) {
236 // Ignore malformed entries.
237 if (!l->has_name()) {
238 continue;
239 }
240 if (!l->has_type()) {
241 continue;
242 }
243
244 // Attempt to insert the location.
245 auto result = locations.insert(CopyFlatBuffer(l));
246 if (!result.second) {
247 // Already there, so merge the new table into the original.
248 *result.first = MergeFlatBuffers(*result.first, CopyFlatBuffer(l));
249 }
250 }
251 }
252
253 // Now repeat this for the application list.
254 absl::btree_set<Flatbuffer<Application>> applications;
255 if (config.message().has_applications()) {
256 for (const Application *a : *config.message().applications()) {
257 if (!a->has_name()) {
258 continue;
259 }
260
261 auto result = applications.insert(CopyFlatBuffer(a));
262 if (!result.second) {
263 *result.first = MergeFlatBuffers(*result.first, CopyFlatBuffer(a));
264 }
265 }
266 }
267
268 flatbuffers::FlatBufferBuilder fbb;
269 fbb.ForceDefaults(1);
270
271 // Start by building the vectors. They need to come before the final table.
272 // Locations
273 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Location>>>
274 locations_offset;
275 {
276 ::std::vector<flatbuffers::Offset<Location>> location_offsets;
277 for (const Flatbuffer<Location> &l : locations) {
278 location_offsets.emplace_back(
279 CopyFlatBuffer<Location>(&l.message(), &fbb));
280 }
281 locations_offset = fbb.CreateVector(location_offsets);
282 }
283
284 // Applications
285 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
286 applications_offset;
287 {
288 ::std::vector<flatbuffers::Offset<Application>> applications_offsets;
289 for (const Flatbuffer<Application> &a : applications) {
290 applications_offsets.emplace_back(
291 CopyFlatBuffer<Application>(&a.message(), &fbb));
292 }
293 applications_offset = fbb.CreateVector(applications_offsets);
294 }
295
296 // Just copy the maps
297 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
298 maps_offset;
299 {
300 ::std::vector<flatbuffers::Offset<Map>> map_offsets;
301 if (config.message().has_maps()) {
302 for (const Map *m : *config.message().maps()) {
303 map_offsets.emplace_back(CopyFlatBuffer<Map>(m, &fbb));
304 }
305 maps_offset = fbb.CreateVector(map_offsets);
306 }
307 }
308
309 // And then build a Configuration with them all.
310 ConfigurationBuilder configuration_builder(fbb);
311 configuration_builder.add_locations(locations_offset);
312 if (config.message().has_maps()) {
313 configuration_builder.add_maps(maps_offset);
314 }
315 configuration_builder.add_applications(applications_offset);
316
317 fbb.Finish(configuration_builder.Finish());
318 return fbb.Release();
319}
320
321// Compares (l < p) a location, and a name, type tuple.
322bool CompareLocations(const Location *l,
323 ::std::pair<absl::string_view, absl::string_view> p) {
324 int name_compare = l->name()->string_view().compare(p.first);
325 if (name_compare == 0) {
326 return l->type()->string_view() < p.second;
327 } else if (name_compare < 0) {
328 return true;
329 } else {
330 return false;
331 }
332};
333
334// Compares for equality (l == p) a location, and a name, type tuple.
335bool EqualsLocations(const Location *l,
336 ::std::pair<absl::string_view, absl::string_view> p) {
337 return l->name()->string_view() == p.first &&
338 l->type()->string_view() == p.second;
339}
340
341// Compares (l < p) an application, and a name;
342bool CompareApplications(const Application *a, absl::string_view name) {
343 return a->name()->string_view() < name;
344};
345
346// Compares for equality (l == p) an application, and a name;
347bool EqualsApplications(const Application *a, absl::string_view name) {
348 return a->name()->string_view() == name;
349}
350
351// Maps name for the provided maps. Modifies name.
352void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
353 absl::string_view *name) {
354 // For the same reason we merge configs in reverse order, we want to process
355 // maps in reverse order. That lets the outer config overwrite locations from
356 // the inner configs.
357 for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
358 if (i->has_match() && i->match()->has_name() && i->has_rename() &&
359 i->rename()->has_name() && i->match()->name()->string_view() == *name) {
360 VLOG(1) << "Renamed \"" << *name << "\" to \""
361 << i->rename()->name()->string_view() << "\"";
362 *name = i->rename()->name()->string_view();
363 }
364 }
365}
366
Brian Silverman66f079a2013-08-26 16:24:30 -0700367} // namespace
368
369const char *GetRootDirectory() {
370 static aos::Once<const char> once(DoGetRootDirectory);
371 return once.Get();
372}
373
374const char *GetLoggingDirectory() {
375 static aos::Once<const char> once(DoGetLoggingDirectory);
376 return once.Get();
377}
378
379const in_addr &GetOwnIPAddress() {
380 static aos::Once<const in_addr> once(DoGetOwnIPAddress);
381 return *once.Get();
382}
383
Austin Schuhcb108412019-10-13 16:09:54 -0700384Flatbuffer<Configuration> ReadConfig(const absl::string_view path) {
385 // We only want to read a file once. So track the visited files in a set.
386 absl::btree_set<std::string> visited_paths;
387 return MergeConfiguration(ReadConfig(path, &visited_paths));
388}
389
390const Location *GetLocation(const Flatbuffer<Configuration> &config,
391 absl::string_view name, absl::string_view type,
392 absl::string_view application_name) {
393 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
394 << "\" }";
395
396 // First handle application specific maps. Only do this if we have a matching
397 // application name, and it has maps.
398 if (config.message().has_applications()) {
399 auto application_iterator =
400 std::lower_bound(config.message().applications()->cbegin(),
401 config.message().applications()->cend(),
402 application_name, CompareApplications);
403 if (application_iterator != config.message().applications()->cend() &&
404 EqualsApplications(*application_iterator, application_name)) {
405 if (application_iterator->has_maps()) {
406 HandleMaps(application_iterator->maps(), &name);
407 }
408 }
409 }
410
411 // Now do global maps.
412 if (config.message().has_maps()) {
413 HandleMaps(config.message().maps(), &name);
414 }
415
416 // Then look for the location.
417 auto location_iterator =
418 std::lower_bound(config.message().locations()->cbegin(),
419 config.message().locations()->cend(),
420 std::make_pair(name, type), CompareLocations);
421
422 // Make sure we actually found it, and it matches.
423 if (location_iterator != config.message().locations()->cend() &&
424 EqualsLocations(*location_iterator, std::make_pair(name, type))) {
425 VLOG(1) << "Found: " << FlatbufferToJson(*location_iterator);
426 return *location_iterator;
427 } else {
428 VLOG(1) << "No match for { \"name\": \"" << name << "\", \"type\": \""
429 << type << "\" }";
430 return nullptr;
431 }
432}
433
Brian Silverman66f079a2013-08-26 16:24:30 -0700434} // namespace configuration
435} // namespace aos