// file      : odb/odb.cxx
// copyright : Copyright (c) 2009-2015 Code Synthesis Tools CC
// license   : GNU GPL v3; see accompanying LICENSE file

#include <errno.h>
#include <stdlib.h>    // getenv, setenv
#include <string.h>    // strerror, memset
#include <unistd.h>    // stat, close
#include <sys/types.h> // stat
#include <sys/stat.h>  // stat

// Process.
//
#ifndef _WIN32
#  include <unistd.h>    // execvp, fork, dup2, pipe, {STDIN,STDERR}_FILENO
#  include <sys/types.h> // waitpid
#  include <sys/wait.h>  // waitpid
#else
#  ifndef WIN32_LEAN_AND_MEAN
#    define WIN32_LEAN_AND_MEAN
#  endif
#  include <windows.h>   // CreatePipe, CreateProcess
#  include <io.h>        // _open_osfhandle
#  include <fcntl.h>     // _O_TEXT
#endif

#include <string>
#include <vector>
#include <cstddef>     // size_t
#include <sstream>
#include <fstream>
#include <iostream>
#include <ext/stdio_filebuf.h>

#include <cutl/fs/path.hxx>

#include <odb/version.hxx>
#include <odb/options.hxx>
#include <odb/profile.hxx>

#ifdef HAVE_CONFIG_H
#  include <odb/config.h>
#endif

using namespace std;
using cutl::fs::path;
using cutl::fs::invalid_path;

typedef vector<string> strings;
typedef vector<path> paths;

//
// Path manipulation.
//

// Escape backslashes in the path.
//
static string
escape_path (string const&);

// Search the PATH environment variable for the file.
//
static path
path_search (path const&);

// Driver path with the directory part (search PATH).
//
static path
driver_path (path const& driver);

#ifndef ODB_STATIC_PLUGIN
static path
plugin_path (path const& driver, string const& gxx);
#endif

//
// Process manipulation.
//
struct process_info
{
#ifndef _WIN32
  pid_t id;
#else
  HANDLE id;
#endif

  int out_fd; // Write to this fd to send to the new process' stdin.
  int in_efd; // Read from this fd to receive from the new process' stderr.
  int in_ofd; // Read from this fd to receive from the new process' stdout.
};

struct process_failure {};

// Start another process using the specified command line. Connect the
// newly created process' stdin to out_fd. Also if connect_* are true,
// connect the created process' stdout and stderr to in_*fd. Issue
// diagnostics and throw process_failure if anything goes wrong. The
// name argument is the name of the current process for diagnostics.
//
static process_info
start_process (char const* args[],
               char const* name,
               bool connect_stderr = false,
               bool connect_stdout = false);

// Wait for the process to terminate. Return true if the process terminated
// normally and with the zero exit status. Issue diagnostics and throw
// process_failure if anything goes wrong. The name argument is the name
// of the current process for diagnostics.
//
static bool
wait_process (process_info, char const* name);

//
//
static string
encode_plugin_option (string const& k, string const& v);

// Extract header search paths from GCC's -v output. May throw the
// profile_failure, process_failure and invalid_path exceptions. Name
// is the program name (argv[0]) for diagnostics.
//
static paths
profile_paths (strings const& args, char const* name);

static char const* const db_macro[] =
{
  "-DODB_DATABASE_COMMON",
  "-DODB_DATABASE_MSSQL",
  "-DODB_DATABASE_MYSQL",
  "-DODB_DATABASE_ORACLE",
  "-DODB_DATABASE_PGSQL",
  "-DODB_DATABASE_SQLITE"
};

