blob: cd92f205a023d63862da0265949ab1d3fb170af9 [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"
Austin Schuhcb108412019-10-13 16:09:54 -070016#include "aos/unique_malloc_ptr.h"
17#include "aos/util/file.h"
18#include "glog/logging.h"
John Park7bca9812019-10-14 21:23:45 -070019#include "absl/base/call_once.h"
Brian Silverman66f079a2013-08-26 16:24:30 -070020
21namespace aos {
Austin Schuhcb108412019-10-13 16:09:54 -070022
Austin Schuh40485ed2019-10-26 21:51:44 -070023// Define the compare and equal operators for Channel and Application so we can
Austin Schuhcb108412019-10-13 16:09:54 -070024// 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.
Austin Schuh40485ed2019-10-26 21:51:44 -070030bool operator<(const FlatbufferDetachedBuffer<Channel> &lhs,
31 const FlatbufferDetachedBuffer<Channel> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -070032 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
Austin Schuh40485ed2019-10-26 21:51:44 -070044bool operator==(const FlatbufferDetachedBuffer<Channel> &lhs,
45 const FlatbufferDetachedBuffer<Channel> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -070046 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
Austin Schuh40485ed2019-10-26 21:51:44 -070052bool operator==(const FlatbufferDetachedBuffer<Application> &lhs,
53 const FlatbufferDetachedBuffer<Application> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -070054 return lhs.message().name()->string_view() ==
55 rhs.message().name()->string_view();
56}
57
Austin Schuh40485ed2019-10-26 21:51:44 -070058bool operator<(const FlatbufferDetachedBuffer<Application> &lhs,
59 const FlatbufferDetachedBuffer<Application> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -070060 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";
John Park7bca9812019-10-14 21:23:45 -070070
71void DoGetOwnIPAddress(in_addr *retu) {
Brian Silverman01be0002014-05-10 15:44:38 -070072 static const char *kOverrideVariable = "FRC971_IP_OVERRIDE";
73 const char *override_ip = getenv(kOverrideVariable);
74 if (override_ip != NULL) {
Austin Schuhcb108412019-10-13 16:09:54 -070075 LOG(INFO) << "Override IP is " << override_ip;
John Park7bca9812019-10-14 21:23:45 -070076 if (inet_aton(override_ip, retu) != 0) {
77 return;
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) {
John Park7bca9812019-10-14 21:23:45 -070098 *retu = reinterpret_cast<sockaddr_in *>(__builtin_assume_aligned(
Brian Silverman63cf2412013-11-17 05:44:36 -080099 addrs->ifa_addr, alignof(sockaddr_in)))->sin_addr;
John Park7bca9812019-10-14 21:23:45 -0700100 return;
Brian Silverman66f079a2013-08-26 16:24:30 -0700101 }
102 }
103 }
Austin Schuhcb108412019-10-13 16:09:54 -0700104 LOG(FATAL) << "couldn't find an AF_INET interface named \""
105 << kLinuxNetInterface << "\"";
Brian Silverman66f079a2013-08-26 16:24:30 -0700106}
107
John Park7bca9812019-10-14 21:23:45 -0700108void DoGetRootDirectory(char** retu) {
Brian Silverman66f079a2013-08-26 16:24:30 -0700109 ssize_t size = 0;
John Park7bca9812019-10-14 21:23:45 -0700110 *retu = NULL;
Brian Silverman66f079a2013-08-26 16:24:30 -0700111 while (true) {
Brian Silverman66f079a2013-08-26 16:24:30 -0700112 size += 256;
John Park7bca9812019-10-14 21:23:45 -0700113 if (*retu != nullptr) delete *retu;
114 *retu = new char[size];
Brian Silverman66f079a2013-08-26 16:24:30 -0700115
John Park7bca9812019-10-14 21:23:45 -0700116 ssize_t ret = readlink("/proc/self/exe", *retu, size);
Brian Silverman66f079a2013-08-26 16:24:30 -0700117 if (ret < 0) {
118 if (ret != -1) {
Austin Schuhcb108412019-10-13 16:09:54 -0700119 LOG(WARNING) << "it returned " << ret << ", not -1";
Brian Silverman66f079a2013-08-26 16:24:30 -0700120 }
John Park7bca9812019-10-14 21:23:45 -0700121
122 PLOG(FATAL) << "readlink(\"/proc/self/exe\", " << *retu << ", " << size
Austin Schuhcb108412019-10-13 16:09:54 -0700123 << ") failed";
Brian Silverman66f079a2013-08-26 16:24:30 -0700124 }
125 if (ret < size) {
John Park7bca9812019-10-14 21:23:45 -0700126 void *last_slash = memrchr(*retu, '/', ret);
Brian Silverman66f079a2013-08-26 16:24:30 -0700127 if (last_slash == NULL) {
John Park7bca9812019-10-14 21:23:45 -0700128 *retu[ret] = '\0';
129 LOG(FATAL) << "couldn't find a '/' in \"" << *retu << "\"";
Brian Silverman66f079a2013-08-26 16:24:30 -0700130 }
131 *static_cast<char *>(last_slash) = '\0';
John Park7bca9812019-10-14 21:23:45 -0700132 LOG(INFO) << "got a root dir of \"" << *retu << "\"";
133 return;
Brian Silverman66f079a2013-08-26 16:24:30 -0700134 }
135 }
136}
137
John Park7bca9812019-10-14 21:23:45 -0700138void DoGetLoggingDirectory(char** retu) {
Brian Silverman66f079a2013-08-26 16:24:30 -0700139 static const char kSuffix[] = "/../../tmp/robot_logs";
140 const char *root = GetRootDirectory();
John Park7bca9812019-10-14 21:23:45 -0700141 *retu = new char[strlen(root) + sizeof(kSuffix)];
142 strcpy(*retu, root);
143 strcat(*retu, kSuffix);
Brian Silverman66f079a2013-08-26 16:24:30 -0700144}
145
Austin Schuhcb108412019-10-13 16:09:54 -0700146// Extracts the folder part of a path. Returns ./ if there is no path.
147absl::string_view ExtractFolder(const absl::string_view filename) {
148 auto last_slash_pos = filename.find_last_of("/\\");
149
150 return last_slash_pos == absl::string_view::npos
151 ? absl::string_view("./")
152 : filename.substr(0, last_slash_pos + 1);
153}
154
Austin Schuh40485ed2019-10-26 21:51:44 -0700155FlatbufferDetachedBuffer<Configuration> ReadConfig(
Austin Schuhcb108412019-10-13 16:09:54 -0700156 const absl::string_view path, absl::btree_set<std::string> *visited_paths) {
Austin Schuh40485ed2019-10-26 21:51:44 -0700157 FlatbufferDetachedBuffer<Configuration> config(JsonToFlatbuffer(
Austin Schuhcb108412019-10-13 16:09:54 -0700158 util::ReadFileToStringOrDie(path), ConfigurationTypeTable()));
159 // Depth first. Take the following example:
160 //
161 // config1.json:
162 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700163 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700164 // {
165 // "name": "/foo",
166 // "type": ".aos.bar",
167 // "max_size": 5
168 // }
169 // ],
170 // "imports": [
171 // "config2.json",
172 // ]
173 // }
174 //
175 // config2.json:
176 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700177 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700178 // {
179 // "name": "/foo",
180 // "type": ".aos.bar",
181 // "max_size": 7
182 // }
183 // ],
184 // }
185 //
186 // We want the main config (config1.json) to be able to override the imported
187 // config. That means that it needs to be merged into the imported configs,
188 // not the other way around.
189
190 // Track that we have seen this file before recursing.
191 visited_paths->insert(::std::string(path));
192
193 if (config.message().has_imports()) {
194 // Capture the imports.
195 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *v =
196 config.message().imports();
197
198 // And then wipe them. This gets GCed when we merge later.
199 config.mutable_message()->clear_imports();
200
201 // Start with an empty configuration to merge into.
Austin Schuh40485ed2019-10-26 21:51:44 -0700202 FlatbufferDetachedBuffer<Configuration> merged_config =
203 FlatbufferDetachedBuffer<Configuration>::Empty();
Austin Schuhcb108412019-10-13 16:09:54 -0700204
205 const ::std::string folder(ExtractFolder(path));
206
207 for (const flatbuffers::String *str : *v) {
208 const ::std::string included_config = folder + str->c_str();
209 // Abort on any paths we have already seen.
210 CHECK(visited_paths->find(included_config) == visited_paths->end())
211 << ": Found duplicate file " << included_config << " while reading "
212 << path;
213
214 // And them merge everything in.
215 merged_config = MergeFlatBuffers(
216 merged_config, ReadConfig(included_config, visited_paths));
217 }
218
219 // Finally, merge this file in.
220 config = MergeFlatBuffers(merged_config, config);
221 }
222 return config;
223}
224
225// Remove duplicate entries, and handle overrides.
Austin Schuh40485ed2019-10-26 21:51:44 -0700226FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
Austin Schuhcb108412019-10-13 16:09:54 -0700227 const Flatbuffer<Configuration> &config) {
Austin Schuh40485ed2019-10-26 21:51:44 -0700228 // Store all the channels in a sorted set. This lets us track channels we
Austin Schuhcb108412019-10-13 16:09:54 -0700229 // have seen before and merge the updates in.
Austin Schuh40485ed2019-10-26 21:51:44 -0700230 absl::btree_set<FlatbufferDetachedBuffer<Channel>> channels;
Austin Schuhcb108412019-10-13 16:09:54 -0700231
Austin Schuh40485ed2019-10-26 21:51:44 -0700232 if (config.message().has_channels()) {
233 for (const Channel *c : *config.message().channels()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700234 // Ignore malformed entries.
Austin Schuh40485ed2019-10-26 21:51:44 -0700235 if (!c->has_name()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700236 continue;
237 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700238 if (!c->has_type()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700239 continue;
240 }
241
Austin Schuh40485ed2019-10-26 21:51:44 -0700242 // Attempt to insert the channel.
243 auto result = channels.insert(CopyFlatBuffer(c));
Austin Schuhcb108412019-10-13 16:09:54 -0700244 if (!result.second) {
245 // Already there, so merge the new table into the original.
Austin Schuh40485ed2019-10-26 21:51:44 -0700246 *result.first = MergeFlatBuffers(*result.first, CopyFlatBuffer(c));
Austin Schuhcb108412019-10-13 16:09:54 -0700247 }
248 }
249 }
250
251 // Now repeat this for the application list.
Austin Schuh40485ed2019-10-26 21:51:44 -0700252 absl::btree_set<FlatbufferDetachedBuffer<Application>> applications;
Austin Schuhcb108412019-10-13 16:09:54 -0700253 if (config.message().has_applications()) {
254 for (const Application *a : *config.message().applications()) {
255 if (!a->has_name()) {
256 continue;
257 }
258
259 auto result = applications.insert(CopyFlatBuffer(a));
260 if (!result.second) {
261 *result.first = MergeFlatBuffers(*result.first, CopyFlatBuffer(a));
262 }
263 }
264 }
265
266 flatbuffers::FlatBufferBuilder fbb;
267 fbb.ForceDefaults(1);
268
269 // Start by building the vectors. They need to come before the final table.
Austin Schuh40485ed2019-10-26 21:51:44 -0700270 // Channels
271 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
272 channels_offset;
Austin Schuhcb108412019-10-13 16:09:54 -0700273 {
Austin Schuh40485ed2019-10-26 21:51:44 -0700274 ::std::vector<flatbuffers::Offset<Channel>> channel_offsets;
275 for (const FlatbufferDetachedBuffer<Channel> &c : channels) {
276 channel_offsets.emplace_back(
277 CopyFlatBuffer<Channel>(&c.message(), &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -0700278 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700279 channels_offset = fbb.CreateVector(channel_offsets);
Austin Schuhcb108412019-10-13 16:09:54 -0700280 }
281
282 // Applications
283 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
284 applications_offset;
285 {
286 ::std::vector<flatbuffers::Offset<Application>> applications_offsets;
Austin Schuh40485ed2019-10-26 21:51:44 -0700287 for (const FlatbufferDetachedBuffer<Application> &a : applications) {
Austin Schuhcb108412019-10-13 16:09:54 -0700288 applications_offsets.emplace_back(
289 CopyFlatBuffer<Application>(&a.message(), &fbb));
290 }
291 applications_offset = fbb.CreateVector(applications_offsets);
292 }
293
294 // Just copy the maps
295 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
296 maps_offset;
297 {
298 ::std::vector<flatbuffers::Offset<Map>> map_offsets;
299 if (config.message().has_maps()) {
300 for (const Map *m : *config.message().maps()) {
301 map_offsets.emplace_back(CopyFlatBuffer<Map>(m, &fbb));
302 }
303 maps_offset = fbb.CreateVector(map_offsets);
304 }
305 }
306
307 // And then build a Configuration with them all.
308 ConfigurationBuilder configuration_builder(fbb);
Austin Schuh40485ed2019-10-26 21:51:44 -0700309 configuration_builder.add_channels(channels_offset);
Austin Schuhcb108412019-10-13 16:09:54 -0700310 if (config.message().has_maps()) {
311 configuration_builder.add_maps(maps_offset);
312 }
313 configuration_builder.add_applications(applications_offset);
314
315 fbb.Finish(configuration_builder.Finish());
316 return fbb.Release();
317}
318
Austin Schuh40485ed2019-10-26 21:51:44 -0700319// Compares (c < p) a channel, and a name, type tuple.
320bool CompareChannels(const Channel *c,
321 ::std::pair<absl::string_view, absl::string_view> p) {
322 int name_compare = c->name()->string_view().compare(p.first);
Austin Schuhcb108412019-10-13 16:09:54 -0700323 if (name_compare == 0) {
Austin Schuh40485ed2019-10-26 21:51:44 -0700324 return c->type()->string_view() < p.second;
Austin Schuhcb108412019-10-13 16:09:54 -0700325 } else if (name_compare < 0) {
326 return true;
327 } else {
328 return false;
329 }
330};
331
Austin Schuh40485ed2019-10-26 21:51:44 -0700332// Compares for equality (c == p) a channel, and a name, type tuple.
333bool EqualsChannels(const Channel *c,
Austin Schuhcb108412019-10-13 16:09:54 -0700334 ::std::pair<absl::string_view, absl::string_view> p) {
Austin Schuh40485ed2019-10-26 21:51:44 -0700335 return c->name()->string_view() == p.first &&
336 c->type()->string_view() == p.second;
Austin Schuhcb108412019-10-13 16:09:54 -0700337}
338
Austin Schuh40485ed2019-10-26 21:51:44 -0700339// Compares (c < p) an application, and a name;
Austin Schuhcb108412019-10-13 16:09:54 -0700340bool CompareApplications(const Application *a, absl::string_view name) {
341 return a->name()->string_view() < name;
342};
343
Austin Schuh40485ed2019-10-26 21:51:44 -0700344// Compares for equality (c == p) an application, and a name;
Austin Schuhcb108412019-10-13 16:09:54 -0700345bool EqualsApplications(const Application *a, absl::string_view name) {
346 return a->name()->string_view() == name;
347}
348
349// Maps name for the provided maps. Modifies name.
350void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
351 absl::string_view *name) {
352 // For the same reason we merge configs in reverse order, we want to process
Austin Schuh40485ed2019-10-26 21:51:44 -0700353 // maps in reverse order. That lets the outer config overwrite channels from
Austin Schuhcb108412019-10-13 16:09:54 -0700354 // the inner configs.
355 for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
356 if (i->has_match() && i->match()->has_name() && i->has_rename() &&
357 i->rename()->has_name() && i->match()->name()->string_view() == *name) {
358 VLOG(1) << "Renamed \"" << *name << "\" to \""
359 << i->rename()->name()->string_view() << "\"";
360 *name = i->rename()->name()->string_view();
361 }
362 }
363}
364
Brian Silverman66f079a2013-08-26 16:24:30 -0700365} // namespace
366
367const char *GetRootDirectory() {
John Park7bca9812019-10-14 21:23:45 -0700368 static char* root_dir;// return value
369 static absl::once_flag once_;
370 absl::call_once(once_, DoGetRootDirectory, &root_dir);
371 return root_dir;
Brian Silverman66f079a2013-08-26 16:24:30 -0700372}
373
374const char *GetLoggingDirectory() {
John Park7bca9812019-10-14 21:23:45 -0700375 static char* retu;// return value
376 static absl::once_flag once_;
377 absl::call_once(once_, DoGetLoggingDirectory, &retu);
378 return retu;
Brian Silverman66f079a2013-08-26 16:24:30 -0700379}
380
381const in_addr &GetOwnIPAddress() {
John Park7bca9812019-10-14 21:23:45 -0700382 static in_addr retu;// return value
383 static absl::once_flag once_;
384 absl::call_once(once_, DoGetOwnIPAddress, &retu);
385 return retu;
Brian Silverman66f079a2013-08-26 16:24:30 -0700386}
387
Austin Schuh40485ed2019-10-26 21:51:44 -0700388FlatbufferDetachedBuffer<Configuration> ReadConfig(
389 const absl::string_view path) {
Austin Schuhcb108412019-10-13 16:09:54 -0700390 // We only want to read a file once. So track the visited files in a set.
391 absl::btree_set<std::string> visited_paths;
392 return MergeConfiguration(ReadConfig(path, &visited_paths));
393}
394
Austin Schuh40485ed2019-10-26 21:51:44 -0700395const Channel *GetChannel(const Configuration *config, absl::string_view name,
396 absl::string_view type,
397 absl::string_view application_name) {
Austin Schuhcb108412019-10-13 16:09:54 -0700398 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
399 << "\" }";
400
401 // First handle application specific maps. Only do this if we have a matching
402 // application name, and it has maps.
Austin Schuh40485ed2019-10-26 21:51:44 -0700403 if (config->has_applications()) {
404 auto application_iterator = std::lower_bound(
405 config->applications()->cbegin(), config->applications()->cend(),
406 application_name, CompareApplications);
407 if (application_iterator != config->applications()->cend() &&
Austin Schuhcb108412019-10-13 16:09:54 -0700408 EqualsApplications(*application_iterator, application_name)) {
409 if (application_iterator->has_maps()) {
410 HandleMaps(application_iterator->maps(), &name);
411 }
412 }
413 }
414
415 // Now do global maps.
Austin Schuh40485ed2019-10-26 21:51:44 -0700416 if (config->has_maps()) {
417 HandleMaps(config->maps(), &name);
Austin Schuhcb108412019-10-13 16:09:54 -0700418 }
419
Austin Schuh40485ed2019-10-26 21:51:44 -0700420 // Then look for the channel.
421 auto channel_iterator =
422 std::lower_bound(config->channels()->cbegin(),
423 config->channels()->cend(),
424 std::make_pair(name, type), CompareChannels);
Austin Schuhcb108412019-10-13 16:09:54 -0700425
426 // Make sure we actually found it, and it matches.
Austin Schuh40485ed2019-10-26 21:51:44 -0700427 if (channel_iterator != config->channels()->cend() &&
428 EqualsChannels(*channel_iterator, std::make_pair(name, type))) {
429 VLOG(1) << "Found: " << FlatbufferToJson(*channel_iterator);
430 return *channel_iterator;
Austin Schuhcb108412019-10-13 16:09:54 -0700431 } else {
432 VLOG(1) << "No match for { \"name\": \"" << name << "\", \"type\": \""
433 << type << "\" }";
434 return nullptr;
435 }
436}
437
Brian Silverman66f079a2013-08-26 16:24:30 -0700438} // namespace configuration
439} // namespace aos