// -*- mode: cpp; mode: fold -*-
// Description								/*{{{*/
/* #####################################################################

   dummy solver to get quickly a scenario file out of APT

   ##################################################################### */
									/*}}}*/
// Include Files							/*{{{*/
#include <config.h>

#include <apt-pkg/cmndline.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/edsp.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/strutl.h>

#include <apt-private/private-cmndline.h>

#include <cstdio>
#include <iostream>
#include <memory>
#include <sstream>

#include <sys/types.h>
#include <sys/wait.h>

#include <string.h>
#include <unistd.h>
#include <signal.h>

#include <apti18n.h>
									/*}}}*/

static bool ShowHelp(CommandLine &)					/*{{{*/
{
   std::cout <<
      _("Usage: apt-dump-solver\n"
	    "\n"
	    "apt-dump-solver is an interface to store an EDSP scenario in\n"
	    "a file and optionally forwards it to another solver.\n");
   return true;
}
									/*}}}*/
static std::vector<aptDispatchWithHelp> GetCommands()			/*{{{*/
{
   return {};
}
									/*}}}*/
static int WriteError(char const * const uid, std::ostringstream &out, FileFd &stdoutfd, pid_t const &Solver)/*{{{*/
{
   _error->DumpErrors(out);
   // ensure the solver isn't printing into "our" error message, too
   if (Solver != 0)
      ExecWait(Solver, "dump", true);
   EDSP::WriteError(uid, out.str(), stdoutfd);
   return 0;
}
									/*}}}*/
int main(int argc,const char *argv[])					/*{{{*/
{
   CommandLine CmdL;
   ParseCommandLine(CmdL, APT_CMD::APT_DUMP_SOLVER, &_config, nullptr, argc, argv, &ShowHelp, &GetCommands);
   _config->Clear("Dir::Log");

   bool const is_forwarding_dumper = (CmdL.FileSize() != 0);

   FileFd stdoutfd;
   if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
      return 252;

   FileFd dump;
   char const * const filename = is_forwarding_dumper ? CmdL.FileList[0] : getenv("APT_EDSP_DUMP_FILENAME");
   if (filename == nullptr || strlen(filename) == 0)
   {
      if (is_forwarding_dumper == false)
      {
	 EDSP::WriteError("ERR_NO_FILENAME", "You have to set the environment variable APT_EDSP_DUMP_FILENAME\n"
	       "to a valid filename to store the dump of EDSP solver input in.\n"
	       "For example with: export APT_EDSP_DUMP_FILENAME=/tmp/dump.edsp", stdoutfd);
	 return 0;
      }
   }
   else
   {
      // ignore errors here as logging isn't really critical
      _error->PushToStack();
      if (dump.Open(filename, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false &&
	    is_forwarding_dumper == false)
      {
	 _error->MergeWithStack();
	 std::ostringstream out;
	 out << "Writing EDSP solver input to file '" << filename << "' failed as it couldn't be created!\n";
	 return WriteError("ERR_CREATE_FILE", out, stdoutfd, 0);
      }
      _error->RevertToStack();
   }

   pid_t Solver = 0;
   FileFd forward;
   if (is_forwarding_dumper)
   {
      signal(SIGPIPE, SIG_IGN);
      int external[] = {-1, -1};
      if (pipe(external) != 0)
	 return 250;
      for (int i = 0; i < 2; ++i)
	 SetCloseExec(external[i], true);

      Solver = ExecFork();
      if (Solver == 0) {
	 _config->Set("APT::Sandbox::User", _config->Find("APT::Solver::RunAsUser", _config->Find("APT::Sandbox::User")));
	 DropPrivileges();
	 dup2(external[0], STDIN_FILENO);
	 execv(CmdL.FileList[1], const_cast<char**>(CmdL.FileList + 1));
	 std::cerr << "Failed to execute  '" << CmdL.FileList[1] << "'!" << std::endl;
	 _exit(100);
      }
      close(external[0]);

      if (WaitFd(external[1], true, 5) == false)
	 return 251;

      if (forward.OpenDescriptor(external[1], FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
	 return 252;
   }

   DropPrivileges();

   FileFd input;
   if (input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false)
   {
      std::ostringstream out;
      out << "Writing EDSP solver input to file '" << filename << "' failed as stdin couldn't be opened!\n";
      return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver);
   }

   constexpr size_t BufSize = 64 * 1024;
   std::unique_ptr<char[]> Buf(new char[BufSize]);
   unsigned long long ToRead = 0;
   do {
      if (input.Read(Buf.get(),BufSize, &ToRead) == false)
      {
	 std::ostringstream out;
	 out << "Writing EDSP solver input to file '" << filename << "' failed as reading from stdin failed!\n";
	 return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver);
      }
      if (ToRead == 0)
	 break;
      if (forward.IsOpen() && forward.Failed() == false && forward.Write(Buf.get(),ToRead) == false)
	 forward.Close();
      if (dump.IsOpen() && dump.Failed() == false && dump.Write(Buf.get(),ToRead) == false)
	 dump.Close();
   } while (true);
   input.Close();
   forward.Close();
   dump.Close();

   if (is_forwarding_dumper)
   {
      // Wait and collect the error code
      int Status;
      while (waitpid(Solver, &Status, 0) != Solver)
      {
	 if (errno == EINTR)
	    continue;

	 std::ostringstream out;
	 ioprintf(out, _("Waited for %s but it wasn't there"), CmdL.FileList[1]);
	 return WriteError("ERR_FORWARD", out, stdoutfd, 0);
      }
      if (WIFEXITED(Status))
	 return WEXITSTATUS(Status);
      else
	 return 255;
   }
   else if (_error->PendingError())
   {
      std::ostringstream out;
      out << "Writing EDSP solver input to file '" << filename << "' failed due to write errors!\n";
      return WriteError("ERR_WRITE_ERROR", out, stdoutfd, Solver);
   }
   else
      EDSP::WriteError("ERR_JUST_DUMPING", "I am too dumb, i can just dump!\nPlease use one of my friends instead!", stdoutfd);
   return 0;
}
