/*************************************************
* Unix EntropySource Source File                 *
* (C) 1999-2003 The Botan Project                *
*************************************************/

#ifndef _XOPEN_SOURCE
  #define _XOPEN_SOURCE 500
#endif
#ifndef _XOPEN_SOURCE_EXTENDED
  #define _XOPEN_SOURCE_EXTENDED 1
#endif

#include <botan/es_unix.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>

namespace Botan {

namespace {

bool Unix_Program_Cmp(const Unix_Program& a, const Unix_Program& b)
   { return (a.priority < b.priority); }

}

/*************************************************
* Unix_EntropySource Constructor                 *
*************************************************/
Unix_EntropySource::Unix_EntropySource()
   {
   u32bit count = 0;
   while(true)
      {
      if(DEFAULT_SOURCES[count].name == "") break;
      count++;
      }
   add_sources(DEFAULT_SOURCES, count);
   }

/*************************************************
* Add sources to the list                        *
*************************************************/
void Unix_EntropySource::add_sources(const Unix_Program srcs[], u32bit count)
   {
   sources.insert(sources.end(), srcs, srcs + count);
   std::sort(sources.begin(), sources.end(), Unix_Program_Cmp);
   }

/*************************************************
* Unix Fast Poll                                 *
*************************************************/
void Unix_EntropySource::do_fast_poll()
   {
   gather(2);
   }

/*************************************************
* Unix Slow Poll                                 *
*************************************************/
void Unix_EntropySource::do_slow_poll()
   {
   gather(4);
   }

/*************************************************
* Gather Entropy From Several Unix_Programs      *
*************************************************/
void Unix_EntropySource::gather(u32bit max_priority)
   {
   for(u32bit j = 1; j != max_priority; j++)
      {
      std::vector<u32bit> indexes;

      for(u32bit k = 0; k != sources.size(); k++)
         if(sources[k].priority == j && sources[k].working)
            indexes.push_back(k);

      if(indexes.size() == 0) continue;
      gather_from(indexes);
      }
   }


namespace {

/*************************************************
* A structure for holding pipe information       *
*************************************************/
struct Command_Info
   {
   public:
      u32bit index;
      int fd;
      FILE* pipe;
      Command_Info() { pipe = 0; fd = 0; index = 0; }
   };

}

/*************************************************
* Gather entropy from some Unix programs         *
*************************************************/
void Unix_EntropySource::gather_from(const std::vector<u32bit>& indexes)
   {
   const u32bit MIN_OUTPUT = 10;

   std::vector<Command_Info> command(indexes.size());

   for(u32bit j = 0; j != indexes.size(); j++)
      {
      Unix_Program& src = sources[indexes[j]];

      std::string command_str = src.name + " " + src.args + " 2>/dev/null";

      FILE* pipe = popen(command_str.c_str(), "r");
      if(!pipe)
         throw Stream_IO_Error("Unix_EntropySource: popen failed");

      command[j].index = indexes[j];
      command[j].pipe = pipe;
      command[j].fd = fileno(pipe);
      }

   while(true)
      {
      int max_fd = 0;
      fd_set read_set;
      bool commands_left = false;

      FD_ZERO(&read_set);
      for(u32bit j = 0; j != command.size(); j++)
         {
         if(command[j].pipe)
            {
            FD_SET(command[j].fd, &read_set);
            max_fd = std::max(max_fd, command[j].fd);
            commands_left = true;
            }
         }

      if(!commands_left)
         break;

      struct timeval tv;
      tv.tv_sec = 0;
      tv.tv_usec = 10000;

      add_timestamp();
      if(select(max_fd+1, &read_set, 0, 0, &tv) == -1)
         break;
      add_timestamp();

      SecureVector<byte> read_buf(DEFAULT_BUFFERSIZE);
      for(u32bit j = 0; j != command.size(); j++)
         {
         if(!FD_ISSET(command[j].fd, &read_set))
            continue;

         u32bit total_read = 0;
         while(!ferror(command[j].pipe) && !feof(command[j].pipe))
            {
            size_t got = fread(read_buf, 1, read_buf.size(), command[j].pipe);
            add_bytes(read_buf, got);
            total_read += got;
            }

         if(pclose(command[j].pipe) == 127*256 || total_read < MIN_OUTPUT)
            sources[command[j].index].working = false;
         command[j].pipe = 0;
         }
      }
   }

}
