"Fossies" - the Fresh Open Source Software Archive

Member "apt-2.2.4/apt-private/private-json-hooks.cc" (10 Jun 2021, 11442 Bytes) of package /linux/misc/apt-2.2.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code 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>
   13 #include <apt-private/private-json-hooks.h>
   14 #include <apt-private/private-output.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  */
   30 class APT_HIDDEN JsonWriter
   31 {
   32    std::ostream &os;
   33    std::locale old_locale;
   34 
   35    enum write_state
   36    {
   37       empty,
   38       in_array_first_element,
   39       in_array,
   40       in_object_first_key,
   41       in_object_key,
   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); }
   89    JsonWriter &beginArray()
   90    {
   91       maybeComma();
   92       pushState(in_array_first_element);
   93       os << '[';
   94       return *this;
   95    }
   96    JsonWriter &endArray()
   97    {
   98       popState();
   99       os << ']';
  100       return *this;
  101    }
  102    JsonWriter &beginObject()
  103    {
  104       maybeComma();
  105       pushState(in_object_first_key);
  106       os << '{';
  107       return *this;
  108    }
  109    JsonWriter &endObject()
  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       {
  260       case pkgDepCache::ModeInstall:
  261       case pkgDepCache::ModeDelete:
  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       {
  275       case pkgDepCache::ModeInstall:
  276      jsonWriter.name("mode").value("install");
  277      break;
  278       case pkgDepCache::ModeDelete:
  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;
  362       MergeKeepFdsFromConfiguration(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 
  393      DpkgChrootDirectory();
  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 }