int
main (int argc, char* argv[])
{
  ostream& e (cerr);

  try
  {
    strings args, plugin_args;
    bool v (false);

    // The first argument points to the program name, which is
    // g++ by default.
    //
#ifdef ODB_GXX_NAME
    path gxx (ODB_GXX_NAME);

    if (gxx.empty ())
    {
      e << argv[0] << ": error: embedded g++ compile name is empty" << endl;
      return 1;
    }

    // If the g++ name is a relative path (starts with '.'), then use
    // our own path as base.
    //
    if (gxx.string ()[0] == '.')
    {
      path dp (driver_path (path (argv[0])));
      path d (dp.directory ());

      if (!d.empty ())
        gxx = d / gxx;
    }

    args.push_back (gxx.string ());

    // Also modify LD_LIBRARY_PATH to include the lib path.
    //
#ifndef _WIN32
    {
#ifdef __APPLE__
      char const name[] = "DYLD_LIBRARY_PATH";
#else
      char const name[] = "LD_LIBRARY_PATH";
#endif

      string ld_paths;

      if (char const* s = getenv (name))
        ld_paths = s;

      path d (gxx.directory ());

      if (!d.empty ())
      {
        d.complete ();
        d /= path ("..") / path ("lib");

        if (ld_paths.empty ())
          ld_paths = d.string ();
        else
          ld_paths = d.string () + path::traits::path_separator + ld_paths;

        if (setenv (name, ld_paths.c_str (), 1) != 0)
        {
          e << argv[0] << ": error: unable to update environment" << endl;
          return 1;
        }
      }
    }
#endif // _WIN32

#else
    args.push_back ("g++");
#endif // ODB_GXX_NAME

    // Default options.
    //
    args.push_back ("-x");
    args.push_back ("c++");
    args.push_back (""); // Reserve space for -std=c++XX.
    args.push_back ("-S");
    args.push_back ("-Wunknown-pragmas");
    args.push_back ("-Wno-deprecated");
    args.push_back (""); // Reserve space for -fplugin=path.

    // Parse the default options file if we have one.
    //
    strings def_inc_dirs;
    strings def_defines;
#ifdef ODB_DEFAULT_OPTIONS_FILE
    {
      path file (ODB_DEFAULT_OPTIONS_FILE);

      // If the path is relative, then use the driver's path as a base. If
      // the file is not found in that directory, then also try outer
      // directory (so that we can find /etc if driver is in /usr/bin).
      //
      if (file.relative ())
      {
        bool found (false);
        path dd (driver_path (path (argv[0])).directory ());

        for (path d (dd);; d = d.directory ())
        {
          path f (d / file);
          // Check that the file exist without checking for permissions, etc.
          //
          struct stat s;
          if (stat (f.string ().c_str (), &s) == 0 && S_ISREG (s.st_mode))
          {
            file = f;
            found = true;
            break;
          }

          if (d.root ())
            break;
        }

        if (!found)
          file = dd / file; // For diagnostics.
      }

      int ac (3);
      const char* av[4] = {argv[0], "--file", file.string ().c_str (), 0};
      cli::argv_file_scanner s (ac, const_cast<char**> (av), "--file");

      bool first_x (true);

      while (s.more ())
      {
        string a (s.next ());
        size_t n (a.size ());

        // -x
        //
        if (a == "-x")
        {
          if (!s.more () || (a = s.next ()).empty ())
          {
            e << file << ": error: expected argument for the " << a
              << " option" << endl;
            return 1;
          }

          if (first_x)
          {
            first_x = false;

            // If it doesn't start with '-', then it must be the g++
            // executable name. Update the first argument with it.
            //
            if (a[0] != '-')
              args[0] = a;
            else
              args.push_back (a);
          }
          else
            args.push_back (a);
        }
        // -I
        //
        else if (n > 1 && a[0] == '-' && a[1] == 'I')
        {
          def_inc_dirs.push_back (a);

          if (n == 2) // -I /path
          {
            if (!s.more () || (a = s.next ()).empty ())
            {
              e << file << ": error: expected argument for the -I option"
                << endl;
              return 1;
            }

            def_inc_dirs.push_back (a);
          }
        }
        // -isystem, -iquote, -idirafter, and -framework (Mac OS X)
        //
        else if (a == "-isystem"   ||
                 a == "-iquote"    ||
                 a == "-idirafter" ||
                 a == "-framework")
        {
          def_inc_dirs.push_back (a);

          if (!s.more () || (a = s.next ()).empty ())
          {
            e << file << ": error: expected argument for the " << a
              << " option" << endl;
            return 1;
          }

          def_inc_dirs.push_back (a);
        }
        // -D
        //
        else if (n > 1 && a[0] == '-' && a[1] == 'D')
        {
          def_defines.push_back (a);

          if (n == 2) // -D macro
          {
            if (!s.more () || (a = s.next ()).empty ())
            {
              e << file << ": error: expected argument for the -D option"
                << endl;
              return 1;
            }

            def_defines.push_back (a);
          }
        }
        // -U
        //
        else if (n > 1 && a[0] == '-' && a[1] == 'U')
        {
          def_defines.push_back (a);

          if (n == 2) // -U macro
          {
            if (!s.more () || (a = s.next ()).empty ())
            {
              e << file << ": error: expected argument for the -U option"
                << endl;
              return 1;
            }

            def_defines.push_back (a);
          }
        }
        else
          plugin_args.push_back (a);
      }
    }
#endif

    // Add the default preprocessor defines (-D/-U) before the user-supplied
    // ones.
    //
    args.insert (args.end (), def_defines.begin (), def_defines.end ());

    // Parse driver options.
    //
    bool first_x (true);

    for (int i = 1; i < argc; ++i)
    {
      string a (argv[i]);
      size_t n (a.size ());

      // -v
      //
      if (a == "-v")
      {
        v = true;
        args.push_back (a);
      }
      // -x
      //
      else if (a == "-x")
      {
        if (++i == argc || argv[i][0] == '\0')
        {
          e << argv[0] << ": error: expected argument for the -x option" << endl;
          return 1;
        }

        a = argv[i];

        if (first_x)
        {
          first_x = false;

          // If it doesn't start with '-', then it must be the g++
          // executable name. Update the first argument with it.
          //
          if (a[0] != '-')
            args[0] = a;
          else
            args.push_back (a);
        }
        else
          args.push_back (a);
      }
      // -I
      //
      else if (n > 1 && a[0] == '-' && a[1] == 'I')
      {
        args.push_back (a);

        if (n == 2) // -I /path
        {
          if (++i == argc || argv[i][0] == '\0')
          {
            e << argv[0] << ": error: expected argument for the -I option"
              << endl;
            return 1;
          }

          args.push_back (argv[i]);
        }
      }
      // -isystem, -iquote, -idirafter, and -framework (Mac OS X)
      //
      else if (a == "-isystem"   ||
               a == "-iquote"    ||
               a == "-idirafter" ||
               a == "-framework")
      {
        args.push_back (a);

        if (++i == argc || argv[i][0] == '\0')
        {
          e << argv[0] << ": error: expected argument for the " << a
            << " option" << endl;
          return 1;
        }

        args.push_back (argv[i]);
      }
      // -D
      //
      else if (n > 1 && a[0] == '-' && a[1] == 'D')
      {
        args.push_back (a);

        if (n == 2) // -D macro
        {
          if (++i == argc || argv[i][0] == '\0')
          {
            e << argv[0] << ": error: expected argument for the -D option"
              << endl;
            return 1;
          }

          args.push_back (argv[i]);
        }
      }
      // -U
      //
      else if (n > 1 && a[0] == '-' && a[1] == 'U')
      {
        args.push_back (a);

        if (n == 2) // -U macro
        {
          if (++i == argc || argv[i][0] == '\0')
          {
            e << argv[0] << ": error: expected argument for the -U option"
              << endl;
            return 1;
          }

          args.push_back (argv[i]);
        }
      }
      // Store everything else in a list so that we can parse it with the
      // cli parser. This is the only reliable way to find out where the
      // options end.
      //
      else
        plugin_args.push_back (a);
    }

    // Add the default include directories (-I) after the user-supplied
    // ones.
    //
    args.insert (args.end (), def_inc_dirs.begin (), def_inc_dirs.end ());

    // Find the plugin.
    //
    {
#ifndef ODB_STATIC_PLUGIN
      path plugin (plugin_path (path (argv[0]), args[0]));
#else
      // Use a dummy name if the plugin is linked into the compiler.
      //
      path plugin ("odb");
#endif

      if (plugin.empty ())
        return 1; // Diagnostics has already been issued.

      args[7] = "-fplugin=" + plugin.string ();
    }

    // Parse plugin options. We have to do it twice to get the target
    // database which is needed while loading profiles.
    //
    vector<char*> av;
    av.push_back (argv[0]);

    for (strings::iterator i (plugin_args.begin ()), end (plugin_args.end ());
         i != end; ++i)
    {
      av.push_back (const_cast<char*> (i->c_str ()));
    }

    int ac (static_cast<int> (av.size ()));

    cli::argv_file_scanner::option_info oi[3];
    oi[0].option = "--options-file";
    oi[0].search_func = 0;
    oi[1].option = "-p";
    oi[2].option = "--profile";

    vector<database> dbs;
    bool show_sloc;
    size_t sloc_limit;
    {
      oi[1].search_func = &profile_search_ignore;
      oi[2].search_func = &profile_search_ignore;

      cli::argv_file_scanner scan (ac, &av[0], oi, 3);
      options ops (scan);

      // Handle --version.
      //
      if (ops.version ())
      {
        cout << "ODB object-relational mapping (ORM) compiler for C++ "
          ODB_COMPILER_VERSION_STR << endl
             << "Copyright (c) 2009-2015 Code Synthesis Tools CC" << endl;

        cout << "This is free software; see the source for copying "
             << "conditions. There is NO\nwarranty; not even for "
             << "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." << endl;

        return 0;
      }

      // Handle --help.
      //
      if (ops.help ())
      {
        cout << "Usage: " << argv[0] << " [options] file [file ...]" << endl
             << "Options:" << endl;

        options::print_usage (cout);
        return 0;
      }

      // Check that required options were specifed.
      //
      dbs = ops.database ();

      if (dbs.empty ())
      {
        e << argv[0] << ": error: no database specified with the --database "
          << "option" << endl;
        return 1;
      }

      if (dbs.size () > 1 && !ops.multi_database_specified ())
      {
        e << argv[0] << ": error: --multi-database option required when " <<
          "multiple databases are specified"<< endl;
        return 1;
      }

      show_sloc = ops.show_sloc ();
      sloc_limit = ops.sloc_limit_specified () ? ops.sloc_limit () : 0;

      // Translate some ODB options to GCC options.
      //
      switch (ops.std ())
      {
      case cxx_version::cxx98:
        {
          args[3] = "-std=gnu++98";
          break;
        }
      case cxx_version::cxx11:
        {
          args[3] = "-std=gnu++0x"; // gnu++11 was only added in GCC 4.7.0.
          break;
        }
      case cxx_version::cxx14:
        {
          args[3] = "-std=gnu++14";
          break;
        }
      }
    }

    // Obtain profile (-I) search paths.
    //
    paths prof_paths (profile_paths (args, argv[0]));

    if (v)
    {
      e << "Profile search paths:" << endl;

      for (paths::const_iterator i (prof_paths.begin ());
           i != prof_paths.end (); ++i)
        e << " " << *i << endl;
    }

    // Pass profile search paths (svc-path option).
    //
    for (paths::const_iterator i (prof_paths.begin ());
         i != prof_paths.end (); ++i)
    {
      args.push_back (encode_plugin_option ("svc-path", i->string ()));
    }

    // Add common ODB macros.
    //
    args.push_back ("-DODB_COMPILER");

    {
      ostringstream ostr;
      ostr << ODB_COMPILER_VERSION;
      args.push_back ("-DODB_COMPILER_VERSION=" + ostr.str ());
    }

    // Compile for each database.
    //
    size_t sloc_total (0);

    for (vector<database>::iterator i (dbs.begin ()); i != dbs.end (); ++i)
    {
      database db (*i);
      strings db_args (args);

      // Add database-specific ODB macro.
      //
      db_args.push_back (db_macro[db]);

      // Second parse.
      //
      profile_data pd (prof_paths, db, argv[0]);
      oi[1].search_func = &profile_search;
      oi[2].search_func = &profile_search;
      oi[1].arg = &pd;
      oi[2].arg = &pd;

      cli::argv_file_scanner scan (ac, &av[0], oi, 3);
      options ops (scan);

      size_t end (scan.end () - 1); // We have one less in plugin_args.

      if (end == plugin_args.size ())
      {
        e << argv[0] << ": error: input file expected" << endl;
        return 1;
      }

      // Encode plugin options.
      //
      // Add the database we are compiling for first. More databases
      // could be specified in options files but they will be ignored
      // by the plugin (it only cares about the first).
      //
      db_args.push_back (encode_plugin_option ("database", db.string ()));

      cli::options const& desc (options::description ());
      for (size_t i (0); i < end; ++i)
      {
        string k, v;
        string a (plugin_args[i]);

        // Ignore certain options.
        //
        if (a == "--")
        {
          // Ignore the option seperator since GCC doesn't understand it.
          //
          continue;
        }
        else if (a == "-d" || a == "--database")
        {
          // Ignore all other databases.
          //
          i++; // Skip the value.
          continue;
        }

        cli::options::const_iterator it (desc.find (a));

        if (it == desc.end ())
        {
          e << argv[0] << ": ice: unexpected option '" << a << "'" << endl;
          return 1;
        }

        if (a.size () > 2 && a[0] == '-' && a[1] == '-')
          k = string (a, 2); // long format
        else
          k = string (a, 1); // short format

        // If there are more arguments then we may have a value.
        //
        if (!it->flag ())
        {
          if (i + 1 == end)
          {
            e << argv[0] << ": ice: expected argument for '" << a << "'"
              << endl;
            return 1;
          }

          v = plugin_args[++i];
        }

        db_args.push_back (encode_plugin_option (k, v));
      }

      // Reserve space for and remember the position of the svc-file
      // option.
      //
      size_t svc_file_pos (db_args.size ());
      db_args.push_back ("");

      // If compiling multiple input files at once, pass them also with
      // the --svc-file option.
      //
      bool at_once (ops.at_once () && plugin_args.size () - end > 1);
      if (at_once)
      {
        if (ops.input_name ().empty ())
        {
          e << "error: --input-name required when compiling multiple " <<
            "input files at once (--at-once)" << endl;
          return 1;
        }

        for (size_t i (end); i < plugin_args.size (); ++i)
          db_args.push_back (
            encode_plugin_option ("svc-file", plugin_args[i]));
      }

      // Create an execvp-compatible argument array.
      //
      typedef vector<char const*> cstrings;
      cstrings exec_args;

      for (strings::const_iterator i (db_args.begin ()), end (db_args.end ());
           i != end; ++i)
      {
        exec_args.push_back (i->c_str ());
      }

      exec_args.push_back ("-"); // Compile stdin.
      exec_args.push_back (0);

      // Iterate over the input files and compile each of them.
      //
      for (; end < plugin_args.size (); ++end)
      {
        string name (at_once ? ops.input_name () : plugin_args[end]);

        // Set the --svc-file option.
        //
        db_args[svc_file_pos] = encode_plugin_option ("svc-file", name);
        exec_args[svc_file_pos] = db_args[svc_file_pos].c_str ();

        //
        //
        ifstream ifs;

        if (!at_once)
        {
          ifs.open (name.c_str (), ios_base::in | ios_base::binary);

          if (!ifs.is_open ())
          {
            e << name << ": error: unable to open in read mode" << endl;
            return 1;
          }
        }

        if (v)
        {
          e << "Compiling " << name << endl;
          for (cstrings::const_iterator i (exec_args.begin ());
               i != exec_args.end (); ++i)
          {
            if (*i != 0)
              e << *i << (*(i + 1) != 0 ? ' ' : '\n');
          }
        }

        process_info pi (start_process (&exec_args[0], argv[0], false, true));

        {
          __gnu_cxx::stdio_filebuf<char> fb (
            pi.out_fd, ios_base::out | ios_base::binary);
          ostream os (&fb);

          if (!at_once)
          {
            // See if we there is a UTF-8 BOM in the input file. If so,
            // then we need to write it before prologues.
            //
            if (ifs.peek () == 0xEF)
            {
              ifs.get ();
              if (ifs.get () != 0xBB || ifs.get () != 0xBF)
              {
                e << name << ": error: invalid UTF-8 BOM sequence" << endl;
                fb.close ();
                wait_process (pi, argv[0]);
                return 1;
              }

              os.put (0xEF);
              os.put (0xBB);
              os.put (0xBF);
            }
          }

          if (!ops.trace ())
          {
            // Add the standard prologue.
            //
            os << "#line 1 \"<standard-odb-prologue>\"" << endl;

            // Make sure ODB compiler and libodb versions are compatible.
            //
            os << "#include <odb/version.hxx>" << endl
               << endl
               << "#if ODB_VERSION != " << ODB_VERSION << endl
               << "#  error incompatible ODB compiler and runtime " <<
              "versions" << endl
               << "#endif" << endl
               << endl;

            // Include std::string. It is used as a default type for
            // the implicit discriminator member in polymorphism
            // support.
            //
            os << "#include <string>" << endl
               << endl;

            // Add ODB compiler metaprogramming tests.
            //
            os << "namespace odb" << endl
               << "{" << endl
               << "namespace compiler" << endl
               << "{" << endl;

            // operator< test, used in validator.
            //
            os << "template <typename T>" << endl
               << "bool" << endl
               << "has_lt_operator (const T& x, const T& y)" << endl
               << "{"  << endl
               << "bool r (x < y);"  << endl
               << "return r;"  << endl
               << "}" << endl;

            os << "}" << endl
               << "}" << endl;
          }

          // Add custom prologue if any.
          //
          // NOTE: if you change the format, you also need to update code
          // in include.cxx
          //
          size_t pro_count (1);
          if (ops.odb_prologue ().count (db) != 0)
          {
            strings const& pro (ops.odb_prologue ()[db]);
            for (size_t i (0); i < pro.size (); ++i, ++pro_count)
            {
              os << "#line 1 \"<odb-prologue-" << pro_count << ">\"" << endl
                 << pro[i] << endl;
            }
          }

          if (ops.odb_prologue_file ().count (db) != 0)
          {
            strings const& prof (ops.odb_prologue_file ()[db]);
            for (size_t i (0); i < prof.size (); ++i, ++pro_count)
            {
              os << "#line 1 \"<odb-prologue-" << pro_count << ">\""
                 << endl;

              ifstream ifs (prof[i].c_str (), ios_base::in | ios_base::binary);

              if (!ifs.is_open ())
              {
                e << prof[i] << ": error: unable to open in read mode" << endl;
                fb.close ();
                wait_process (pi, argv[0]);
                return 1;
              }

              if (!(os << ifs.rdbuf ()))
              {
                e << prof[i] << ": error: io failure" << endl;
                fb.close ();
                wait_process (pi, argv[0]);
                return 1;
              }

              os << endl;
            }
          }

          if (at_once)
          {
            // Include all the input files (no need to escape).
            //
            os << "#line 1 \"<command-line>\"" << endl;

            bool b (ops.include_with_brackets ());
            char op (b ? '<' : '"'), cl (b ? '>' : '"');

            for (; end < plugin_args.size (); ++end)
              os << "#include " << op << plugin_args[end] << cl << endl;
          }
          else
          {
            // Write the synthesized translation unit to stdout.
            //
            os << "#line 1 \"" << escape_path (name) << "\"" << endl;

            if (!(os << ifs.rdbuf ()))
            {
              e << name << ": error: io failure" << endl;
              fb.close ();
              wait_process (pi, argv[0]);
              return 1;
            }

            // Add a new line in case the input file doesn't end with one.
            //
            os << endl;
          }

          // Add custom epilogue if any.
          //
          // NOTE: if you change the format, you also need to update code
          // in include.cxx
          //
          size_t epi_count (1);
          if (ops.odb_epilogue ().count (db) != 0)
          {
            strings const& epi (ops.odb_epilogue ()[db]);
            for (size_t i (0); i < epi.size (); ++i, ++epi_count)
            {
              os << "#line 1 \"<odb-epilogue-" << epi_count << ">\"" << endl
                 << epi[i] << endl;
            }
          }

          if (ops.odb_epilogue_file ().count (db) != 0)
          {
            strings const& epif (ops.odb_epilogue_file ()[db]);
            for (size_t i (0); i < epif.size (); ++i, ++epi_count)
            {
              os << "#line 1 \"<odb-epilogue-" << epi_count << ">\""
                 << endl;

              ifstream ifs (epif[i].c_str (), ios_base::in | ios_base::binary);

              if (!ifs.is_open ())
              {
                e << epif[i] << ": error: unable to open in read mode" << endl;
                fb.close ();
                wait_process (pi, argv[0]);
                return 1;
              }

              if (!(os << ifs.rdbuf ()))
              {
                e << epif[i] << ": error: io failure" << endl;
                fb.close ();
                wait_process (pi, argv[0]);
                return 1;
              }

              os << endl;
            }
          }

          if (!ops.trace ())
          {
            // Add the standard epilogue at the end so that we see all
            // the declarations.
            //
            os << "#line 1 \"<standard-odb-epilogue>\"" << endl;

            // Includes for standard smart pointers. The Boost TR1 header
            // may or may not delegate to the GCC implementation. In either
            // case, the necessary declarations will be provided so we don't
            // need to do anything.
            //
            os << "#include <memory>" << endl
               << "#ifndef BOOST_TR1_MEMORY_HPP_INCLUDED" << endl
               << "#  include <tr1/memory>" << endl
               << "#endif" << endl;

            // Standard wrapper traits.
            //
            os << "#include <odb/wrapper-traits.hxx>" << endl
               << "#include <odb/tr1/wrapper-traits.hxx>" << endl;

            // Standard pointer traits.
            //
            os << "#include <odb/pointer-traits.hxx>" << endl
               << "#include <odb/tr1/pointer-traits.hxx>" << endl;

            // Standard container traits.
            //
            os << "#include <odb/container-traits.hxx>" << endl;
          }
        }

        // Filter the output stream looking for communication from the
        // plugin.
        //
        {
          __gnu_cxx::stdio_filebuf<char> fb (pi.in_ofd, ios_base::in);
          istream is (&fb);

          for (bool first (true); !is.eof (); )
          {
            string line;
            getline (is, line);

            if (is.fail () && !is.eof ())
            {
              e << argv[0] << ": error: io failure while parsing output"
                << endl;
              wait_process (pi, argv[0]);
              return 1;
            }

            if (line.compare (0, 9, "odb:sloc:") == 0)
            {
              if (show_sloc || sloc_limit != 0)
              {
                size_t n;
                istringstream is (string (line, 9, string::npos));

                if (!(is >> n && is.eof ()))
                {
                  e << argv[0] << ": error: invalid odb:sloc value" << endl;
                  wait_process (pi, argv[0]);
                  return 1;
                }

                sloc_total += n;
              }

              continue;
            }

            if (first)
              first = false;
            else
              cout << endl;

            cout << line;
          }
        }

        if (!wait_process (pi, argv[0]))
          return 1;
      } // End input file loop.
    } // End database loop.

    // Handle SLOC.
    //
    if (show_sloc)
      e << "total: " << sloc_total << endl;

    if (sloc_limit != 0 && sloc_limit < sloc_total)
    {
      e << argv[0] << ": error: SLOC limit of " << sloc_limit << " lines " <<
        "has been exceeded" << endl;

      if (!show_sloc)
        e << argv[0] << ": info: use the --show-sloc option to see the "
          << "current total" << endl;

      return 1;
    }
  }
  catch (profile_failure const&)
  {
    // Diagnostics has already been issued.
    //
    return 1;
  }
  catch (process_failure const&)
  {
    // Diagnostics has already been issued.
    //
    return 1;
  }
  catch (invalid_path const& ex)
  {
    e << argv[0] << ": error: invalid path '" << ex.path () << "'" << endl;
    return 1;
  }
  catch (cli::exception const& ex)
  {
    e << ex << endl;
    return 1;
  }
}

