Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
utils.cc
1 #include "utilities/utils.h"
2 
3 #include <stdio.h>
4 #include <algorithm>
5 #include <array>
6 #include <cstdlib>
7 #include <fstream>
8 #include <iomanip>
9 #include <iostream>
10 #include <random>
11 #include <sstream>
12 #include <stdexcept>
13 #include <string>
14 
15 #include <archive.h>
16 #include <archive_entry.h>
17 #include <arpa/inet.h>
18 #include <fcntl.h>
19 #include <glob.h>
20 #include <ifaddrs.h>
21 #include <netinet/in.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #include <boost/algorithm/string.hpp>
26 #include <boost/archive/iterators/base64_from_binary.hpp>
27 #include <boost/archive/iterators/binary_from_base64.hpp>
28 #include <boost/archive/iterators/remove_whitespace.hpp>
29 #include <boost/archive/iterators/transform_width.hpp>
30 #include <boost/filesystem.hpp>
31 #include <boost/uuid/random_generator.hpp>
32 #include <boost/uuid/uuid_io.hpp>
33 
34 #include "aktualizr_version.h"
35 #include "logging/logging.h"
36 
37 static const std::array<const char *, 132> adverbs = {
38  "adorable", "acidic", "ample", "aromatic", "artistic", "attractive", "basic", "beautiful",
39  "best", "blissful", "bubbly", "celebrated", "cheap", "chilly", "cloudy", "colorful",
40  "colossal", "complete", "conventional", "costly", "creamy", "crisp", "dense", "double",
41  "dry", "easy", "every", "exotic", "expert", "fake", "fabulous", "fast",
42  "fine", "firm", "first", "flaky", "flat", "fluffy", "frozen", "generous",
43  "giant", "glass", "glorious", "golden", "good", "grand", "great", "half",
44  "happy", "hard", "healthy", "heavy", "hot", "huge", "humble", "ideal",
45  "icy", "incredible", "interesting", "joyous", "juicy", "jumbo", "large", "late",
46  "lavish", "leafy", "lean", "light", "lovely", "marvelous", "mature", "modern",
47  "modest", "neat", "new", "nice", "nifty", "nutty", "oily", "ornate",
48  "perfect", "plain", "posh", "pretty", "prime", "proper", "pure", "quick",
49  "raw", "real", "rich", "ripe", "safe", "salty", "several", "short",
50  "simple", "slim", "slow", "smooth", "soft", "solid", "speedy", "spotless",
51  "strong", "stylish", "subtle", "super", "sweet", "tasty", "tempting", "tender",
52  "terrific", "thick", "thin", "tidy", "tiny", "twin", "ultimate", "unique",
53  "uniform", "unusual", "valuable", "vast", "warm", "wavy", "wet", "whole",
54  "wide", "wild", "wooden", "young"};
55 
56 static const std::array<const char *, 128> names = {"Allerlei",
57  "Apfelkuchen",
58  "Backerbsen",
59  "Baumkuchen",
60  "Beetenbartsch",
61  "Berliner",
62  "Bethmaennchen",
63  "Biersuppe",
64  "Birnenfladen",
65  "Bohnen",
66  "Bratapfel",
67  "Bratkartoffeln",
68  "Brezel",
69  "Broetchen",
70  "Butterkuchen",
71  "Currywurst",
72  "Dampfnudel",
73  "Dibbelabbes",
74  "Eierkuchen",
75  "Eintopf",
76  "Erbsensuppe",
77  "Flaedlesuppe",
78  "Flammkuchen",
79  "Fliederbeersuppe",
80  "Franzbroetchen",
81  "Funkenkuechlein",
82  "Gedadschde",
83  "Gemueseschnitzel",
84  "Germknoedel",
85  "Gerstensuppe",
86  "Griessbrei",
87  "Gruenkohl",
88  "Gruetze",
89  "Gummibaerchen",
90  "Gurkensalat",
91  "Habermus",
92  "Haddekuche",
93  "Hagebuttenmark",
94  "Handkaese",
95  "Herrencreme",
96  "Hoorische",
97  "Kaesekuchen",
98  "Kaiserschmarrn",
99  "Kartoffelpueree",
100  "Kartoffelpuffer",
101  "Kartoffelsalat",
102  "Kastanien",
103  "Kichererbsen",
104  "Kirschenmichel",
105  "Kirschtorte",
106  "Klaben",
107  "Kloesse",
108  "Kluntjes",
109  "Knaeckebrot",
110  "Kniekuechle",
111  "Knoedel",
112  "Kohlroulade",
113  "Krautfleckerl",
114  "Kuerbiskernbrot",
115  "Kuerbissuppe",
116  "Lebkuchen",
117  "Linsen",
118  "Loeffelerbsen",
119  "Magenbrot",
120  "Marillenknoedel",
121  "Maroni",
122  "Marsch",
123  "Marzipan",
124  "Maultaschen",
125  "Milliramstrudel",
126  "Mischbrot",
127  "Mohnnudeln",
128  "Mohnpielen",
129  "Mohnzelten",
130  "Muesli",
131  "Nussecke",
132  "Nusstorte",
133  "Palatschinke",
134  "Pellkartoffeln",
135  "Pfannkuchen",
136  "Pfefferkuchen",
137  "Pillekuchen",
138  "Pommes",
139  "Poschweck",
140  "Powidltascherl",
141  "Printen",
142  "Prinzregententorte",
143  "Pumpernickel",
144  "Punschkrapfen",
145  "Quarkkeulchen",
146  "Quetschkartoffeln",
147  "Raclette",
148  "Radi",
149  "Reibekuchen",
150  "Reinling",
151  "Riebel",
152  "Roeggelchen",
153  "Roesti",
154  "Sauerkraut",
155  "Schmalzkuchen",
156  "Schmorgurken",
157  "Schnippelbohnen",
158  "Schoeberl",
159  "Schrippe",
160  "Schupfnudel",
161  "Schuxen",
162  "Schwammerlsuppe",
163  "Schweineohren",
164  "Sonnenblumenkernbrot",
165  "Spaetzle",
166  "Spaghettieis",
167  "Spargel",
168  "Spekulatius",
169  "Springerle",
170  "Spritzkuchen",
171  "Stampfkartoffeln",
172  "Sterz",
173  "Stollen",
174  "Streuselkuchen",
175  "Tilsit",
176  "Toastbrot",
177  "Topfenstrudel",
178  "Vollkornbrot",
179  "Wibele",
180  "Wickelkloesse",
181  "Zimtwaffeln",
182  "Zwetschkenroester",
183  "Zwiebelkuchen"};
184 
185 typedef boost::archive::iterators::base64_from_binary<
186  boost::archive::iterators::transform_width<std::string::const_iterator, 6, 8> >
187  base64_text;
188 
189 typedef boost::archive::iterators::transform_width<
190  boost::archive::iterators::binary_from_base64<
191  boost::archive::iterators::remove_whitespace<std::string::const_iterator> >,
192  8, 6>
193  base64_to_bin;
194 
195 std::string Utils::fromBase64(std::string base64_string) {
196  int64_t paddingChars = std::count(base64_string.begin(), base64_string.end(), '=');
197  std::replace(base64_string.begin(), base64_string.end(), '=', 'A');
198  std::string result(base64_to_bin(base64_string.begin()), base64_to_bin(base64_string.end()));
199  result.erase(result.end() - paddingChars, result.end());
200  return result;
201 }
202 
203 std::string Utils::toBase64(const std::string &tob64) {
204  std::string b64sig(base64_text(tob64.begin()), base64_text(tob64.end()));
205  b64sig.append((3 - tob64.length() % 3) % 3, '=');
206  return b64sig;
207 }
208 
209 // Strip leading and trailing quotes
210 std::string Utils::stripQuotes(const std::string &value) {
211  std::string res = value;
212  res.erase(std::remove(res.begin(), res.end(), '\"'), res.end());
213  return res;
214 }
215 
216 // Add leading and trailing quotes
217 std::string Utils::addQuotes(const std::string &value) { return "\"" + value + "\""; }
218 
219 std::string Utils::extractField(const std::string &in, unsigned int field_id) {
220  std::string out;
221  auto it = in.begin();
222 
223  // skip spaces
224  for (; it != in.end() && (isspace(*it) != 0); it++) {
225  ;
226  }
227  for (unsigned int k = 0; k < field_id; k++) {
228  bool empty = true;
229  for (; it != in.end() && (isspace(*it) == 0); it++) {
230  empty = false;
231  }
232  if (empty) {
233  throw std::runtime_error(std::string("No such field ").append(std::to_string(field_id)));
234  }
235  for (; it != in.end() && (isspace(*it) != 0); it++) {
236  ;
237  }
238  }
239 
240  for (; it != in.end() && (isspace(*it) == 0); it++) {
241  out += *it;
242  }
243  return out;
244 }
245 
246 Json::Value Utils::parseJSON(const std::string &json_str) {
247  std::istringstream strs(json_str);
248  Json::Value json_value;
249  parseFromStream(Json::CharReaderBuilder(), strs, &json_value, nullptr);
250  return json_value;
251 }
252 
253 Json::Value Utils::parseJSONFile(const boost::filesystem::path &filename) {
254  std::ifstream path_stream(filename.c_str());
255  std::string content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
256  return Utils::parseJSON(content);
257 }
258 
259 std::string Utils::genPrettyName() {
260  std::random_device urandom;
261 
262  std::uniform_int_distribution<size_t> adverbs_dist(0, adverbs.size() - 1);
263  std::uniform_int_distribution<size_t> nouns_dist(0, names.size() - 1);
264  std::uniform_int_distribution<size_t> digits(0, 999);
265  std::stringstream pretty_name;
266  pretty_name << adverbs[adverbs_dist(urandom)];
267  pretty_name << "-";
268  pretty_name << names[nouns_dist(urandom)];
269  pretty_name << "-";
270  pretty_name << digits(urandom);
271  std::string res = pretty_name.str();
272  std::transform(res.begin(), res.end(), res.begin(), ::tolower);
273  return res;
274 }
275 
276 std::string Utils::readFile(const boost::filesystem::path &filename, const bool trim) {
277  boost::filesystem::path tmpFilename = filename;
278  tmpFilename += ".new";
279  // that's kind of dangerous to include a specific use-case business logic
280  // into a generic function used by many use cases
281  // TODO: consider refactoring it, e.g. a generic readFile + a specific one with that includes
282  // the ".new" file handling
283  if (boost::filesystem::exists(tmpFilename)) {
284  LOG_WARNING << tmpFilename << " was found on FS, removing";
285  boost::filesystem::remove(tmpFilename);
286  }
287  std::ifstream path_stream(filename.c_str());
288  std::string content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
289 
290  if (trim) {
291  boost::trim_if(content, boost::is_any_of(" \t\r\n"));
292  }
293  return content;
294 }
295 
296 static constexpr size_t BSIZE = 20 * 512;
297 
299  archive_state(std::istream &is_in) : is(is_in) {}
300  std::istream &is;
301  std::array<char, BSIZE> buf{};
302 };
303 
304 static ssize_t read_cb(struct archive *a, void *client_data, const void **buffer) {
305  auto *s = reinterpret_cast<archive_state *>(client_data);
306  if (s->is.fail()) {
307  archive_set_error(a, -1, "unable to read from stream");
308  return 0;
309  }
310  if (s->is.eof()) {
311  return 0;
312  }
313  s->is.read(s->buf.data(), BSIZE);
314  if (!s->is.eof() && s->is.fail()) {
315  archive_set_error(a, -1, "unable to read from stream");
316  return 0;
317  }
318  *buffer = s->buf.data();
319 
320  return s->is.gcount();
321 }
322 
323 void Utils::writeFile(const boost::filesystem::path &filename, const std::string &content, bool create_directories) {
324  if (create_directories) {
325  boost::filesystem::create_directories(filename.parent_path());
326  }
327  Utils::writeFile(filename, content.c_str(), content.size());
328 }
329 
330 void Utils::writeFile(const boost::filesystem::path &filename, const char *content, size_t size) {
331  // also replace the target file atomically by creating filename.new and
332  // renaming it to the target file name
333  boost::filesystem::path tmpFilename = filename;
334  tmpFilename += ".new";
335 
336  std::ofstream file(tmpFilename.c_str());
337  if (!file.good()) {
338  throw std::runtime_error(std::string("Error opening file ") + tmpFilename.string());
339  }
340  file.write(content, static_cast<std::streamsize>(size));
341  file.close();
342 
343  boost::filesystem::rename(tmpFilename, filename);
344 }
345 
346 void Utils::writeFile(const boost::filesystem::path &filename, const Json::Value &content, bool create_directories) {
347  Utils::writeFile(filename, jsonToStr(content), create_directories);
348 }
349 
350 std::string Utils::jsonToStr(const Json::Value &json) {
351  std::stringstream ss;
352  ss << json;
353  return ss.str();
354 }
355 
356 std::string Utils::jsonToCanonicalStr(const Json::Value &json) {
357  static Json::StreamWriterBuilder wbuilder = []() {
358  Json::StreamWriterBuilder w;
359  wbuilder["indentation"] = "";
360  return w;
361  }();
362  return Json::writeString(wbuilder, json);
363 }
364 
365 Json::Value Utils::getHardwareInfo() {
366  std::string result;
367  const int exit_code = shell("lshw -json", &result);
368 
369  if (exit_code != 0) {
370  LOG_WARNING << "Could not execute lshw (is it installed?).";
371  return Json::Value();
372  }
373  const Json::Value parsed = Utils::parseJSON(result);
374  return (parsed.isArray()) ? parsed[0] : parsed;
375 }
376 
377 Json::Value Utils::getNetworkInfo() {
378  // get interface with default route
379  std::ifstream path_stream("/proc/net/route");
380  std::string route_content((std::istreambuf_iterator<char>(path_stream)), std::istreambuf_iterator<char>());
381 
382  struct Itf {
383  std::string name = std::string();
384  std::string ip = std::string();
385  std::string mac = std::string();
386  } itf;
387  std::istringstream route_stream(route_content);
388  std::array<char, 200> line{};
389 
390  // skip first line
391  route_stream.getline(&line[0], line.size());
392  while (route_stream.getline(&line[0], line.size())) {
393  std::string itfn = Utils::extractField(&line[0], 0);
394  std::string droute = Utils::extractField(&line[0], 1);
395  if (droute == "00000000") {
396  itf.name = itfn;
397  // take the first routing to 0
398  break;
399  }
400  }
401 
402  if (itf.name != "") {
403  {
404  // get ip address
405  StructGuard<struct ifaddrs> ifaddrs(nullptr, freeifaddrs);
406  {
407  struct ifaddrs *ifa;
408  if (getifaddrs(&ifa) < 0) {
409  LOG_ERROR << "getifaddrs: " << std::strerror(errno);
410  } else {
411  ifaddrs.reset(ifa);
412  }
413  }
414  if (ifaddrs != nullptr) {
415  for (struct ifaddrs *ifa = ifaddrs.get(); ifa != nullptr; ifa = ifa->ifa_next) {
416  if (itf.name == ifa->ifa_name) {
417  if (ifa->ifa_addr == nullptr) {
418  continue;
419  }
420  if (ifa->ifa_addr->sa_family != AF_INET) {
421  continue;
422  }
423  const struct sockaddr_storage *sa = reinterpret_cast<struct sockaddr_storage *>(ifa->ifa_addr);
424 
425  itf.ip = Utils::ipDisplayName(*sa);
426  }
427  }
428  }
429  }
430  {
431  // get mac address
432  std::ifstream mac_stream("/sys/class/net/" + itf.name + "/address");
433  std::string m((std::istreambuf_iterator<char>(mac_stream)), std::istreambuf_iterator<char>());
434  itf.mac = std::move(m);
435  boost::trim_right(itf.mac);
436  }
437  }
438 
439  Json::Value network_info;
440  network_info["local_ipv4"] = itf.ip;
441  network_info["mac"] = itf.mac;
442  network_info["hostname"] = Utils::getHostname();
443 
444  return network_info;
445 }
446 
447 std::string Utils::getHostname() {
448  std::array<char, 200> hostname{};
449  if (gethostname(hostname.data(), hostname.size()) < 0) {
450  return "";
451  }
452  return std::string(hostname.data());
453 }
454 
455 std::string Utils::randomUuid() {
456  std::random_device urandom;
457  boost::uuids::basic_random_generator<std::random_device> uuid_gen(urandom);
458  return boost::uuids::to_string(uuid_gen());
459 }
460 
461 // Note that this doesn't work with broken symlinks.
462 void Utils::copyDir(const boost::filesystem::path &from, const boost::filesystem::path &to) {
463  boost::filesystem::remove_all(to);
464 
465  boost::filesystem::create_directories(to);
466  boost::filesystem::directory_iterator it(from);
467 
468  for (; it != boost::filesystem::directory_iterator(); it++) {
469  if (boost::filesystem::is_directory(it->path())) {
470  copyDir(it->path(), to / it->path().filename());
471  } else {
472  boost::filesystem::copy_file(it->path(), to / it->path().filename());
473  }
474  }
475 }
476 
477 std::string Utils::readFileFromArchive(std::istream &as, const std::string &filename, const bool trim) {
478  struct archive *a = archive_read_new();
479  if (a == nullptr) {
480  LOG_ERROR << "archive error: could not initialize archive object";
481  throw std::runtime_error("archive error");
482  }
483  archive_read_support_filter_all(a);
484  archive_read_support_format_all(a);
485  auto state = std_::make_unique<archive_state>(std::ref(as));
486  int r = archive_read_open(a, reinterpret_cast<void *>(state.get()), nullptr, read_cb, nullptr);
487  if (r != ARCHIVE_OK) {
488  LOG_ERROR << "archive error: " << archive_error_string(a);
489  archive_read_free(a);
490  throw std::runtime_error("archive error");
491  }
492 
493  bool found = false;
494  std::stringstream out_stream;
495  struct archive_entry *entry;
496  while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
497  if (filename != archive_entry_pathname(entry)) {
498  archive_read_data_skip(a);
499  continue;
500  }
501 
502  const char *buff;
503  size_t size;
504  int64_t offset;
505 
506  for (;;) {
507  r = archive_read_data_block(a, reinterpret_cast<const void **>(&buff), &size, &offset);
508  if (r == ARCHIVE_EOF) {
509  found = true;
510  break;
511  }
512  if (r != ARCHIVE_OK) {
513  LOG_ERROR << "archive error: " << archive_error_string(a);
514  break;
515  }
516  if (size > 0 && buff != nullptr) {
517  out_stream.write(buff, static_cast<ssize_t>(size));
518  }
519  }
520  }
521 
522  r = archive_read_free(a);
523  if (r != ARCHIVE_OK) {
524  LOG_ERROR << "archive error: " << archive_error_string(a);
525  }
526 
527  if (!found) {
528  throw std::runtime_error("could not extract " + filename + " from archive");
529  }
530 
531  std::string result = out_stream.str();
532  if (trim) {
533  boost::trim_if(result, boost::is_any_of(" \t\r\n"));
534  }
535  return result;
536 }
537 
538 static ssize_t write_cb(struct archive *a, void *client_data, const void *buffer, size_t length) {
539  auto *s = reinterpret_cast<std::ostream *>(client_data);
540  s->write(reinterpret_cast<const char *>(buffer), static_cast<ssize_t>(length));
541  if (s->fail()) {
542  archive_set_error(a, -1, "unable to write in stream");
543  return -1;
544  }
545 
546  return static_cast<ssize_t>(length);
547 }
548 
549 void Utils::writeArchive(const std::map<std::string, std::string> &entries, std::ostream &as) {
550  struct archive *a = archive_write_new();
551  if (a == nullptr) {
552  LOG_ERROR << "archive error: could not initialize archive object";
553  throw std::runtime_error("archive error");
554  }
555  archive_write_set_format_pax(a);
556  archive_write_add_filter_gzip(a);
557 
558  int r = archive_write_open(a, reinterpret_cast<void *>(&as), nullptr, write_cb, nullptr);
559  if (r != ARCHIVE_OK) {
560  LOG_ERROR << "archive error: " << archive_error_string(a);
561  archive_write_free(a);
562  throw std::runtime_error("archive error");
563  }
564 
565  struct archive_entry *entry = archive_entry_new();
566  for (const auto &el : entries) {
567  archive_entry_clear(entry);
568  archive_entry_set_filetype(entry, AE_IFREG);
569  archive_entry_set_perm(entry, S_IRWXU | S_IRWXG | S_IRWXO);
570  archive_entry_set_size(entry, static_cast<ssize_t>(el.second.size()));
571  archive_entry_set_pathname(entry, el.first.c_str());
572  if (archive_write_header(a, entry) != 0) {
573  LOG_ERROR << "archive error: " << archive_error_string(a);
574  archive_entry_free(entry);
575  archive_write_free(a);
576  throw std::runtime_error("archive error");
577  }
578  if (archive_write_data(a, el.second.c_str(), el.second.size()) < 0) {
579  LOG_ERROR << "archive error: " << archive_error_string(a);
580  archive_entry_free(entry);
581  archive_write_free(a);
582  throw std::runtime_error("archive error");
583  }
584  }
585  archive_entry_free(entry);
586  r = archive_write_free(a);
587  if (r != ARCHIVE_OK) {
588  LOG_ERROR << "archive error: " << archive_error_string(a);
589  }
590 }
591 
592 sockaddr_storage Utils::ipGetSockaddr(int fd) {
593  sockaddr_storage ss{};
594  socklen_t len = sizeof(ss);
595  if (getsockname(fd, reinterpret_cast<sockaddr *>(&ss), &len) < 0) {
596  throw std::runtime_error(std::string("Could not get sockaddr: ") + std::strerror(errno));
597  }
598 
599  return ss;
600 }
601 
602 std::string Utils::ipDisplayName(const sockaddr_storage &saddr) {
603  std::array<char, INET6_ADDRSTRLEN> ipstr{};
604 
605  switch (saddr.ss_family) {
606  case AF_INET: {
607  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
608  inet_ntop(AF_INET, &sa->sin_addr, ipstr.data(), ipstr.size());
609  return std::string(ipstr.data());
610  }
611  case AF_INET6: {
612  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
613  inet_ntop(AF_INET6, &sa->sin6_addr, ipstr.data(), ipstr.size());
614  return std::string(ipstr.data());
615  }
616  default:
617  return "unknown";
618  }
619 }
620 
621 int Utils::ipPort(const sockaddr_storage &saddr) {
622  in_port_t p;
623  if (saddr.ss_family == AF_INET) {
624  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
625  p = sa->sin_port;
626  } else if (saddr.ss_family == AF_INET6) {
627  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
628  p = sa->sin6_port;
629  } else {
630  return -1;
631  }
632 
633  return ntohs(p); // NOLINT(readability-isolate-declaration)
634 }
635 
636 int Utils::shell(const std::string &command, std::string *output, bool include_stderr) {
637  std::array<char, 128> buffer{};
638  std::string full_command(command);
639  if (include_stderr) {
640  full_command += " 2>&1";
641  }
642  FILE *pipe = popen(full_command.c_str(), "r");
643  if (pipe == nullptr) {
644  *output = "popen() failed!";
645  return -1;
646  }
647  while (feof(pipe) == 0) {
648  if (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
649  *output += buffer.data();
650  }
651  }
652  int exitcode = pclose(pipe);
653  return WEXITSTATUS(exitcode);
654 }
655 
656 boost::filesystem::path Utils::absolutePath(const boost::filesystem::path &root, const boost::filesystem::path &file) {
657  if (file.is_absolute() || root.empty()) {
658  return file;
659  }
660  return (root / file);
661 }
662 
663 // passing ext argument keep the leading point e.g. '.ext'
664 // results are returned sorted in alpanumeric order
665 std::vector<boost::filesystem::path> Utils::getDirEntriesByExt(const boost::filesystem::path &dir_path,
666  const std::string &ext) {
667  std::vector<boost::filesystem::path> entries;
668  boost::filesystem::directory_iterator entryItEnd;
669  boost::filesystem::directory_iterator entryIt(dir_path);
670  for (; entryIt != entryItEnd; ++entryIt) {
671  const auto &entry_path = entryIt->path();
672  if (!boost::filesystem::is_directory(*entryIt) && entry_path.extension().string() == ext) {
673  entries.push_back(entry_path);
674  }
675  }
676  std::sort(entries.begin(), entries.end());
677  return entries;
678 }
679 
680 void Utils::createDirectories(const boost::filesystem::path &path, mode_t mode) {
681  boost::filesystem::path parent = path.parent_path();
682  if (!parent.empty() && !boost::filesystem::exists(parent)) {
683  Utils::createDirectories(parent, mode);
684  }
685  if (mkdir(path.c_str(), mode) == -1) {
686  throw std::runtime_error(std::string("could not create directory: ").append(path.native()));
687  }
688  std::cout << "created: " << path.native() << "\n";
689 }
690 
691 bool Utils::createSecureDirectory(const boost::filesystem::path &path) {
692  if (mkdir(path.c_str(), S_IRWXU) == 0) {
693  // directory created successfully
694  return true;
695  }
696 
697  // mkdir failed, see if the directory already exists with correct permissions
698  struct stat st {};
699  int ret = stat(path.c_str(), &st);
700  // checks: - stat succeeded
701  // - is a directory
702  // - no read and write permissions for group and others
703  // - owner is current user
704  return (ret >= 0 && ((st.st_mode & S_IFDIR) == S_IFDIR) && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == S_IRWXU &&
705  (st.st_uid == getuid()));
706 }
707 
708 std::string Utils::urlEncode(const std::string &input) {
709  std::string res;
710 
711  for (char c : input) {
712  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
713  c == '_' || c == '~' || c == '/') {
714  res.push_back(c);
715  } else {
716  res.push_back('%');
717  auto nib = static_cast<char>(((c >> 4) & 0x0F));
718  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
719  nib = static_cast<char>(c & 0x0F);
720  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
721  }
722  }
723  return res;
724 }
725 
726 CURL *Utils::curlDupHandleWrapper(CURL *const curl_in, const bool using_pkcs11) {
727  CURL *curl = curl_easy_duphandle(curl_in);
728 
729  // This is a workaround for a bug in curl. It has been fixed in
730  // 75a845d8cfa71688d59d43788c35829b25b6d6af (curl 7.61.1), but that is not
731  // the default in most distributions yet, so we will continue to use the
732  // workaround.
733  if (using_pkcs11) {
734  curlEasySetoptWrapper(curl, CURLOPT_SSLENGINE, "pkcs11");
735  }
736  return curl;
737 }
738 
740  public:
741  SafeTempRoot(const SafeTempRoot &) = delete;
742  SafeTempRoot operator=(const SafeTempRoot &) = delete;
743  // provide this as a static method so that we can use C++ static destructor
744  // to remove the temp root
745  static boost::filesystem::path &Get() {
746  static SafeTempRoot r;
747 
748  return r.path;
749  }
750 
751  private:
752  SafeTempRoot() {
753  boost::filesystem::path prefix = Utils::getStorageRootPath();
754  if (prefix.empty()) {
755  prefix = boost::filesystem::temp_directory_path();
756  }
757  boost::filesystem::path p = prefix / boost::filesystem::unique_path("aktualizr-%%%%-%%%%-%%%%-%%%%");
758  if (mkdir(p.c_str(), S_IRWXU) == -1) {
759  throw std::runtime_error(std::string("could not create temporary directory root: ").append(p.native()));
760  }
761 
762  path = boost::filesystem::path(p);
763  }
764  ~SafeTempRoot() {
765  try {
766  boost::filesystem::remove_all(path);
767  } catch (...) {
768  // ignore this, not critical
769  }
770  }
771 
772  boost::filesystem::path path;
773 };
774 
775 std::string Utils::storage_root_path_;
776 
777 void Utils::setStorageRootPath(const std::string &storage_root_path) { storage_root_path_ = storage_root_path; }
778 
779 boost::filesystem::path Utils::getStorageRootPath() { return storage_root_path_; }
780 
781 void Utils::setUserAgent(std::string user_agent) { user_agent_ = std::move(user_agent); }
782 
783 const char *Utils::getUserAgent() {
784  if (user_agent_.empty()) {
785  user_agent_ = (std::string("Aktualizr/") + aktualizr_version());
786  }
787  return user_agent_.c_str();
788 }
789 
790 std::string Utils::user_agent_;
791 
792 void Utils::setCaPath(boost::filesystem::path path) { ca_path_ = std::move(path); }
793 
794 const char *Utils::getCaPath() { return ca_path_.c_str(); }
795 
796 boost::filesystem::path Utils::ca_path_{"/etc/ssl/certs"};
797 
798 TemporaryFile::TemporaryFile(const std::string &hint)
799  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {}
800 
801 TemporaryFile::~TemporaryFile() { boost::filesystem::remove(tmp_name_); }
802 
803 void TemporaryFile::PutContents(const std::string &contents) const {
804  mode_t mode = S_IRUSR | S_IWUSR;
805  int fd = open(Path().c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
806  if (fd < 0) {
807  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string() + ": " +
808  std::strerror(errno));
809  }
810  ssize_t written = write(fd, contents.c_str(), contents.size());
811  close(fd);
812  if (written < 0 || static_cast<size_t>(written) != contents.size()) {
813  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string());
814  }
815 }
816 
817 boost::filesystem::path TemporaryFile::Path() const { return tmp_name_; }
818 
819 std::string TemporaryFile::PathString() const { return Path().string(); }
820 
821 TemporaryDirectory::TemporaryDirectory(const std::string &hint)
822  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {
823  Utils::createDirectories(tmp_name_, S_IRWXU);
824 }
825 
826 TemporaryDirectory::~TemporaryDirectory() { boost::filesystem::remove_all(tmp_name_); }
827 
828 boost::filesystem::path TemporaryDirectory::Path() const { return tmp_name_; }
829 
830 boost::filesystem::path TemporaryDirectory::operator/(const boost::filesystem::path &subdir) const {
831  return (tmp_name_ / subdir);
832 }
833 
834 std::string TemporaryDirectory::PathString() const { return Path().string(); }
835 
836 void Utils::setSocketPort(sockaddr_storage *addr, in_port_t port) {
837  if (addr->ss_family == AF_INET) {
838  reinterpret_cast<sockaddr_in *>(addr)->sin_port = port;
839  } else if (addr->ss_family == AF_INET6) {
840  reinterpret_cast<sockaddr_in6 *>(addr)->sin6_port = port;
841  }
842 }
843 
844 bool operator<(const sockaddr_storage &left, const sockaddr_storage &right) {
845  if (left.ss_family == AF_INET) {
846  throw std::runtime_error("IPv4 addresses are not supported");
847  }
848  const unsigned char *left_addr = reinterpret_cast<const sockaddr_in6 *>(&left)->sin6_addr.s6_addr; // NOLINT
849  const unsigned char *right_addr = reinterpret_cast<const sockaddr_in6 *>(&right)->sin6_addr.s6_addr; // NOLINT
850  int res = memcmp(left_addr, right_addr, 16);
851 
852  return (res < 0);
853 }
854 
855 Socket::Socket() {
856  socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
857  if (-1 == socket_fd_) {
858  throw std::system_error(errno, std::system_category(), "socket");
859  }
860 }
861 
862 Socket::~Socket() { ::close(socket_fd_); }
863 
864 std::string Socket::toString() const {
865  auto saddr = Utils::ipGetSockaddr(socket_fd_);
866  return Utils::ipDisplayName(saddr) + ":" + std::to_string(Utils::ipPort(saddr));
867 }
868 
869 void Socket::bind(in_port_t port, bool reuse) const {
870  sockaddr_in sa{};
871  memset(&sa, 0, sizeof(sa));
872  sa.sin_family = AF_INET;
873  sa.sin_port = htons(port); // NOLINT(readability-isolate-declaration)
874  sa.sin_addr.s_addr = htonl(INADDR_ANY); // NOLINT(readability-isolate-declaration)
875 
876  int reuseaddr = reuse ? 1 : 0;
877  if (-1 == setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) {
878  throw std::system_error(errno, std::system_category(), "socket");
879  }
880 
881  if (-1 == ::bind(socket_fd_, reinterpret_cast<const sockaddr *>(&sa), sizeof(sa))) {
882  throw std::system_error(errno, std::system_category(), "socket");
883  }
884 }
885 
886 ListenSocket::ListenSocket(in_port_t port) : _port(port) {
887  bind(port);
888  if (_port == 0) {
889  // ephemeral port was bound, find out its real port number
890  auto ephemeral_port = Utils::ipPort(Utils::ipGetSockaddr(socket_fd_));
891  if (-1 != ephemeral_port) {
892  _port = static_cast<in_port_t>(ephemeral_port);
893  }
894  }
895 }
896 
897 ConnectionSocket::ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port)
898  : remote_sock_address_{} {
899  memset(&remote_sock_address_, 0, sizeof(remote_sock_address_));
900  remote_sock_address_.sin_family = AF_INET;
901  if (-1 == inet_pton(AF_INET, ip.c_str(), &(remote_sock_address_.sin_addr))) {
902  throw std::system_error(errno, std::system_category(), "socket");
903  }
904  remote_sock_address_.sin_port = htons(port); // NOLINT(readability-isolate-declaration)
905 
906  if (bind_port > 0) {
907  bind(bind_port);
908  }
909 }
910 
911 ConnectionSocket::~ConnectionSocket() { ::shutdown(socket_fd_, SHUT_RDWR); }
912 
913 int ConnectionSocket::connect() {
914  return ::connect(socket_fd_, reinterpret_cast<const struct sockaddr *>(&remote_sock_address_),
915  sizeof(remote_sock_address_));
916 }
917 
918 CurlEasyWrapper::CurlEasyWrapper() {
919  handle = curl_easy_init();
920  if (handle == nullptr) {
921  throw std::runtime_error("Could not initialize curl handle");
922  }
923  curlEasySetoptWrapper(handle, CURLOPT_USERAGENT, Utils::getUserAgent());
924 }
925 
926 CurlEasyWrapper::~CurlEasyWrapper() {
927  if (handle != nullptr) {
928  curl_easy_cleanup(handle);
929  }
930 }
Results of libaktualizr API calls.
Definition: results.h:13