#include "DACommand.h"
#include "DABase.h"
#include "DAInternal.h"
#include <fcntl.h>
#include <paths.h>
#include <pthread.h>
#include <sysexits.h>
#include <unistd.h>
#include <mach/mach.h>
#include <sys/wait.h>
enum
{
__kDACommandRunLoopSourceJobKindExecute = 0x00000001
};
typedef UInt32 __DACommandRunLoopSourceJobKind;
struct __DACommandRunLoopSourceJob
{
__DACommandRunLoopSourceJobKind kind;
struct __DACommandRunLoopSourceJob * next;
union
{
struct
{
pid_t pid;
int pipe;
DACommandExecuteCallback callback;
void * callbackContext;
} execute;
};
};
typedef struct __DACommandRunLoopSourceJob __DACommandRunLoopSourceJob;
static __DACommandRunLoopSourceJob * __gDACommandRunLoopSourceJobs = NULL;
static pthread_mutex_t __gDACommandRunLoopSourceLock = PTHREAD_MUTEX_INITIALIZER;
static CFMachPortRef __gDACommandRunLoopSourcePort = NULL;
static void __DACommandExecute( char * const * argv,
UInt32 options,
uid_t userUID,
gid_t userGID,
DACommandExecuteCallback callback,
void * callbackContext )
{
pid_t executablePID = 0;
int outputPipe[2] = { -1, -1 };
int status = EX_OK;
assert( __gDACommandRunLoopSourcePort );
if ( ( options & kDACommandExecuteOptionCaptureOutput ) )
{
status = pipe( outputPipe );
if ( status ) { status = EX_NOINPUT; goto __DACommandExecuteErr; }
}
pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
executablePID = fork( );
if ( executablePID == 0 )
{
int fd;
setuid( userUID );
setgid( userGID );
for ( fd = getdtablesize() - 1; fd > -1; fd-- )
{
if ( fd != outputPipe[1] )
{
close( fd );
}
}
fd = open( _PATH_DEVNULL, O_RDWR, 0 );
if ( fd != -1 )
{
dup2( fd, STDIN_FILENO );
dup2( fd, STDOUT_FILENO );
dup2( fd, STDERR_FILENO );
if ( fd > 2 )
{
close( fd );
}
}
if ( outputPipe[1] != -1 )
{
dup2( outputPipe[1], STDOUT_FILENO );
}
execv( argv[0], argv );
_exit( EX_OSERR );
}
if ( executablePID != -1 )
{
if ( callback )
{
__DACommandRunLoopSourceJob * job;
job = malloc( sizeof( __DACommandRunLoopSourceJob ) );
if ( job )
{
job->kind = __kDACommandRunLoopSourceJobKindExecute;
job->next = __gDACommandRunLoopSourceJobs;
job->execute.pid = executablePID;
job->execute.pipe = ( outputPipe[0] != -1 ) ? dup( outputPipe[0] ) : -1;
job->execute.callback = callback;
job->execute.callbackContext = callbackContext;
__gDACommandRunLoopSourceJobs = job;
}
}
}
pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
if ( executablePID == -1 ) { status = EX_OSERR; goto __DACommandExecuteErr; }
__DACommandExecuteErr:
if ( outputPipe[0] != -1 ) close( outputPipe[0] );
if ( outputPipe[1] != -1 ) close( outputPipe[1] );
if ( status )
{
if ( callback )
{
( callback )( status, NULL, callbackContext );
}
}
}
static void __DACommandRunLoopSourceCallback( CFMachPortRef port, void * message, CFIndex messageSize, void * info )
{
pid_t pid;
int status;
while ( ( pid = waitpid( -1, &status, WNOHANG ) ) > 0 )
{
__DACommandRunLoopSourceJob * job = NULL;
__DACommandRunLoopSourceJob * jobLast = NULL;
pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
for ( job = __gDACommandRunLoopSourceJobs; job; jobLast = job, job = job->next )
{
assert( job->kind == __kDACommandRunLoopSourceJobKindExecute );
if ( job->execute.pid == pid )
{
CFMutableDataRef output = NULL;
if ( jobLast )
{
jobLast->next = job->next;
}
else
{
__gDACommandRunLoopSourceJobs = job->next;
}
pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
if ( job->execute.pipe != -1 )
{
output = CFDataCreateMutable( kCFAllocatorDefault, 0 );
if ( output )
{
UInt8 * buffer;
buffer = malloc( PIPE_BUF );
if ( buffer )
{
int count;
while ( ( count = read( job->execute.pipe, buffer, PIPE_BUF ) ) > 0 )
{
CFDataAppendBytes( output, buffer, count );
}
free( buffer );
}
}
close( job->execute.pipe );
}
status = WIFEXITED( status ) ? ( ( char ) WEXITSTATUS( status ) ) : status;
( job->execute.callback )( status, output, job->execute.callbackContext );
if ( output )
{
CFRelease( output );
}
free( job );
pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
break;
}
}
pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
}
}
static void __DACommandSignal( int sig )
{
mach_msg_header_t message;
message.msgh_bits = MACH_MSGH_BITS( MACH_MSG_TYPE_COPY_SEND, 0 );
message.msgh_id = 0;
message.msgh_local_port = MACH_PORT_NULL;
message.msgh_remote_port = CFMachPortGetPort( __gDACommandRunLoopSourcePort );
message.msgh_reserved = 0;
message.msgh_size = sizeof( message );
mach_msg( &message, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL );
}
CFRunLoopSourceRef DACommandCreateRunLoopSource( CFAllocatorRef allocator, CFIndex order )
{
CFRunLoopSourceRef source = NULL;
pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
if ( __gDACommandRunLoopSourcePort == NULL )
{
__gDACommandRunLoopSourcePort = CFMachPortCreate( kCFAllocatorDefault, __DACommandRunLoopSourceCallback, NULL, NULL );
if ( __gDACommandRunLoopSourcePort )
{
mach_port_limits_t limits = { 0 };
limits.mpl_qlimit = 1;
mach_port_set_attributes( mach_task_self( ),
CFMachPortGetPort( __gDACommandRunLoopSourcePort ),
MACH_PORT_LIMITS_INFO,
( mach_port_info_t ) &limits,
MACH_PORT_LIMITS_INFO_COUNT );
}
if ( __gDACommandRunLoopSourcePort )
{
sig_t sig;
sig = signal( SIGCHLD, __DACommandSignal );
if ( sig == SIG_ERR )
{
CFRelease( __gDACommandRunLoopSourcePort );
__gDACommandRunLoopSourcePort = NULL;
}
}
}
if ( __gDACommandRunLoopSourcePort )
{
source = CFMachPortCreateRunLoopSource( allocator, __gDACommandRunLoopSourcePort, order );
}
pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
return source;
}
void DACommandExecute( CFURLRef executable,
DACommandExecuteOptions options,
uid_t userUID,
gid_t userGID,
DACommandExecuteCallback callback,
void * callbackContext,
... )
{
int argc = 0;
char ** argv = NULL;
CFTypeRef argument = NULL;
va_list arguments = { 0 };
int status = EX_OK;
va_start( arguments, callbackContext );
for ( argc = 1; va_arg( arguments, CFTypeRef ); argc++ ) { }
va_end( arguments );
argv = malloc( ( argc + 1 ) * sizeof( char * ) );
if ( argv == NULL ) { status = EX_SOFTWARE; goto DACommandExecuteErr; }
memset( argv, 0, ( argc + 1 ) * sizeof( char * ) );
argv[0] = ___CFURLCopyFileSystemRepresentation( executable );
if ( argv[0] == NULL ) { status = EX_DATAERR; goto DACommandExecuteErr; }
va_start( arguments, callbackContext );
for ( argc = 1; ( argument = va_arg( arguments, CFTypeRef ) ); argc++ )
{
CFStringRef string;
string = CFStringCreateWithFormat( kCFAllocatorDefault, 0, CFSTR( "%@" ), argument );
if ( string )
{
argv[argc] = ___CFStringCopyCString( string );
CFRelease( string );
}
if ( argv[argc] == NULL ) break;
}
va_end( arguments );
if ( argument ) { status = EX_SOFTWARE; goto DACommandExecuteErr; }
__DACommandExecute( argv, options, userUID, userGID, callback, callbackContext );
DACommandExecuteErr:
if ( argv )
{
for ( argc = 0; argv[argc]; argc++ )
{
free( argv[argc] );
}
free( argv );
}
if ( status )
{
if ( callback )
{
( callback )( status, NULL, callbackContext );
}
}
}