static string
encode_plugin_option (string const& k, string const& cv)
{
  string o ("-fplugin-arg-odb-"), v (cv);
  o += k;

  if (!v.empty ())
  {
    o += '=';

    // A value cannot contain '='. Encode it as the backspace
    // character.
    //
    for (size_t i (0); i < v.size (); ++i)
      if (v[i] == '=')
        v[i] = '\b';

    o += v;
  }

  return o;
}

static paths
profile_paths (strings const& sargs, char const* name)
{
  // Copy some of the arguments from the passed list. We also need
  // the g++ executable.
  //
  strings args;

  args.push_back (sargs[0]);
  args.push_back ("-v");
  args.push_back ("-x");
  args.push_back ("c++");
  args.push_back ("-E");
  args.push_back ("-P");

  for (strings::const_iterator i (++sargs.begin ()), end (sargs.end ());
       i != end; ++i)
  {
    string const& a (*i);

    // -I
    //
    if (a.size () > 1 && a[0] == '-' && a[1] == 'I')
    {
      args.push_back (a);

      if (a.size () == 2) // -I /path
      {
        args.push_back (*(++i));
      }
    }
    // -framework
    //
    else if (a == "-isystem"   ||
             a == "-iquote"    ||
             a == "-idirafter" ||
             a == "-isysroot"  ||
             a == "-framework")
    {
      args.push_back (a);

      if (++i == end)
      {
        cerr << name << ": error: expected argument for the " << a
             << " option" << endl;
        throw profile_failure ();
      }

      args.push_back (*i);
    }
    // --sysroot
    //
    else if (a.compare (0, 10, "--sysroot=") == 0)
      args.push_back (a);
    // -std
    //
    else if (a.compare (0, 5, "-std=") == 0)
      args.push_back (a);
  }

  // Create an execvp-compatible argument array.
  //
  vector<char const*> exec_args;

  for (strings::const_iterator i (args.begin ()), end (args.end ());
       i != end; ++i)
  {
    exec_args.push_back (i->c_str ());
  }

  exec_args.push_back ("-"); // Compile stdin.
  exec_args.push_back (0);

  process_info pi (start_process (&exec_args[0], name, true));
  close (pi.out_fd); // Preprocess empty file.

  // Read the output into a temporary string stream. We don't parse
  // it on the fly because we don't know whether it is the data or
  // diagnostics until after the process is terminated and we get
  // the exit code. We also cannot first wait for the exist code
  // and then read the output because the process might get blocked.
  //
  stringstream ss;
  {
    __gnu_cxx::stdio_filebuf<char> fb (pi.in_efd, ios_base::in);
    istream is (&fb);

    for (bool first (true); !is.eof (); )
    {
      string line;
      getline (is, line);

      if (is.fail () && !is.eof ())
      {
        cerr << name << ": error: "
             << "io failure while parsing profile paths" << endl;

        wait_process (pi, name);
        throw profile_failure ();
      }

      if (first)
        first = false;
      else
        ss << endl;

      ss << line;
    }
  }

  if (!wait_process (pi, name))
  {
    // Things didn't go well and ss should contain the diagnostics.
    // In case it is empty, issue our own.
    //
    if (!ss.str ().empty ())
      cerr << ss.rdbuf ();
    else
      cerr << name << ": error: unable to extract profile paths" << endl;

    throw profile_failure ();
  }

  // Parse the cached output.
  //
  paths r;
  {
    enum
    {
      read_prefix,
      read_path,
      read_suffix
    } state = read_prefix;

    while (!ss.eof () && state != read_suffix)
    {
      string line;
      getline (ss, line);

      if (ss.fail () && !ss.eof ())
      {
        cerr << name << ": error: "
             << "io failure while parsing profile paths" << endl;
        throw profile_failure ();
      }

      switch (state)
      {
      case read_prefix:
        {
          // The English string that we are looking for is "#include <...>
          // search starts here:" but it can be translated. However, all
          // the translations seems to have the "#include" and "<...>"
          // parts, so we can search for those.
          //
          if (line.find ("#include") != string::npos &&
              line.find ("<...>") != string::npos)
            state = read_path;
          break;
        }
      case read_path:
        {
          // The end of the list is terminated with the "End of search
          // list." line, which, again, can be translated. Here we don't
          // have any invariable parts that we can use. Instead, we will
          // rely on the fact that all the paths are space-indented.
          //
          if (!line.empty () && line[0] != ' ')
            state = read_suffix;
          else
            // Paths are indented with a space.
            //
            r.push_back (path (string (line, 1)));

          break;
        }
      case read_suffix:
        {
          // We shouldn't get here.
          break;
        }
      }
    }

    if (state != read_suffix)
    {
      cerr << name << ": error: unable to parse profile paths" << endl;
      throw profile_failure ();
    }
  }

  return r;
}

