//===- llvm/System/Unix/Program.cpp -----------------------------*- C++ -*-===//
// 
//                     The LLVM Compiler Infrastructure
//
// This file was developed by Reid Spencer and is distributed under the 
// University of Illinois Open Source License. See LICENSE.TXT for details.
// 
//===----------------------------------------------------------------------===//
//
// This file implements the Unix specific portion of the Program class.
//
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
//=== WARNING: Implementation here must contain only generic UNIX code that
//===          is guaranteed to work on *all* UNIX variants.
//===----------------------------------------------------------------------===//

#include <llvm/Config/config.h>
#include "Unix.h"
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

extern char** environ;

namespace llvm {
using namespace sys;

// This function just uses the PATH environment variable to find the program.
Path
Program::FindProgramByName(const std::string& progName) {

  // Check some degenerate cases
  if (progName.length() == 0) // no program
    return Path();
  Path temp;
  if (!temp.setFile(progName)) // invalid name
    return Path();
  if (temp.executable()) // already executable as is
    return temp;

  // At this point, the file name is valid and its not executable
 
  // Get the path. If its empty, we can't do anything to find it.
  const char *PathStr = getenv("PATH");
  if (PathStr == 0) 
    return Path();

  // Now we have a colon separated list of directories to search; try them.
  unsigned PathLen = strlen(PathStr);
  while (PathLen) {
    // Find the first colon...
    const char *Colon = std::find(PathStr, PathStr+PathLen, ':');

    // Check to see if this first directory contains the executable...
    Path FilePath;
    if (FilePath.setDirectory(std::string(PathStr,Colon))) {
      FilePath.appendFile(progName);
      if (FilePath.executable())
        return FilePath;                    // Found the executable!
    }

    // Nope it wasn't in this directory, check the next path in the list!
    PathLen -= Colon-PathStr;
    PathStr = Colon;

    // Advance past duplicate colons
    while (*PathStr == ':') {
      PathStr++;
      PathLen--;
    }
  }
  return Path();
}

//
int 
Program::ExecuteAndWait(const Path& path, 
                        const std::vector<std::string>& args) {
  if (!path.executable())
    throw path.toString() + " is not executable"; 

#ifdef HAVE_SYS_WAIT_H
  // Create local versions of the parameters that can be passed into execve()
  // without creating const problems.
  const char* argv[ args.size() + 2 ];
  unsigned index = 0;
  std::string progname(path.getLast());
  argv[index++] = progname.c_str();
  for (unsigned i = 0; i < args.size(); i++)
    argv[index++] = args[i].c_str();
  argv[index] = 0;

  // Create a child process.
  switch (fork()) {
    // An error occured:  Return to the caller.
    case -1:
      ThrowErrno(std::string("Couldn't execute program '") + path.toString() + 
                 "'");
      break;

    // Child process: Execute the program.
    case 0:
      execve (path.c_str(), (char** const)argv, environ);
      // If the execve() failed, we should exit and let the parent pick up
      // our non-zero exit status.
      exit (errno);

    // Parent process: Break out of the switch to do our processing.
    default:
      break;
  }

  // Parent process: Wait for the child process to terminate.
  int status;
  if ((::wait (&status)) == -1)
    ThrowErrno(std::string("Failed waiting for program '") + path.toString() 
               + "'");

  // If the program exited normally with a zero exit status, return success!
  if (WIFEXITED (status))
    return WEXITSTATUS(status);
  else if (WIFSIGNALED(status))
    throw std::string("Program '") + path.toString() + 
          "' received terminating signal.";
  else
    return 0;
    
#else
  throw std::string("Program::ExecuteAndWait not implemented on this platform!\n");
#endif

}

}
// vim: sw=2 smartindent smarttab tw=80 autoindent expandtab
