WSLib.m   [plain text]


//
//  WSLib.m
//  Web Sharing Utility Library
//
//  Created by Al Begley on 4/4/11.
//  Copyright 2011 Apple Inc. All rights reserved.
//

#import "WSLib.h"
#include <launch.h>
#include <unistd.h>

#define APACHE_PLIST_PATH @"/System/Library/LaunchDaemons/org.apache.httpd.plist"
#define APACHE_SERVICE_LABEL @"org.apache.httpd"
#define WEBSHARING_DEFINE_STRING @"-D WEBSHARING_ON"
#define MACOSXSERVER_DEFINE_STRING @"-D MACOSXSERVER"
#define WEBSHARING_DEFINE_ARG @"WEBSHARING_ON"
#define HTTPD_PIDFILE @"/var/run/httpd.pid"

static int pid_from_file(NSString *file) {
	int pid = 0;
	NSScanner *scanner = nil;
	NSString *contents = nil;
	NSData *data = [NSData dataWithContentsOfFile:file];
	
	if (data != nil) {
		contents = [[[NSString allocWithZone:NULL] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
	}
	if (contents == nil) {
		return -1;
	}
	scanner = [NSScanner scannerWithString:contents];
	[scanner setCharactersToBeSkipped:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
	[scanner scanInt:&pid];
	return pid;
}

static int wait_for_pid_from_file(NSString* pidFile) {
	int i, returnValue;
	for (i = 0; i < 6 && (returnValue = pid_from_file(pidFile)) < 0; i++) {
		usleep(200000);	// .2 sec. * 6 iterations = give up after 3 sec.
	}
	return returnValue;
}

static BOOL	WSGetLaunchDaemonServiceState(NSString *inLabel)
{
	launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
	launch_data_t jobLabel = launch_data_new_string([inLabel UTF8String]);
	launch_data_dict_insert(msg, jobLabel, LAUNCH_KEY_GETJOB);
	launch_data_t response = launch_msg(msg);
	launch_data_free(msg);
	if (launch_data_get_type(response) == LAUNCH_DATA_DICTIONARY) {
		launch_data_free(response);
		return YES;
	}
	else
		launch_data_free(response);
	return NO;
}	

static BOOL isApacheRunning()
{
	// Must operate without root privileges, so just check httpd.pid contains pid
	pid_t pid = pid_from_file(HTTPD_PIDFILE);
	return (pid > 0);
}
static BOOL WSSetLaunchDaemonServiceState(NSString* serviceFilePath, BOOL state)
{	
	NSTask* task = [[NSTask alloc] init];
	[task setLaunchPath:@"/bin/launchctl"];
	[task setArguments:[NSArray arrayWithObjects:(state ? @"load" : @"unload"), @"-w", serviceFilePath, nil]];
	[task launch];
	[task waitUntilExit];
	return [task terminationStatus] == 0;
}

static NSArray* WSGetLaunchDaemonServiceConfigArgs(NSString *serviceFilePath)
{
	return [[NSDictionary dictionaryWithContentsOfFile: serviceFilePath] objectForKey:@LAUNCH_JOBKEY_PROGRAMARGUMENTS];
}

static BOOL SetLaunchDaemonServiceConfigArgs(NSString *serviceFilePath, NSArray *args)
{
	NSMutableDictionary* serviceDictionary = [NSDictionary dictionaryWithContentsOfFile: serviceFilePath];
	if (!serviceDictionary || !args || !serviceFilePath)
		return NO;
	[serviceDictionary setObject:args forKey:@LAUNCH_JOBKEY_PROGRAMARGUMENTS];
	return [serviceDictionary writeToFile: serviceFilePath atomically:YES];;
}

int WSGetWebSharingState() {
	// Must run without root privieges
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

	BOOL apacheIsRunning = isApacheRunning();
	if (!apacheIsRunning) {
		[pool release];
		return WS_STATE_STOPPED;
	}
	NSArray* args = WSGetLaunchDaemonServiceConfigArgs(APACHE_PLIST_PATH);
	NSString* argString = [args componentsJoinedByString:@" "];
	NSRange range = [argString rangeOfString:WEBSHARING_DEFINE_STRING];
	BOOL webSharingIsDefined = !(range.location == NSNotFound);

	[pool release];
	if (webSharingIsDefined)
		return WS_STATE_RUNNING;
	else
		return WS_STATE_STOPPED;
}
int WSSetWebSharingState(int newState) {
	int returnValue = newState;
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	BOOL jobIsLoaded = WSGetLaunchDaemonServiceState(APACHE_SERVICE_LABEL);
	NSMutableArray* args = [[WSGetLaunchDaemonServiceConfigArgs(APACHE_PLIST_PATH) mutableCopy] autorelease];
	NSString* argString = [args componentsJoinedByString:@" "];
	NSRange range = [argString rangeOfString:WEBSHARING_DEFINE_STRING];
	BOOL webSharingIsDefined = !(range.location == NSNotFound);
	range = [argString rangeOfString:MACOSXSERVER_DEFINE_STRING];
	BOOL macOSXSereverIsDefined = !(range.location == NSNotFound);

	if (newState == WS_STATE_RUNNING) {
		if (jobIsLoaded) {
			if (!webSharingIsDefined) {
				WSSetLaunchDaemonServiceState(APACHE_PLIST_PATH, NO);
				[args addObject:@"-D"];
				[args addObject:WEBSHARING_DEFINE_ARG];
				if (!SetLaunchDaemonServiceConfigArgs(APACHE_PLIST_PATH, args))
					returnValue = WS_STATE_ERROR;
				else {
					WSSetLaunchDaemonServiceState(APACHE_PLIST_PATH, YES);
					if (wait_for_pid_from_file(HTTPD_PIDFILE) <= 0)
						returnValue = WS_STATE_ERROR;
				}
			}
		}
		else {
			if (webSharingIsDefined) {
				WSSetLaunchDaemonServiceState(APACHE_PLIST_PATH, YES);
				if (wait_for_pid_from_file(HTTPD_PIDFILE) <= 0)
					returnValue = WS_STATE_ERROR;
			}
			else {
				[args addObject:@"-D"];
				[args addObject:WEBSHARING_DEFINE_ARG];
				if (!SetLaunchDaemonServiceConfigArgs(APACHE_PLIST_PATH, args))
					returnValue = WS_STATE_ERROR;
				else {
					WSSetLaunchDaemonServiceState(APACHE_PLIST_PATH, YES);
					if (wait_for_pid_from_file(HTTPD_PIDFILE) <= 0)
						returnValue = WS_STATE_ERROR;
				}
			}
		}
	}
	if (newState == WS_STATE_STOPPED) {
		if (jobIsLoaded) {
			if (webSharingIsDefined) {
				WSSetLaunchDaemonServiceState(APACHE_PLIST_PATH, NO);
				NSUInteger defineIndex = [args indexOfObject:WEBSHARING_DEFINE_ARG];
				[args removeObjectAtIndex:defineIndex];
				[args removeObjectAtIndex:defineIndex - 1];
				if (!SetLaunchDaemonServiceConfigArgs(APACHE_PLIST_PATH, args))
					returnValue = WS_STATE_ERROR;
				else if (macOSXSereverIsDefined)
					WSSetLaunchDaemonServiceState(APACHE_PLIST_PATH, YES);
			}
		}
		else {
			if (webSharingIsDefined) {
				NSUInteger defineIndex = [args indexOfObject:WEBSHARING_DEFINE_ARG];
				[args removeObjectAtIndex:defineIndex];
				[args removeObjectAtIndex:defineIndex - 1];
				if (!SetLaunchDaemonServiceConfigArgs(APACHE_PLIST_PATH, args))
					returnValue = WS_STATE_ERROR;
			}
		}
	}
	return returnValue;
	[pool release];
}