//
// Path manipulation.
//

static string
escape_path (string const& p)
{
  string r;

  for (size_t i (0); i < p.size (); ++i)
  {
    if (p[i] == '\\')
      r += "\\\\";
    else
      r += p[i];
  }

  return r;
}

static path
path_search (path const& f)
{
  typedef path::traits traits;

  // If there is a directory component in the file, then the PATH
  // search does not apply.
  //
  if (!f.directory ().empty ())
    return f;

  string paths;

  // If there is no PATH in environment then the default search
  // path is the current directory.
  //
  if (char const* s = getenv ("PATH"))
    paths = s;
  else
    paths = traits::path_separator;

  // On Windows also check the current directory.
  //
#ifdef _WIN32
  paths += traits::path_separator;
#endif

  struct stat info;

  for (size_t b (0), e (paths.find (traits::path_separator));
       b != string::npos;)
  {
    path p (string (paths, b, e != string::npos ? e - b : e));

    // Empty path (i.e., a double colon or a colon at the beginning
    // or end of PATH) means search in the current dirrectory.
    //
    if (p.empty ())
      p = path (".");

    path dp (p / f);

    // Just check that the file exist without checking for permissions, etc.
    //
    if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode))
      return dp;

    // On Windows also try the path with the .exe extension.
    //
