1/*
2 * Copyright (c) 1998-2013 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include "DACommand.h"
25
26#include "DABase.h"
27#include "DAInternal.h"
28
29#include <fcntl.h>
30#include <paths.h>
31#include <pthread.h>
32#include <sysexits.h>
33#include <unistd.h>
34#include <mach/mach.h>
35#include <sys/wait.h>
36
37enum
38{
39    __kDACommandRunLoopSourceJobKindExecute = 0x00000001
40};
41
42typedef UInt32 __DACommandRunLoopSourceJobKind;
43
44struct __DACommandRunLoopSourceJob
45{
46    __DACommandRunLoopSourceJobKind      kind;
47    struct __DACommandRunLoopSourceJob * next;
48
49    union
50    {
51        struct
52        {
53            pid_t                    pid;
54            int                      pipe;
55            DACommandExecuteCallback callback;
56            void *                   callbackContext;
57        } execute;
58    };
59};
60
61typedef struct __DACommandRunLoopSourceJob __DACommandRunLoopSourceJob;
62
63static __DACommandRunLoopSourceJob * __gDACommandRunLoopSourceJobs = NULL;
64static pthread_mutex_t	             __gDACommandRunLoopSourceLock = PTHREAD_MUTEX_INITIALIZER;
65static CFMachPortRef                 __gDACommandRunLoopSourcePort = NULL;
66
67static void __DACommandExecute( char * const *           argv,
68                                UInt32                   options,
69                                uid_t                    userUID,
70                                gid_t                    userGID,
71                                DACommandExecuteCallback callback,
72                                void *                   callbackContext )
73{
74    /*
75     * Execute a command as the specified user.  The argument list must be NULL terminated.
76     */
77
78    pid_t           executablePID = 0;
79    int             outputPipe[2] = { -1, -1 };
80    int             status        = EX_OK;
81
82    /*
83     * State our assumptions.
84     */
85
86    assert( __gDACommandRunLoopSourcePort );
87
88    /*
89     * Create a pipe in order to capture the executable output.
90     */
91
92    if ( ( options & kDACommandExecuteOptionCaptureOutput ) )
93    {
94        status = pipe( outputPipe );
95        if ( status )  { status = EX_NOINPUT; goto __DACommandExecuteErr; }
96    }
97
98    /*
99     * Fork in order to run the executable.
100     */
101
102    pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
103
104    executablePID = fork( );
105
106    if ( executablePID == 0 )
107    {
108        int fd;
109
110        /*
111         * Prepare the post-fork execution environment.
112         */
113
114        setgid( userGID );
115        setuid( userUID );
116
117        for ( fd = getdtablesize() - 1; fd > -1; fd-- )
118        {
119            if ( fd != outputPipe[1] )
120            {
121                close( fd );
122            }
123        }
124
125        fd = open( _PATH_DEVNULL, O_RDWR, 0 );
126
127        if ( fd != -1 )
128        {
129            dup2( fd, STDIN_FILENO );
130            dup2( fd, STDOUT_FILENO );
131            dup2( fd, STDERR_FILENO );
132
133            if ( fd > 2 )
134            {
135                close( fd );
136            }
137        }
138
139        if ( outputPipe[1] != -1 )
140        {
141            dup2( outputPipe[1], STDOUT_FILENO );
142
143            close( outputPipe[1] );
144        }
145
146        /*
147         * Run the executable.
148         */
149
150        execv( argv[0], argv );
151
152        _exit( EX_OSERR );
153    }
154
155    if ( executablePID != -1 )
156    {
157        /*
158         * Register this callback job on our queue.
159         */
160
161        if ( callback )
162        {
163            __DACommandRunLoopSourceJob * job;
164
165            job = malloc( sizeof( __DACommandRunLoopSourceJob ) );
166
167            if ( job )
168            {
169                job->kind = __kDACommandRunLoopSourceJobKindExecute;
170                job->next = __gDACommandRunLoopSourceJobs;
171
172                job->execute.pid             = executablePID;
173                job->execute.pipe            = ( outputPipe[0] != -1 ) ? dup( outputPipe[0] ) : -1;
174                job->execute.callback        = callback;
175                job->execute.callbackContext = callbackContext;
176
177                __gDACommandRunLoopSourceJobs = job;
178            }
179        }
180    }
181
182    pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
183
184    if ( executablePID == -1 )  { status = EX_OSERR; goto __DACommandExecuteErr; }
185
186    /*
187     * Release our resources.
188     */
189
190__DACommandExecuteErr:
191
192    if ( outputPipe[0] != -1 )  close( outputPipe[0] );
193    if ( outputPipe[1] != -1 )  close( outputPipe[1] );
194
195    /*
196     * Complete the call in case we had a local failure.
197     */
198
199    if ( status )
200    {
201        if ( callback )
202        {
203            ( callback )( status, NULL, callbackContext );
204        }
205    }
206}
207
208static void __DACommandRunLoopSourceCallback( CFMachPortRef port, void * message, CFIndex messageSize, void * info )
209{
210    /*
211     * Process a DACommand CFRunLoopSource fire.  __DACommandSignal() triggers the fire when
212     * a child exits or stops.  We locate the appropriate callback candidate in our job list
213     * and issue the callback.
214     */
215
216    pid_t pid;
217    int   status;
218
219    /*
220     * Scan through exited or stopped children.
221     */
222
223    while ( ( pid = waitpid( -1, &status, WNOHANG ) ) > 0 )
224    {
225        __DACommandRunLoopSourceJob * job     = NULL;
226        __DACommandRunLoopSourceJob * jobLast = NULL;
227
228        pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
229
230        /*
231         * Scan through job list.
232         */
233
234        for ( job = __gDACommandRunLoopSourceJobs; job; jobLast = job, job = job->next )
235        {
236            assert( job->kind == __kDACommandRunLoopSourceJobKindExecute );
237
238            if ( job->execute.pid == pid )
239            {
240                /*
241                 * Process the job's callback.
242                 */
243
244                CFMutableDataRef output = NULL;
245
246                if ( jobLast )
247                {
248                    jobLast->next = job->next;
249                }
250                else
251                {
252                    __gDACommandRunLoopSourceJobs = job->next;
253                }
254
255                pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
256
257                /*
258                 * Capture the executable's output, or the last remains of it, from the pipe.
259                 */
260
261                if ( job->execute.pipe != -1 )
262                {
263                    output = CFDataCreateMutable( kCFAllocatorDefault, 0 );
264
265                    if ( output )
266                    {
267                        UInt8 * buffer;
268
269                        buffer = malloc( PIPE_BUF );
270
271                        if ( buffer )
272                        {
273                            int count;
274
275                            while ( ( count = read( job->execute.pipe, buffer, PIPE_BUF ) ) > 0 )
276                            {
277                                CFDataAppendBytes( output, buffer, count );
278                            }
279
280                            free( buffer );
281                        }
282                    }
283
284                    close( job->execute.pipe );
285                }
286
287                /*
288                 * Issue the callback.
289                 */
290
291                status = WIFEXITED( status ) ? ( ( char ) WEXITSTATUS( status ) ) : status;
292
293                ( job->execute.callback )( status, output, job->execute.callbackContext );
294
295                /*
296                 * Release our resources.
297                 */
298
299                if ( output )
300                {
301                    CFRelease( output );
302                }
303
304                free( job );
305
306                pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
307
308                break;
309            }
310        }
311
312        pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
313    }
314}
315
316static void __DACommandSignal( int sig )
317{
318    /*
319     * Process a SIGCHLD signal.  mach_msg() is safe from a signal handler.
320     */
321
322    mach_msg_header_t message;
323    kern_return_t     status;
324
325    message.msgh_bits        = MACH_MSGH_BITS( MACH_MSG_TYPE_COPY_SEND, 0 );
326    message.msgh_id          = 0;
327    message.msgh_local_port  = MACH_PORT_NULL;
328    message.msgh_remote_port = CFMachPortGetPort( __gDACommandRunLoopSourcePort );
329    message.msgh_reserved    = 0;
330    message.msgh_size        = sizeof( message );
331
332    status = mach_msg( &message, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL );
333
334    if ( status == MACH_SEND_TIMED_OUT )
335    {
336        mach_msg_destroy( &message );
337    }
338}
339
340CFRunLoopSourceRef DACommandCreateRunLoopSource( CFAllocatorRef allocator, CFIndex order )
341{
342    /*
343     * Create a CFRunLoopSource for DACommand callbacks.
344     */
345
346    CFRunLoopSourceRef source = NULL;
347
348    pthread_mutex_lock( &__gDACommandRunLoopSourceLock );
349
350    /*
351     * Initialize our minimal state.
352     */
353
354    if ( __gDACommandRunLoopSourcePort == NULL )
355    {
356        /*
357         * Create the global CFMachPort.  It will be used to post jobs to the run loop.
358         */
359
360        __gDACommandRunLoopSourcePort = CFMachPortCreate( kCFAllocatorDefault, __DACommandRunLoopSourceCallback, NULL, NULL );
361
362        if ( __gDACommandRunLoopSourcePort )
363        {
364            /*
365             * Set up the global CFMachPort.  It requires no more than one queue element.
366             */
367
368            mach_port_limits_t limits = { 0 };
369
370            limits.mpl_qlimit = 1;
371
372            mach_port_set_attributes( mach_task_self( ),
373                                      CFMachPortGetPort( __gDACommandRunLoopSourcePort ),
374                                      MACH_PORT_LIMITS_INFO,
375                                      ( mach_port_info_t ) &limits,
376                                      MACH_PORT_LIMITS_INFO_COUNT );
377        }
378
379        if ( __gDACommandRunLoopSourcePort )
380        {
381            /*
382             * Set up the global signal handler to catch child status changes from BSD.
383             */
384
385            sig_t sig;
386
387            sig = signal( SIGCHLD, __DACommandSignal );
388
389            if ( sig == SIG_ERR )
390            {
391                CFRelease( __gDACommandRunLoopSourcePort );
392
393                __gDACommandRunLoopSourcePort = NULL;
394            }
395        }
396    }
397
398    /*
399     * Obtain the CFRunLoopSource for our CFMachPort.
400     */
401
402    if ( __gDACommandRunLoopSourcePort )
403    {
404        source = CFMachPortCreateRunLoopSource( allocator, __gDACommandRunLoopSourcePort, order );
405    }
406
407    pthread_mutex_unlock( &__gDACommandRunLoopSourceLock );
408
409    return source;
410}
411
412void DACommandExecute( CFURLRef                 executable,
413                       DACommandExecuteOptions  options,
414                       uid_t                    userUID,
415                       gid_t                    userGID,
416                       DACommandExecuteCallback callback,
417                       void *                   callbackContext,
418                       ... )
419{
420    /*
421     * Execute a command as the specified user.  The argument list maps to argv[1] and up.  All
422     * arguments in the argument list shall be of type CFTypeRef, which are converted to string
423     * form via CFCopyDescription().  The argument list must be NULL terminated.
424     */
425
426    int         argc      = 0;
427    char **     argv      = NULL;
428    CFTypeRef   argument  = NULL;
429    va_list     arguments;
430    int         status    = EX_OK;
431
432    /*
433     * Construct the list of arguments -- compute argc.
434     */
435
436    va_start( arguments, callbackContext );
437
438    for ( argc = 1; va_arg( arguments, CFTypeRef ); argc++ )  {  }
439
440    va_end( arguments );
441
442    /*
443     * Construct the list of arguments -- allocate argv.
444     */
445
446    argv = malloc( ( argc + 1 ) * sizeof( char * ) );
447    if ( argv == NULL )  { status = EX_SOFTWARE; goto DACommandExecuteErr; }
448
449    memset( argv, 0, ( argc + 1 ) * sizeof( char * ) );
450
451    /*
452     * Construct the list of arguments -- fill out argv[0].
453     */
454
455    argv[0] = ___CFURLCopyFileSystemRepresentation( executable );
456    if ( argv[0] == NULL )  { status = EX_DATAERR; goto DACommandExecuteErr; }
457
458    /*
459     * Construct the list of arguments -- fill out argv[1] through argv[argc].
460     */
461
462    va_start( arguments, callbackContext );
463
464    for ( argc = 1; ( argument = va_arg( arguments, CFTypeRef ) ); argc++ )
465    {
466        CFStringRef string;
467
468        string = CFStringCreateWithFormat( kCFAllocatorDefault, 0, CFSTR( "%@" ), argument );
469
470        if ( string )
471        {
472            argv[argc] = ___CFStringCopyCString( string );
473
474            CFRelease( string );
475        }
476
477        if ( argv[argc] == NULL )  break;
478    }
479
480    va_end( arguments );
481
482    if ( argument )  { status = EX_SOFTWARE; goto DACommandExecuteErr; }
483
484    /*
485     * Run the executable.
486     */
487
488    __DACommandExecute( argv, options, userUID, userGID, callback, callbackContext );
489
490    /*
491     * Release our resources.
492     */
493
494DACommandExecuteErr:
495
496    if ( argv )
497    {
498        for ( argc = 0; argv[argc]; argc++ )
499        {
500            free( argv[argc] );
501        }
502
503        free( argv );
504    }
505
506    /*
507     * Complete the call in case we had a local failure.
508     */
509
510    if ( status )
511    {
512        if ( callback )
513        {
514            ( callback )( status, NULL, callbackContext );
515        }
516    }
517}
518