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  StructGuardInt<struct archive> a(archive_read_new(), archive_read_free);
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.get());
484  archive_read_support_format_all(a.get());
485  auto state = std_::make_unique<archive_state>(std::ref(as));
486  int r = archive_read_open(a.get(), reinterpret_cast<void *>(state.get()), nullptr, read_cb, nullptr);
487  if (r != ARCHIVE_OK) {
488  LOG_ERROR << "archive error: " << archive_error_string(a.get());
489  throw std::runtime_error("archive error");
490  }
491 
492  bool found = false;
493  std::stringstream out_stream;
494  struct archive_entry *entry;
495  while (archive_read_next_header(a.get(), &entry) == ARCHIVE_OK) {
496  if (filename != archive_entry_pathname(entry)) {
497  archive_read_data_skip(a.get());
498  continue;
499  }
500 
501  const char *buff;
502  size_t size;
503  int64_t offset;
504 
505  for (;;) {
506  r = archive_read_data_block(a.get(), reinterpret_cast<const void **>(&buff), &size, &offset);
507  if (r == ARCHIVE_EOF) {
508  found = true;
509  break;
510  } else if (r != ARCHIVE_OK) {
511  LOG_ERROR << "archive error: " << archive_error_string(a.get());
512  break;
513  }
514  if (size > 0 && buff != nullptr) {
515  out_stream.write(buff, static_cast<ssize_t>(size));
516  }
517  }
518  }
519 
520  r = archive_read_close(a.get());
521  if (r != ARCHIVE_OK) {
522  LOG_ERROR << "archive error: " << archive_error_string(a.get());
523  }
524 
525  if (!found) {
526  throw std::runtime_error("could not extract " + filename + " from archive");
527  }
528 
529  std::string result = out_stream.str();
530  if (trim) {
531  boost::trim_if(result, boost::is_any_of(" \t\r\n"));
532  }
533  return result;
534 }
535 
536 static ssize_t write_cb(struct archive *a, void *client_data, const void *buffer, size_t length) {
537  auto *s = reinterpret_cast<std::ostream *>(client_data);
538  s->write(reinterpret_cast<const char *>(buffer), static_cast<ssize_t>(length));
539  if (s->fail()) {
540  archive_set_error(a, -1, "unable to write in stream");
541  return -1;
542  }
543 
544  return static_cast<ssize_t>(length);
545 }
546 
547 void Utils::writeArchive(const std::map<std::string, std::string> &entries, std::ostream &as) {
548  StructGuardInt<struct archive> a(archive_write_new(), archive_write_free);
549  if (a == nullptr) {
550  LOG_ERROR << "archive error: could not initialize archive object";
551  throw std::runtime_error("archive error");
552  }
553  archive_write_set_format_pax(a.get());
554  archive_write_add_filter_gzip(a.get());
555 
556  int r = archive_write_open(a.get(), reinterpret_cast<void *>(&as), nullptr, write_cb, nullptr);
557  if (r != ARCHIVE_OK) {
558  LOG_ERROR << "archive error: " << archive_error_string(a.get());
559  throw std::runtime_error("archive error");
560  }
561 
562  StructGuard<struct archive_entry> entry(archive_entry_new(), archive_entry_free);
563  for (const auto &el : entries) {
564  archive_entry_clear(entry.get());
565  archive_entry_set_filetype(entry.get(), AE_IFREG);
566  archive_entry_set_perm(entry.get(), S_IRWXU | S_IRWXG | S_IRWXO);
567  archive_entry_set_size(entry.get(), static_cast<ssize_t>(el.second.size()));
568  archive_entry_set_pathname(entry.get(), el.first.c_str());
569  if (archive_write_header(a.get(), entry.get()) != 0) {
570  LOG_ERROR << "archive error: " << archive_error_string(a.get());
571  throw std::runtime_error("archive error");
572  }
573  if (archive_write_data(a.get(), el.second.c_str(), el.second.size()) < 0) {
574  LOG_ERROR << "archive error: " << archive_error_string(a.get());
575  throw std::runtime_error("archive error");
576  }
577  }
578  r = archive_write_close(a.get());
579  if (r != ARCHIVE_OK) {
580  LOG_ERROR << "archive error: " << archive_error_string(a.get());
581  }
582 }
583 
584 /* Removing a file from an archive isn't possible in the obvious sense. The only
585  * way to do so in practice is to create a new archive, copy everything you
586  * _don't_ want to remove, and then replace the old archive with the new one.
587  */
588 void Utils::removeFileFromArchive(const boost::filesystem::path &archive_path, const std::string &filename) {
589  std::ifstream as_in(archive_path.c_str(), std::ios::in | std::ios::binary);
590  if (as_in.fail()) {
591  LOG_ERROR << "Unable to open provided provisioning archive " << archive_path << ": " << std::strerror(errno);
592  throw std::runtime_error("Unable to parse provisioning credentials");
593  }
594  const boost::filesystem::path outfile = archive_path.string() + "-" + boost::filesystem::unique_path().string();
595  std::ofstream as_out(outfile.c_str(), std::ios::out | std::ios::binary);
596  if (as_out.fail()) {
597  LOG_ERROR << "Unable to create file " << outfile << ": " << std::strerror(errno);
598  throw std::runtime_error("Unable to parse provisioning credentials");
599  }
600 
601  StructGuardInt<struct archive> a_in(archive_read_new(), archive_read_free);
602  if (a_in == nullptr) {
603  LOG_ERROR << "archive error: could not initialize archive object";
604  throw std::runtime_error("archive error");
605  }
606  archive_read_support_filter_all(a_in.get());
607  archive_read_support_format_all(a_in.get());
608  auto state = std_::make_unique<archive_state>(std::ref(as_in));
609  int r = archive_read_open(a_in.get(), reinterpret_cast<void *>(state.get()), nullptr, read_cb, nullptr);
610  if (r != ARCHIVE_OK) {
611  LOG_ERROR << "archive error: " << archive_error_string(a_in.get());
612  throw std::runtime_error("archive error");
613  }
614 
615  StructGuardInt<struct archive> a_out(archive_write_new(), archive_write_free);
616  if (a_out == nullptr) {
617  LOG_ERROR << "archive error: could not initialize archive object";
618  throw std::runtime_error("archive error");
619  }
620  archive_write_set_format_zip(a_out.get());
621  r = archive_write_open(a_out.get(), reinterpret_cast<void *>(&as_out), nullptr, write_cb, nullptr);
622  if (r != ARCHIVE_OK) {
623  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
624  throw std::runtime_error("archive error");
625  }
626 
627  bool found = false;
628  struct archive_entry *entry_in;
629  while (archive_read_next_header(a_in.get(), &entry_in) == ARCHIVE_OK) {
630  const char *entry_name = archive_entry_pathname(entry_in);
631  if (filename == entry_name) {
632  archive_read_data_skip(a_in.get());
633  found = true;
634  continue;
635  }
636 
637  StructGuard<struct archive_entry> entry_out(archive_entry_new(), archive_entry_free);
638  const struct stat *entry_stat = archive_entry_stat(entry_in);
639  archive_entry_copy_stat(entry_out.get(), entry_stat);
640  archive_entry_set_pathname(entry_out.get(), entry_name);
641  if (archive_write_header(a_out.get(), entry_out.get()) != 0) {
642  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
643  throw std::runtime_error("archive error");
644  }
645 
646  const char *buff;
647  size_t size;
648  int64_t offset;
649 
650  for (;;) {
651  r = archive_read_data_block(a_in.get(), reinterpret_cast<const void **>(&buff), &size, &offset);
652  if (r == ARCHIVE_EOF) {
653  break;
654  } else if (r != ARCHIVE_OK) {
655  LOG_ERROR << "archive error: " << archive_error_string(a_in.get());
656  break;
657  }
658  if (size > 0 && buff != nullptr) {
659  if (archive_write_data(a_out.get(), buff, size) < 0) {
660  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
661  throw std::runtime_error("archive error");
662  }
663  }
664  }
665  }
666 
667  r = archive_read_close(a_in.get());
668  if (r != ARCHIVE_OK) {
669  LOG_ERROR << "archive error: " << archive_error_string(a_in.get());
670  }
671 
672  r = archive_write_close(a_out.get());
673  if (r != ARCHIVE_OK) {
674  LOG_ERROR << "archive error: " << archive_error_string(a_out.get());
675  }
676 
677  if (found) {
678  boost::filesystem::rename(outfile, archive_path);
679  } else {
680  boost::filesystem::remove(outfile);
681  throw std::runtime_error("Requested file not found in archive!");
682  }
683 }
684 
685 sockaddr_storage Utils::ipGetSockaddr(int fd) {
686  sockaddr_storage ss{};
687  socklen_t len = sizeof(ss);
688  if (getsockname(fd, reinterpret_cast<sockaddr *>(&ss), &len) < 0) {
689  throw std::runtime_error(std::string("Could not get sockaddr: ") + std::strerror(errno));
690  }
691 
692  return ss;
693 }
694 
695 std::string Utils::ipDisplayName(const sockaddr_storage &saddr) {
696  std::array<char, INET6_ADDRSTRLEN> ipstr{};
697 
698  switch (saddr.ss_family) {
699  case AF_INET: {
700  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
701  inet_ntop(AF_INET, &sa->sin_addr, ipstr.data(), ipstr.size());
702  return std::string(ipstr.data());
703  }
704  case AF_INET6: {
705  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
706  inet_ntop(AF_INET6, &sa->sin6_addr, ipstr.data(), ipstr.size());
707  return std::string(ipstr.data());
708  }
709  default:
710  return "unknown";
711  }
712 }
713 
714 int Utils::ipPort(const sockaddr_storage &saddr) {
715  in_port_t p;
716  if (saddr.ss_family == AF_INET) {
717  const auto *sa = reinterpret_cast<const sockaddr_in *>(&saddr);
718  p = sa->sin_port;
719  } else if (saddr.ss_family == AF_INET6) {
720  const auto *sa = reinterpret_cast<const sockaddr_in6 *>(&saddr);
721  p = sa->sin6_port;
722  } else {
723  return -1;
724  }
725 
726  return ntohs(p); // NOLINT(readability-isolate-declaration)
727 }
728 
729 int Utils::shell(const std::string &command, std::string *output, bool include_stderr) {
730  std::array<char, 128> buffer{};
731  std::string full_command(command);
732  if (include_stderr) {
733  full_command += " 2>&1";
734  }
735  FILE *pipe = popen(full_command.c_str(), "r");
736  if (pipe == nullptr) {
737  *output = "popen() failed!";
738  return -1;
739  }
740  while (feof(pipe) == 0) {
741  if (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
742  *output += buffer.data();
743  }
744  }
745  int exitcode = pclose(pipe);
746  return WEXITSTATUS(exitcode);
747 }
748 
749 boost::filesystem::path Utils::absolutePath(const boost::filesystem::path &root, const boost::filesystem::path &file) {
750  if (file.is_absolute() || root.empty()) {
751  return file;
752  }
753  return (root / file);
754 }
755 
756 // passing ext argument keep the leading point e.g. '.ext'
757 // results are returned sorted in alpanumeric order
758 std::vector<boost::filesystem::path> Utils::getDirEntriesByExt(const boost::filesystem::path &dir_path,
759  const std::string &ext) {
760  std::vector<boost::filesystem::path> entries;
761  boost::filesystem::directory_iterator entryItEnd;
762  boost::filesystem::directory_iterator entryIt(dir_path);
763  for (; entryIt != entryItEnd; ++entryIt) {
764  const auto &entry_path = entryIt->path();
765  if (!boost::filesystem::is_directory(*entryIt) && entry_path.extension().string() == ext) {
766  entries.push_back(entry_path);
767  }
768  }
769  std::sort(entries.begin(), entries.end());
770  return entries;
771 }
772 
773 void Utils::createDirectories(const boost::filesystem::path &path, mode_t mode) {
774  boost::filesystem::path parent = path.parent_path();
775  if (!parent.empty() && !boost::filesystem::exists(parent)) {
776  Utils::createDirectories(parent, mode);
777  }
778  if (mkdir(path.c_str(), mode) == -1) {
779  throw std::runtime_error(std::string("could not create directory: ").append(path.native()));
780  }
781  std::cout << "created: " << path.native() << "\n";
782 }
783 
784 bool Utils::createSecureDirectory(const boost::filesystem::path &path) {
785  if (mkdir(path.c_str(), S_IRWXU) == 0) {
786  // directory created successfully
787  return true;
788  }
789 
790  // mkdir failed, see if the directory already exists with correct permissions
791  struct stat st {};
792  int ret = stat(path.c_str(), &st);
793  // checks: - stat succeeded
794  // - is a directory
795  // - no read and write permissions for group and others
796  // - owner is current user
797  return (ret >= 0 && ((st.st_mode & S_IFDIR) == S_IFDIR) && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == S_IRWXU &&
798  (st.st_uid == getuid()));
799 }
800 
801 std::string Utils::urlEncode(const std::string &input) {
802  std::string res;
803 
804  for (char c : input) {
805  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' ||
806  c == '_' || c == '~' || c == '/') {
807  res.push_back(c);
808  } else {
809  res.push_back('%');
810  auto nib = static_cast<char>(((c >> 4) & 0x0F));
811  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
812  nib = static_cast<char>(c & 0x0F);
813  res.push_back(static_cast<char>((nib < 10) ? nib + '0' : nib - 10 + 'A'));
814  }
815  }
816  return res;
817 }
818 
819 CURL *Utils::curlDupHandleWrapper(CURL *const curl_in, const bool using_pkcs11) {
820  CURL *curl = curl_easy_duphandle(curl_in);
821 
822  // This is a workaround for a bug in curl. It has been fixed in
823  // 75a845d8cfa71688d59d43788c35829b25b6d6af (curl 7.61.1), but that is not
824  // the default in most distributions yet, so we will continue to use the
825  // workaround.
826  if (using_pkcs11) {
827  curlEasySetoptWrapper(curl, CURLOPT_SSLENGINE, "pkcs11");
828  }
829  return curl;
830 }
831 
833  public:
834  SafeTempRoot(const SafeTempRoot &) = delete;
835  SafeTempRoot operator=(const SafeTempRoot &) = delete;
836  // provide this as a static method so that we can use C++ static destructor
837  // to remove the temp root
838  static boost::filesystem::path &Get() {
839  static SafeTempRoot r;
840 
841  return r.path;
842  }
843 
844  private:
845  SafeTempRoot() {
846  boost::filesystem::path prefix = Utils::getStorageRootPath();
847  if (prefix.empty()) {
848  prefix = boost::filesystem::temp_directory_path();
849  }
850  boost::filesystem::path p = prefix / boost::filesystem::unique_path("aktualizr-%%%%-%%%%-%%%%-%%%%");
851  if (mkdir(p.c_str(), S_IRWXU) == -1) {
852  throw std::runtime_error(std::string("could not create temporary directory root: ").append(p.native()));
853  }
854 
855  path = boost::filesystem::path(p);
856  }
857  ~SafeTempRoot() {
858  try {
859  boost::filesystem::remove_all(path);
860  } catch (...) {
861  // ignore this, not critical
862  }
863  }
864 
865  boost::filesystem::path path;
866 };
867 
868 std::string Utils::storage_root_path_;
869 
870 void Utils::setStorageRootPath(const std::string &storage_root_path) { storage_root_path_ = storage_root_path; }
871 
872 boost::filesystem::path Utils::getStorageRootPath() { return storage_root_path_; }
873 
874 void Utils::setUserAgent(std::string user_agent) { user_agent_ = std::move(user_agent); }
875 
876 const char *Utils::getUserAgent() {
877  if (user_agent_.empty()) {
878  user_agent_ = (std::string("Aktualizr/") + aktualizr_version());
879  }
880  return user_agent_.c_str();
881 }
882 
883 std::string Utils::user_agent_;
884 
885 void Utils::setCaPath(boost::filesystem::path path) { ca_path_ = std::move(path); }
886 
887 const char *Utils::getCaPath() { return ca_path_.c_str(); }
888 
889 boost::filesystem::path Utils::ca_path_{"/etc/ssl/certs"};
890 
891 TemporaryFile::TemporaryFile(const std::string &hint)
892  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {}
893 
894 TemporaryFile::~TemporaryFile() { boost::filesystem::remove(tmp_name_); }
895 
896 void TemporaryFile::PutContents(const std::string &contents) const {
897  mode_t mode = S_IRUSR | S_IWUSR;
898  int fd = open(Path().c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode);
899  if (fd < 0) {
900  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string() + ": " +
901  std::strerror(errno));
902  }
903  ssize_t written = write(fd, contents.c_str(), contents.size());
904  close(fd);
905  if (written < 0 || static_cast<size_t>(written) != contents.size()) {
906  throw std::runtime_error(std::string("Could not write content to file: ") + Path().string());
907  }
908 }
909 
910 boost::filesystem::path TemporaryFile::Path() const { return tmp_name_; }
911 
912 std::string TemporaryFile::PathString() const { return Path().string(); }
913 
914 TemporaryDirectory::TemporaryDirectory(const std::string &hint)
915  : tmp_name_(SafeTempRoot::Get() / boost::filesystem::unique_path(std::string("%%%%-%%%%-").append(hint))) {
916  Utils::createDirectories(tmp_name_, S_IRWXU);
917 }
918 
919 TemporaryDirectory::~TemporaryDirectory() { boost::filesystem::remove_all(tmp_name_); }
920 
921 boost::filesystem::path TemporaryDirectory::Path() const { return tmp_name_; }
922 
923 boost::filesystem::path TemporaryDirectory::operator/(const boost::filesystem::path &subdir) const {
924  return (tmp_name_ / subdir);
925 }
926 
927 std::string TemporaryDirectory::PathString() const { return Path().string(); }
928 
929 Socket::Socket() {
930  socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
931  if (-1 == socket_fd_) {
932  throw std::system_error(errno, std::system_category(), "socket");
933  }
934 }
935 
936 Socket::~Socket() { ::close(socket_fd_); }
937 
938 std::string Socket::toString() const {
939  auto saddr = Utils::ipGetSockaddr(socket_fd_);
940  return Utils::ipDisplayName(saddr) + ":" + std::to_string(Utils::ipPort(saddr));
941 }
942 
943 void Socket::bind(in_port_t port, bool reuse) const {
944  sockaddr_in sa{};
945  memset(&sa, 0, sizeof(sa));
946  sa.sin_family = AF_INET;
947  sa.sin_port = htons(port); // NOLINT(readability-isolate-declaration)
948  sa.sin_addr.s_addr = htonl(INADDR_ANY); // NOLINT(readability-isolate-declaration)
949 
950  int reuseaddr = reuse ? 1 : 0;
951  if (-1 == setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) {
952  throw std::system_error(errno, std::system_category(), "socket");
953  }
954 
955  if (-1 == ::bind(socket_fd_, reinterpret_cast<const sockaddr *>(&sa), sizeof(sa))) {
956  throw std::system_error(errno, std::system_category(), "socket");
957  }
958 }
959 
960 ListenSocket::ListenSocket(in_port_t port) : _port(port) {
961  bind(port);
962  if (_port == 0) {
963  // ephemeral port was bound, find out its real port number
964  auto ephemeral_port = Utils::ipPort(Utils::ipGetSockaddr(socket_fd_));
965  if (-1 != ephemeral_port) {
966  _port = static_cast<in_port_t>(ephemeral_port);
967  }
968  }
969 }
970 
971 ConnectionSocket::ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port)
972  : remote_sock_address_{} {
973  memset(&remote_sock_address_, 0, sizeof(remote_sock_address_));
974  remote_sock_address_.sin_family = AF_INET;
975  if (-1 == inet_pton(AF_INET, ip.c_str(), &(remote_sock_address_.sin_addr))) {
976  throw std::system_error(errno, std::system_category(), "socket");
977  }
978  remote_sock_address_.sin_port = htons(port); // NOLINT(readability-isolate-declaration)
979 
980  if (bind_port > 0) {
981  bind(bind_port);
982  }
983 }
984 
985 ConnectionSocket::~ConnectionSocket() { ::shutdown(socket_fd_, SHUT_RDWR); }
986 
987 int ConnectionSocket::connect() {
988  return ::connect(socket_fd_, reinterpret_cast<const struct sockaddr *>(&remote_sock_address_),
989  sizeof(remote_sock_address_));
990 }
991 
992 CurlEasyWrapper::CurlEasyWrapper() {
993  handle = curl_easy_init();
994  if (handle == nullptr) {
995  throw std::runtime_error("Could not initialize curl handle");
996  }
997  curlEasySetoptWrapper(handle, CURLOPT_USERAGENT, Utils::getUserAgent());
998 }
999 
1000 CurlEasyWrapper::~CurlEasyWrapper() {
1001  if (handle != nullptr) {
1002  curl_easy_cleanup(handle);
1003  }
1004 }
Results of libaktualizr API calls.
Definition: results.h:12