#ifdef _WIN32
    dp += ".exe";

    if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode))
      return dp;
#endif

    if (e == string::npos)
      b = e;
    else
    {
      b = e + 1;
      e = paths.find (traits::path_separator, b);
    }
  }

  return path ();
}

static path
driver_path (path const& drv)
{
  return drv.directory ().empty () ? path_search (drv) : drv;
}

#ifndef ODB_STATIC_PLUGIN
static path
plugin_path (path const& drv,
#ifdef ODB_GCC_PLUGIN_DIR
             string const& gxx)
#else
             string const&)
#endif
{
  // Figure out the plugin base name which is just the driver name.
  // If the driver name starts with 'lt-', then we are running through
  // the libtool script. Strip this prefix -- the shared object should
  // be in the same directory.
  //
  string b (drv.leaf ().string ());
  bool lt (b.size () > 3 && b[0] == 'l' && b[1] == 't' && b[2] == '-');
  if (lt)
    b = string (b, 3, string::npos);

  path dp (driver_path (drv));

  if (dp.empty ())
  {
    cerr << drv << ": error: unable to resolve ODB driver path" << endl;
    return path ();
  }

  dp = dp.directory ();
  struct stat info;

  // Regardless of whether we were given a plugin path, first try
  // the current directory for the .la file. This will make sure
  // running ODB from the build directory works as expected.
  //
  path pp (dp / path (b + ".la"));
  if (stat (pp.string ().c_str (), &info) == 0)
  {
    pp = dp / path (".libs") / path (b + ".so");
    if (stat (pp.string ().c_str (), &info) == 0)
      return pp;
  }

#ifdef ODB_GCC_PLUGIN_DIR
  // Plugin should be installed into the GCC default plugin directory.
  // Ideally, in this situation, we would simply pass the plugin name and
  // let GCC append the correct directory. Unfortunately, this mechanism
  // was only added in GCC 4.6 so in order to support 4.5 we will have to
  // emulate it ourselves.
  //
  if (!lt)
  {
    // First get the default GCC plugin directory.
    //
    path d;
    vector<char const*> exec_args;
    exec_args.push_back (gxx.c_str ());
    exec_args.push_back ("-print-file-name=plugin");
    exec_args.push_back (0);

    process_info pi (
      start_process (
        &exec_args[0], drv.string ().c_str (), false, true));
    close (pi.out_fd);

    // Read the path from stdout.
    //
    {
      __gnu_cxx::stdio_filebuf<char> fb (pi.in_ofd, ios_base::in);
      istream is (&fb);
      string line;
      getline (is, line);
      d = path (line);
    }

    if (!wait_process (pi, drv.string ().c_str ()))
      return path (); // Assume GCC issued some diagnostics.

    if (d.string () == "plugin")
    {
      cerr << drv << ": error: unable to obtain GCC plugin directory" << endl;
      return path ();
    }

    // See if the plugin is there.
    //
    pp = d / path (b + ".so");
    if (stat (pp.string ().c_str (), &info) != 0)
    {
      cerr << drv << ": error: no ODB plugin in GCC plugin directory '" <<
        d << "'" << endl;
      return path ();
    }

    return pp;
  }
#elif defined (ODB_PLUGIN_PATH)
  // If we were given a plugin path, use that unless we are running
  // via libtool.
  //
  if (!lt)
  {
    string rp (ODB_PLUGIN_PATH);
    if (!rp.empty ())
      dp /= path (rp);

    pp = dp / path (b + ".so");

    if (stat (pp.string ().c_str (), &info) != 0)
    {
      cerr << drv << ": error: no ODB plugin in '" << dp << "'" << endl;
      return path ();
    }

    return pp;
  }
#endif

  // Try .so in the current directory.
  //
  pp = dp / path (b + ".so");
  if (stat (pp.string ().c_str (), &info) != 0)
  {
    cerr << drv << ": error: unable to locate ODB plugin" << endl;
    return path ();
  }

  return pp;
}
#endif

