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