apt  2.2.4
About: Apt (Advanced Package Tool) is a management system for software packages (Debian/Ubuntu). Release series 2.2.
  Fossies Dox: apt-2.2.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

private-json-hooks.cc
Go to the documentation of this file.
1 /*
2  * private-json-hooks.cc - 2nd generation, JSON-RPC, hooks for APT
3  *
4  * Copyright (c) 2018 Canonical Ltd
5  *
6  * SPDX-License-Identifier: GPL-2.0+
7  */
8 
9 #include <apt-pkg/debsystem.h>
10 #include <apt-pkg/fileutl.h>
11 #include <apt-pkg/macros.h>
12 #include <apt-pkg/strutl.h>
15 
16 #include <iomanip>
17 #include <ostream>
18 #include <sstream>
19 #include <stack>
20 
21 #include <signal.h>
22 #include <sys/socket.h>
23 #include <sys/types.h>
24 
25 /**
26  * @brief Simple JSON writer
27  *
28  * This performs no error checking, so be careful.
29  */
31 {
32  std::ostream &os;
33  std::locale old_locale;
34 
36  {
42  in_object_val
43  } state = empty;
44 
45  std::stack<write_state> old_states;
46 
47  void maybeComma()
48  {
49  switch (state)
50  {
51  case empty:
52  break;
53  case in_object_val:
54  state = in_object_key;
55  break;
56  case in_object_key:
57  state = in_object_val;
58  os << ',';
59  break;
60  case in_array:
61  os << ',';
62  break;
63  case in_array_first_element:
64  state = in_array;
65  break;
66  case in_object_first_key:
67  state = in_object_val;
68  break;
69  default:
70  abort();
71  }
72  }
73 
74  void pushState(write_state state)
75  {
76  old_states.push(this->state);
77  this->state = state;
78  }
79 
80  void popState()
81  {
82  this->state = old_states.top();
83  old_states.pop();
84  }
85 
86  public:
87  explicit JsonWriter(std::ostream &os) : os(os), old_locale{os.imbue(std::locale::classic())} {}
88  ~JsonWriter() { os.imbue(old_locale); }
90  {
91  maybeComma();
92  pushState(in_array_first_element);
93  os << '[';
94  return *this;
95  }
97  {
98  popState();
99  os << ']';
100  return *this;
101  }
103  {
104  maybeComma();
105  pushState(in_object_first_key);
106  os << '{';
107  return *this;
108  }
110  {
111  popState();
112  os << '}';
113  return *this;
114  }
115  std::ostream &encodeString(std::ostream &out, std::string const &str)
116  {
117  out << '"';
118 
119  for (std::string::const_iterator c = str.begin(); c != str.end(); c++)
120  {
121  if (*c <= 0x1F || *c == '"' || *c == '\\')
122  ioprintf(out, "\\u%04X", *c);
123  else
124  out << *c;
125  }
126 
127  out << '"';
128  return out;
129  }
130  JsonWriter &name(std::string const &name)
131  {
132  maybeComma();
133  encodeString(os, name) << ':';
134  return *this;
135  }
136  JsonWriter &value(std::string const &value)
137  {
138  maybeComma();
139  encodeString(os, value);
140  return *this;
141  }
142  JsonWriter &value(const char *value)
143  {
144  maybeComma();
145  if (value == nullptr)
146  os << "null";
147  else
148  encodeString(os, value);
149  return *this;
150  }
151  JsonWriter &value(int value)
152  {
153  maybeComma();
154  os << value;
155  return *this;
156  }
157  JsonWriter &value(long value)
158  {
159  maybeComma();
160  os << value;
161  return *this;
162  }
163  JsonWriter &value(long long value)
164  {
165  maybeComma();
166  os << value;
167  return *this;
168  }
169  JsonWriter &value(unsigned long long value)
170  {
171  maybeComma();
172  os << value;
173  return *this;
174  }
175  JsonWriter &value(unsigned long value)
176  {
177  maybeComma();
178  os << value;
179  return *this;
180  }
181  JsonWriter &value(unsigned int value)
182  {
183  maybeComma();
184  os << value;
185  return *this;
186  }
187  JsonWriter &value(bool value)
188  {
189  maybeComma();
190  os << (value ? "true" : "false");
191  return *this;
192  }
193  JsonWriter &value(double value)
194  {
195  maybeComma();
196  os << value;
197  return *this;
198  }
199 };
200 
201 /**
202  * @brief Write a VerIterator to a JsonWriter
203  */
204 static void verIterToJson(JsonWriter &writer, CacheFile &Cache, pkgCache::VerIterator const &Ver)
205 {
206  writer.beginObject();
207  writer.name("id").value(Ver->ID);
208  writer.name("version").value(Ver.VerStr());
209  writer.name("architecture").value(Ver.Arch());
210  writer.name("pin").value(Cache->GetPolicy().GetPriority(Ver));
211  writer.endObject();
212 }
213 
214 /**
215  * @brief Copy of debSystem::DpkgChrootDirectory()
216  * @todo Remove
217  */
218 static void DpkgChrootDirectory()
219 {
220  std::string const chrootDir = _config->FindDir("DPkg::Chroot-Directory");
221  if (chrootDir == "/")
222  return;
223  std::cerr << "Chrooting into " << chrootDir << std::endl;
224  if (chroot(chrootDir.c_str()) != 0)
225  _exit(100);
226  if (chdir("/") != 0)
227  _exit(100);
228 }
229 
230 /**
231  * @brief Send a notification to the hook's stream
232  */
233 static void NotifyHook(std::ostream &os, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages)
234 {
235  SortedPackageUniverse Universe(Cache);
236  JsonWriter jsonWriter{os};
237 
238  jsonWriter.beginObject();
239 
240  jsonWriter.name("jsonrpc").value("2.0");
241  jsonWriter.name("method").value(method);
242 
243  /* Build params */
244  jsonWriter.name("params").beginObject();
245  jsonWriter.name("command").value(FileList[0]);
246  jsonWriter.name("search-terms").beginArray();
247  for (int i = 1; FileList[i] != NULL; i++)
248  jsonWriter.value(FileList[i]);
249  jsonWriter.endArray();
250  jsonWriter.name("unknown-packages").beginArray();
251  for (auto const &PkgName : UnknownPackages)
252  jsonWriter.value(PkgName);
253  jsonWriter.endArray();
254 
255  jsonWriter.name("packages").beginArray();
256  for (auto const &Pkg : Universe)
257  {
258  switch (Cache[Pkg].Mode)
259  {
262  break;
263  default:
264  continue;
265  }
266 
267  jsonWriter.beginObject();
268 
269  jsonWriter.name("id").value(Pkg->ID);
270  jsonWriter.name("name").value(Pkg.Name());
271  jsonWriter.name("architecture").value(Pkg.Arch());
272 
273  switch (Cache[Pkg].Mode)
274  {
276  jsonWriter.name("mode").value("install");
277  break;
279  jsonWriter.name("mode").value(Cache[Pkg].Purge() ? "purge" : "deinstall");
280  break;
281  default:
282  continue;
283  }
284  jsonWriter.name("automatic").value(bool(Cache[Pkg].Flags & pkgCache::Flag::Auto));
285 
286  jsonWriter.name("versions").beginObject();
287 
288  if (Cache[Pkg].CandidateVer != nullptr)
289  verIterToJson(jsonWriter.name("candidate"), Cache, Cache[Pkg].CandidateVerIter(Cache));
290  if (Cache[Pkg].InstallVer != nullptr)
291  verIterToJson(jsonWriter.name("install"), Cache, Cache[Pkg].InstVerIter(Cache));
292  if (Pkg->CurrentVer != 0)
293  verIterToJson(jsonWriter.name("current"), Cache, Pkg.CurrentVer());
294 
295  jsonWriter.endObject();
296 
297  jsonWriter.endObject();
298  }
299 
300  jsonWriter.endArray(); // packages
301  jsonWriter.endObject(); // params
302  jsonWriter.endObject(); // main
303 }
304 
305 /// @brief Build the hello handshake message for 0.1 protocol
306 static std::string BuildHelloMessage()
307 {
308  std::stringstream Hello;
309  JsonWriter(Hello).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.hello").name("id").value(0).name("params").beginObject().name("versions").beginArray().value("0.1").endArray().endObject().endObject();
310 
311  return Hello.str();
312 }
313 
314 /// @brief Build the bye notification for 0.1 protocol
315 static std::string BuildByeMessage()
316 {
317  std::stringstream Bye;
318  JsonWriter(Bye).beginObject().name("jsonrpc").value("2.0").name("method").value("org.debian.apt.hooks.bye").name("params").beginObject().endObject().endObject();
319 
320  return Bye.str();
321 }
322 
323 /// @brief Run the Json hook processes in the given option.
324 bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set<std::string> const &UnknownPackages)
325 {
326  std::stringstream ss;
327  NotifyHook(ss, method, FileList, Cache, UnknownPackages);
328  std::string TheData = ss.str();
329  std::string HelloData = BuildHelloMessage();
330  std::string ByeData = BuildByeMessage();
331 
332  bool result = true;
333 
334  Configuration::Item const *Opts = _config->Tree(option.c_str());
335  if (Opts == 0 || Opts->Child == 0)
336  return true;
337  Opts = Opts->Child;
338 
339  // Flush output before calling hooks
340  std::clog.flush();
341  std::cerr.flush();
342  std::cout.flush();
343  c2out.flush();
344  c1out.flush();
345 
346  sighandler_t old_sigpipe = signal(SIGPIPE, SIG_IGN);
347  sighandler_t old_sigint = signal(SIGINT, SIG_IGN);
348  sighandler_t old_sigquit = signal(SIGQUIT, SIG_IGN);
349 
350  unsigned int Count = 1;
351  for (; Opts != 0; Opts = Opts->Next, Count++)
352  {
353  if (Opts->Value.empty() == true)
354  continue;
355 
356  if (_config->FindB("Debug::RunScripts", false) == true)
357  std::clog << "Running external script with list of all .deb file: '"
358  << Opts->Value << "'" << std::endl;
359 
360  // Create the pipes
361  std::set<int> KeepFDs;
363  int Pipes[2];
364  if (socketpair(AF_UNIX, SOCK_STREAM, 0, Pipes) != 0)
365  {
366  result = _error->Errno("pipe", "Failed to create IPC pipe to subprocess");
367  break;
368  }
369 
370  int InfoFD = 3;
371 
372  if (InfoFD != Pipes[0])
373  SetCloseExec(Pipes[0], true);
374  else
375  KeepFDs.insert(Pipes[0]);
376 
377  SetCloseExec(Pipes[1], true);
378 
379  // Purified Fork for running the script
380  pid_t Process = ExecFork(KeepFDs);
381  if (Process == 0)
382  {
383  // Setup the FDs
384  dup2(Pipes[0], InfoFD);
385  SetCloseExec(STDOUT_FILENO, false);
386  SetCloseExec(STDIN_FILENO, false);
387  SetCloseExec(STDERR_FILENO, false);
388 
389  std::string hookfd;
390  strprintf(hookfd, "%d", InfoFD);
391  setenv("APT_HOOK_SOCKET", hookfd.c_str(), 1);
392 
394  const char *Args[4];
395  Args[0] = "/bin/sh";
396  Args[1] = "-c";
397  Args[2] = Opts->Value.c_str();
398  Args[3] = 0;
399  execv(Args[0], (char **)Args);
400  _exit(100);
401  }
402  close(Pipes[0]);
403  FILE *F = fdopen(Pipes[1], "w+");
404  if (F == 0)
405  {
406  result = _error->Errno("fdopen", "Failed to open new FD");
407  break;
408  }
409 
410  fwrite(HelloData.data(), HelloData.size(), 1, F);
411  fwrite("\n\n", 2, 1, F);
412  fflush(F);
413 
414  char *line = nullptr;
415  size_t linesize = 0;
416  ssize_t size = getline(&line, &linesize, F);
417 
418  if (size < 0)
419  {
420  if (errno != ECONNRESET && errno != EPIPE)
421  _error->Error("Could not read response to hello message from hook %s: %s", Opts->Value.c_str(), strerror(errno));
422  goto out;
423  }
424  else if (strstr(line, "error") != nullptr)
425  {
426  _error->Error("Hook %s reported an error during hello: %s", Opts->Value.c_str(), line);
427  goto out;
428  }
429 
430  size = getline(&line, &linesize, F);
431  if (size < 0)
432  {
433  _error->Error("Could not read message separator line after handshake from %s: %s", Opts->Value.c_str(), feof(F) ? "end of file" : strerror(errno));
434  goto out;
435  }
436  else if (size == 0 || line[0] != '\n')
437  {
438  _error->Error("Expected empty line after handshake from %s, received %s", Opts->Value.c_str(), line);
439  goto out;
440  }
441 
442  fwrite(TheData.data(), TheData.size(), 1, F);
443  fwrite("\n\n", 2, 1, F);
444 
445  fwrite(ByeData.data(), ByeData.size(), 1, F);
446  fwrite("\n\n", 2, 1, F);
447  out:
448  fclose(F);
449  // Clean up the sub process
450  if (ExecWait(Process, Opts->Value.c_str()) == false)
451  {
452  result = _error->Error("Failure running hook %s", Opts->Value.c_str());
453  break;
454  }
455  }
456  signal(SIGINT, old_sigint);
457  signal(SIGPIPE, old_sigpipe);
458  signal(SIGQUIT, old_sigquit);
459 
460  return result;
461 }
strprintf(m, msg, repo.c_str())
const Item * Tree(const char *Name) const
bool FindB(const char *Name, bool const &Default=false) const
std::string FindDir(const char *Name, const char *Default=0) const
Simple JSON writer.
JsonWriter & beginObject()
JsonWriter & value(bool value)
JsonWriter & beginArray()
std::ostream & os
std::locale old_locale
JsonWriter(std::ostream &os)
JsonWriter & value(unsigned int value)
JsonWriter & value(unsigned long long value)
std::ostream & encodeString(std::ostream &out, std::string const &str)
JsonWriter & name(std::string const &name)
JsonWriter & endObject()
JsonWriter & value(const char *value)
JsonWriter & value(double value)
void pushState(write_state state)
std::stack< write_state > old_states
JsonWriter & value(long long value)
JsonWriter & value(std::string const &value)
JsonWriter & value(long value)
JsonWriter & value(unsigned long value)
JsonWriter & value(int value)
JsonWriter & endArray()
pkgPolicy * GetPolicy()
Definition: cachefile.h:75
virtual signed short GetPriority(pkgCache::VerIterator const &Ver, bool ConsiderFiles=true) APT_OVERRIDE
Definition: policy.cc:311
FILE
Configuration * _config
void SetCloseExec(int Fd, bool Close)
Definition: fileutl.cc:792
bool ExecWait(pid_t Pid, const char *Name, bool Reap)
Definition: fileutl.cc:942
void MergeKeepFdsFromConfiguration(std::set< int > &KeepFDs)
Definition: fileutl.cc:860
pid_t ExecFork()
Definition: fileutl.cc:881
#define APT_HIDDEN
Definition: macros.h:78
static std::string BuildHelloMessage()
Build the hello handshake message for 0.1 protocol.
static void verIterToJson(JsonWriter &writer, CacheFile &Cache, pkgCache::VerIterator const &Ver)
Write a VerIterator to a JsonWriter.
static void DpkgChrootDirectory()
Copy of debSystem::DpkgChrootDirectory()
bool RunJsonHook(std::string const &option, std::string const &method, const char **FileList, CacheFile &Cache, std::set< std::string > const &UnknownPackages)
Run the Json hook processes in the given option.
static void NotifyHook(std::ostream &os, std::string const &method, const char **FileList, CacheFile &Cache, std::set< std::string > const &UnknownPackages)
Send a notification to the hook's stream.
static std::string BuildByeMessage()
Build the bye notification for 0.1 protocol.
APT_PUBLIC std::ostream c1out
APT_PUBLIC std::ostream c2out
void ioprintf(ostream &out, const char *format,...)
Definition: strutl.cc:1433