//
//  NSTask+Path.m
//  MulleScionist
//
//  Created by Nat! on 17.04.14.
//
//

#import "NSTask+MullePATH.h"
#include <string.h>
#include <errno.h>


@implementation NSTask (MullePATH)

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <spawn.h>
#include <paths.h>
#include <errno.h>
#include <crt_externs.h>


//
// this is stolen from FreeBSD and rewitten
/*
 * Copyright (c) 1988, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

typedef struct
{
   struct sigaction   intact;
   struct sigaction   quitact;
   sigset_t           defaultsig;
   sigset_t           oldsigblock;
   int                flags;
} system_context;


// call this before __system and fork

static void  __pre_system( system_context *info)
{
   struct sigaction   ign;
   sigset_t           newsigblock;
   
   sigemptyset( &info->defaultsig);
   
   /*
    * Ignore SIGINT and SIGQUIT, block SIGCHLD. Remember to save
    * existing signal dispositions.
    */
   ign.sa_handler = SIG_IGN;
   sigemptyset( &ign.sa_mask);
   ign.sa_flags = 0;
   
   info->flags = POSIX_SPAWN_SETSIGMASK;
   sigaction(SIGINT, &ign, &info->intact);
   if( info->intact.sa_handler != SIG_IGN)
   {
      sigaddset( &info->defaultsig, SIGINT);
      info->flags |= POSIX_SPAWN_SETSIGDEF;
   }
   
   sigaction( SIGQUIT, &ign, &info->quitact);
   if( info->quitact.sa_handler != SIG_IGN)
   {
      sigaddset( &info->defaultsig, SIGQUIT);
      info->flags |= POSIX_SPAWN_SETSIGDEF;
   }
   
   sigemptyset( &newsigblock);
   sigaddset( &newsigblock, SIGCHLD);
   sigprocmask( SIG_BLOCK, &newsigblock, &info->oldsigblock);
}



// call this after wait
static void  __post_system( system_context *info)
{
   sigaction( SIGINT, &info->intact, NULL);
   sigaction( SIGQUIT,  &info->quitact, NULL);
   sigprocmask( SIG_SETMASK, &info->oldsigblock, NULL);
}


static int   __system( char *command, system_context *info, char  **env)
{
   posix_spawnattr_t   attr;
   char               *argv[] = {"sh", "-c", command, NULL};
   int                 err;
   int                 pid;
   
   if( (err = posix_spawnattr_init( &attr)) != 0)
   {
      errno = err;
      return -1;
   }
   
   posix_spawnattr_setsigmask( &attr, &info->oldsigblock);
   if( info->flags & POSIX_SPAWN_SETSIGDEF)
      posix_spawnattr_setsigdefault( &attr, &info->defaultsig);
   posix_spawnattr_setflags( &attr, info->flags);
   
   err = posix_spawn( &pid, _PATH_BSHELL, NULL, &attr, (char *const *)argv, env);
   posix_spawnattr_destroy( &attr);
   
   if( err != 0)
   {
      errno = err;
      return( -1);
   }
   
   return( pid);
}


static NSString   *environmentString( NSString *key, id value)
{
   return( [NSString stringWithFormat:@"%@=%@", key, [value description]]);
}


char   **mulleEnvironmentARGV( NSDictionary *environment)
{
   NSEnumerator   *rover;
   NSString       *key;
   NSString       *s;
   NSUInteger     i, n;
   char           **env;
   char           *buf;
   size_t         len;
   size_t         total;
   
   n = [environment count];
   if( ! n)
      return( NULL);
   
   len   = 0;
   rover = [environment keyEnumerator];
   while( key = [rover nextObject])
   {
      s    = environmentString( key, [environment objectForKey:key]);
      len += [s cStringLength] + 1;
   }
   
   total  = sizeof( char *) * (n + 1);
   total += len;
   env = (char **) [[NSMutableData dataWithLength:total] mutableBytes];
   if( ! env)
      [NSException raise:NSMallocException
                  format:@"fail"];
   
   buf = (char *) &env[ n + 1];
   i   = 0;
   rover = [environment keyEnumerator];
   while( key = [rover nextObject])
   {
      s = environmentString( key, [environment objectForKey:key]);
      strcpy( buf, [s cString]);
      
      env[ i++] = buf;
      buf       = &buf[ strlen( buf) + 1];
      
   }
   env[ i] = 0;
   
   return( env);
}


+ (NSString *) mulleSystem:(NSString *) command
               environment:(NSDictionary *) environment
           returningStderr:(BOOL) flag
{
   NSMutableString   *s;
   char              **env;
   char              buf[ 256];
   int               fd[ 2];
   int               fdx[ 2];
   int               i;
   int               rval;
   int               status;
   int               wrfd[ 2];
   pid_t             pid;
   ssize_t           len;
   system_context    info;
   
   rval  = pipe( fd);
   rval |= pipe( fdx);
   rval |= pipe( wrfd);
   if( rval)
      [NSException raise:NSInternalInconsistencyException
                  format:@"%s", strerror( errno)];
   
   pid = fork();
   if( pid == -1)
      [NSException raise:NSInternalInconsistencyException
                  format:@"%s", strerror( errno)];
   
   __pre_system( &info);
   
   if( ! pid)
   {
      dup2( fd[ 1],  flag ? STDERR_FILENO : STDOUT_FILENO);
      dup2( fdx[ 1], flag ? STDOUT_FILENO : STDERR_FILENO);
      dup2( wrfd[ 0], STDIN_FILENO);
      
      for( i = 0; i < 2; i++)
      {
         close( fd[ i]);
         close( fdx[ i]);
         close( wrfd[ i]);
      }
      
      env = mulleEnvironmentARGV( environment);

      _exit( __system( (char *) [command cString], &info, env));
   }
   
   close( fd[ 1]);
   close( fdx[ 1]);
   close( wrfd[ 0]);
   
   s = nil;
   for(;;)
   {
      len = read( fd[ 0], buf, 256);
      if( len == -1)
      {
         NSLog( @"%s", strerror( errno));
         s = nil;  // this apparently happens if nothing has been written too...
         break;
      }

      if( ! len)
         break;
      
      if( ! s)
         s = [NSMutableString string];
      [s appendString:[NSString stringWithCString:buf
                                           length:len]];
   }
   
   close( fd[ 0]);
   close( fdx[ 0]);
   close( wrfd[ 1]);
   
   wait( &status);

   __post_system( &info);
   
   return( s);
}


+ (NSString *) mullePATH
{
   return( [self mulleSystem:@"echo $PATH"
                 environment:NULL
             returningStderr:NO]);
}


+ (NSString *) mulleLaunchPathForExecutable:(NSString *) command
                                       PATH:(NSString *) PATH
{
   NSArray         *components;
   NSEnumerator    *rover;
   NSString        *path;
   NSFileManager   *manager;
   
   manager    = [NSFileManager defaultManager];
   components = [PATH componentsSeparatedByString:@":"];
   rover = [components objectEnumerator];
   while( path = [rover nextObject])
   {
      path = [path stringByAppendingPathComponent:command];
      if( [manager isExecutableFileAtPath:path])
         return( path);
   }
   return( nil);
}

@end