//
// Process manipulation.
//

#ifndef _WIN32

static process_info
start_process (char const* args[], char const* name, bool err, bool out)
{
  int out_fd[2];
  int in_efd[2];
  int in_ofd[2];

  if (pipe (out_fd) == -1 ||
      (err && pipe (in_efd) == -1) ||
      (out && pipe (in_ofd) == -1))
  {
    char const* err (strerror (errno));
    cerr << name << ": error: " <<  err << endl;
    throw process_failure ();
  }

  pid_t pid (fork ());

  if (pid == -1)
  {
    char const* err (strerror (errno));
    cerr << name << ": error: " <<  err << endl;
    throw process_failure ();
  }

  if (pid == 0)
  {
    // Child. Close the write end of the pipe and duplicate the read end
    // to stdin. Then close the original read end descriptors.
    //
    if (close (out_fd[1]) == -1 ||
        dup2 (out_fd[0], STDIN_FILENO) == -1 ||
        close (out_fd[0]) == -1)
    {
      char const* err (strerror (errno));
      cerr << name << ": error: " <<  err << endl;
      throw process_failure ();
    }

    // Do the same for the stderr if requested.
    //
    if (err)
    {
      if (close (in_efd[0]) == -1 ||
          dup2 (in_efd[1], STDERR_FILENO) == -1 ||
          close (in_efd[1]) == -1)
      {
        char const* err (strerror (errno));
        cerr << name << ": error: " <<  err << endl;
        throw process_failure ();
      }
    }

    // Do the same for the stdout if requested.
    //
    if (out)
    {
      if (close (in_ofd[0]) == -1 ||
          dup2 (in_ofd[1], STDOUT_FILENO) == -1 ||
          close (in_ofd[1]) == -1)
      {
        char const* err (strerror (errno));
        cerr << name << ": error: " <<  err << endl;
        throw process_failure ();
      }
    }

    if (execvp (args[0], const_cast<char**> (&args[0])) == -1)
    {
      char const* err (strerror (errno));
      cerr << args[0] << ": error: " << err << endl;
      throw process_failure ();
    }
  }
  else
  {
    // Parent. Close the other ends of the pipes.
    //
    if (close (out_fd[0]) == -1 ||
        (err && close (in_efd[1]) == -1) ||
        (out && close (in_ofd[1]) == -1))
    {
      char const* err (strerror (errno));
      cerr << name << ": error: " <<  err << endl;
      throw process_failure ();
    }
  }

  process_info r;
  r.id = pid;
  r.out_fd = out_fd[1];
  r.in_efd = err ? in_efd[0] : 0;
  r.in_ofd = out ? in_ofd[0] : 0;
  return r;
}

static bool
wait_process (process_info pi, char const* name)
{
  int status;

  if (waitpid (pi.id, &status, 0) == -1)
  {
    char const* err (strerror (errno));
    cerr << name << ": error: " <<  err << endl;
    throw process_failure ();
  }

  return WIFEXITED (status) && WEXITSTATUS (status) == 0;
}

#else // _WIN32

static void
print_error (char const* name)
{
  LPTSTR msg;
  DWORD e (GetLastError());

  if (!FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        0,
        e,
        MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &msg,
        0,
        0))
  {
    cerr << name << ": error: unknown error code " << e << endl;
    return;
  }

  cerr << name << ": error: " << msg << endl;
  LocalFree (msg);
}

static process_info
start_process (char const* args[], char const* name, bool err, bool out)
{
  HANDLE out_h[2];
  HANDLE in_eh[2];
  HANDLE in_oh[2];
  SECURITY_ATTRIBUTES sa;

  sa.nLength = sizeof (SECURITY_ATTRIBUTES);
  sa.bInheritHandle = true;
  sa.lpSecurityDescriptor = 0;

  if (!CreatePipe (&out_h[0], &out_h[1], &sa, 0) ||
      !SetHandleInformation (out_h[1], HANDLE_FLAG_INHERIT, 0))
  {
    print_error (name);
    throw process_failure ();
  }

  if (err)
  {
    if (!CreatePipe (&in_eh[0], &in_eh[1], &sa, 0) ||
        !SetHandleInformation (in_eh[0], HANDLE_FLAG_INHERIT, 0))
    {
      print_error (name);
      throw process_failure ();
    }
  }

  if (out)
  {
    if (!CreatePipe (&in_oh[0], &in_oh[1], &sa, 0) ||
        !SetHandleInformation (in_oh[0], HANDLE_FLAG_INHERIT, 0))
    {
      print_error (name);
      throw process_failure ();
    }
  }

  // Create the process.
  //
  path file (args[0]);

  // Do PATH search.
  //
  if (file.directory ().empty ())
    file = path_search (file);

  if (file.empty ())
  {
    cerr << args[0] << ": error: file not found" << endl;
    throw process_failure ();
  }

  // Serialize the arguments to string.
  //
  string cmd_line;

  for (char const** p (args); *p != 0; ++p)
  {
    if (p != args)
      cmd_line += ' ';

    // On Windows we need to protect values with spaces using quotes.
    // Since there could be actual quotes in the value, we need to
    // escape them.
    //
    string a (*p);
    bool quote (a.find (' ') != string::npos);

    if (quote)
      cmd_line += '"';

    for (size_t i (0); i < a.size (); ++i)
    {
      if (a[i] == '"')
        cmd_line += "\\\"";
      else
        cmd_line += a[i];
    }

    if (quote)
      cmd_line += '"';
  }

  // Prepare other info.
  //
  STARTUPINFO si;
  PROCESS_INFORMATION pi;

  memset (&si, 0, sizeof (STARTUPINFO));
  memset (&pi, 0, sizeof (PROCESS_INFORMATION));

  si.cb = sizeof(STARTUPINFO);

  if (err)
    si.hStdError = in_eh[1];
  else
    si.hStdError = GetStdHandle (STD_ERROR_HANDLE);

  if (out)
    si.hStdOutput = in_oh[1];
  else
    si.hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);

  si.hStdInput = out_h[0];
  si.dwFlags |= STARTF_USESTDHANDLES;

  if (!CreateProcess (
        file.string ().c_str (),
        const_cast<char*> (cmd_line.c_str ()),
        0,    // Process security attributes.
        0,    // Primary thread security attributes.
        true, // Inherit handles.
        0,    // Creation flags.
        0,    // Use our environment.
        0,    // Use our current directory.
        &si,
        &pi))
  {
    print_error (name);
    throw process_failure ();
  }

  CloseHandle (pi.hThread);
  CloseHandle (out_h[0]);

  if (err)
    CloseHandle (in_eh[1]);

  if (out)
    CloseHandle (in_oh[1]);

  process_info r;
  r.id = pi.hProcess;
  r.out_fd = _open_osfhandle ((intptr_t) (out_h[1]), 0);

  if (r.out_fd == -1)
  {
    cerr << name << ": error: unable to obtain C file handle" << endl;
    throw process_failure ();
  }

  if (err)
  {
    // Pass _O_TEXT to get newline translation.
    //
    r.in_efd = _open_osfhandle ((intptr_t) (in_eh[0]), _O_TEXT);

    if (r.in_efd == -1)
    {
      cerr << name << ": error: unable to obtain C file handle" << endl;
      throw process_failure ();
    }
  }
  else
    r.in_efd = 0;

  if (out)
  {
    // Pass _O_TEXT to get newline translation.
    //
    r.in_ofd = _open_osfhandle ((intptr_t) (in_oh[0]), _O_TEXT);

    if (r.in_ofd == -1)
    {
      cerr << name << ": error: unable to obtain C file handle" << endl;
      throw process_failure ();
    }
  }
  else
    r.in_ofd = 0;

  return r;
}

static bool
wait_process (process_info pi, char const* name)
{
  DWORD status;

  if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 ||
      !GetExitCodeProcess (pi.id, &status))
  {
    print_error (name);
    throw process_failure ();
  }

  CloseHandle (pi.id);
  return status == 0;
}

#endif // _WIN32
