/**
 * ConsoleMessage.c - ConsoleMessage main
 * Wilfredo Sanchez  | wsanchez@opensource.apple.com
 * Kevin Van Vechten | kevinvv@uclink4.berkeley.edu
 * $Apple$
 **
 * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.1 (the "License").  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 **
 * The ConsoleMessage utility sends an IPC message to SystemStarter
 * containing the message specified on the command line.  SystemStarter
 * will perform the localization.  The message is also printed to
 * the system log.
 **/

#include <unistd.h>
#include <crt_externs.h>
#include <syslog.h>
#include <mach/mach.h>
#include <mach/message.h>
#include <mach/mach_error.h>
#include <servers/bootstrap.h>
#include <CoreFoundation/CoreFoundation.h>
#include "SystemStarterIPC.h"

static CFDataRef sendIPCMessage(CFStringRef aPortName, CFDataRef aData, CFStringRef aRunLoopMode);

static void     usage() __attribute__((__noreturn__));
static void 
usage()
{
	/* char* aProgram = **_NSGetArgv(); */
	fprintf(stderr, "usage:\n"
		"\tConsoleMessage [-v] <message>\n"
		"\tConsoleMessage [-v] -S\n"
		"\tConsoleMessage [-v] -F\n"
		"\tConsoleMessage [-v] -s <service>\n"
		"\tConsoleMessage [-v] -f <service>\n"
		"\tConsoleMessage [-v] -q <setting>\n"
		"\tConsoleMessage [-v] -b <path>\n"
		"\tConsoleMessage [-v] -u\n"
		"\noptions:\n"
		"\t-v: verbose (prints errors to stdout)\n"
		"\t-S: mark all services as successful\n"
		"\t-F: mark all services as failed\n"
		"\t-s: mark a specific service as successful\n"
		"\t-f: mark a specific service as failed\n"
		"\t-q: query a configuration setting\n"
		"\t-b: (ignored)\n"
		"\t-u: (ignored)\n");
	exit(1);
}

enum {
	kActionConsoleMessage,
	kActionSuccess,
	kActionFailure,
	kActionQuery,
};

int 
main(int argc, char *argv[])
{
	int             anExitCode = 0;
	int             aVerboseFlag = 0;
	int             anAction = kActionConsoleMessage;
	char           *aProgram = argv[0];
	char           *anArgCStr = NULL;
	char            c;
	pid_t		w4lw_pid = 0;
	FILE	       *w4lw_f;

	/**
         * Handle command line.
         **/
	while ((c = getopt(argc, argv, "?vSFs:f:q:b:u")) != -1) {
		switch (c) {
		case '?':
			usage();
			break;
		case 'v':
			aVerboseFlag = 1;
			break;
		case 'S':
			anAction = kActionSuccess;
			anArgCStr = NULL;
			break;
		case 'F':
			anAction = kActionFailure;
			anArgCStr = NULL;
			break;
		case 's':
			anAction = kActionSuccess;
			anArgCStr = optarg;
			break;
		case 'f':
			anAction = kActionFailure;
			anArgCStr = optarg;
			break;
		case 'q':
			anAction = kActionQuery;
			anArgCStr = optarg;
			break;
		case 'b':
			exit(EXIT_SUCCESS);
			break;
		case 'u':
			w4lw_f = fopen("/var/run/waiting4loginwindow.pid", "r");
			if (w4lw_f) {
				fscanf(w4lw_f, "%d\n", &w4lw_pid);
				if (w4lw_pid)
					kill(w4lw_pid, SIGTERM);
			}
			exit(EXIT_SUCCESS);
			break;
		default:
			fprintf(stderr, "ignoring unknown option '-%c'\n", c);
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if ((anAction == kActionConsoleMessage && argc != 1) ||
	    (anAction == kActionSuccess && argc != 0) ||
	    (anAction == kActionFailure && argc != 0) ||
	    (anAction == kActionQuery && argc != 0)) {
		usage();
	}
	if (getuid() != 0) {
		fprintf(stderr, "you must be root to run %s\n", aProgram);
		exit(1);
	} else {
		CFMutableDictionaryRef anIPCMessage = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
					  &kCFTypeDictionaryValueCallBacks);

		if (anIPCMessage) {
			CFStringRef     anArg = NULL;
			CFIndex         aPID = getppid();
			CFNumberRef     aPIDNumber = CFNumberCreate(NULL, kCFNumberCFIndexType, &aPID);

			/*
			 * Parent process id is the process id of the startup
			 * item that called ConsoleMessage.
			 */
			CFDictionarySetValue(anIPCMessage, kIPCProcessIDKey, aPIDNumber);
			CFRelease(aPIDNumber);

			if (anArgCStr) {
				anArg = CFStringCreateWithCString(NULL, anArgCStr, kCFStringEncodingUTF8);
			}
			if (anAction == kActionSuccess || anAction == kActionFailure) {
				CFBooleanRef    aStatus = (anAction == kActionSuccess) ? kCFBooleanTrue : kCFBooleanFalse;
				CFDictionarySetValue(anIPCMessage, kIPCMessageKey, kIPCStatusMessage);
				CFDictionarySetValue(anIPCMessage, kIPCStatusKey, aStatus);
				if (anArg)
					CFDictionarySetValue(anIPCMessage, kIPCServiceNameKey, anArg);
			} else if (anAction == kActionQuery && anArg) {
				CFDictionarySetValue(anIPCMessage, kIPCMessageKey, kIPCQueryMessage);
				CFDictionarySetValue(anIPCMessage, kIPCConfigSettingKey, anArg);
			} else if (anAction == kActionConsoleMessage) {
				char           *aConsoleMessageCStr = argv[0];
				CFStringRef     aConsoleMessage = CFStringCreateWithCString(NULL, aConsoleMessageCStr, kCFStringEncodingUTF8);

				syslog(LOG_INFO, "%s", aConsoleMessageCStr);

				CFDictionarySetValue(anIPCMessage, kIPCMessageKey, kIPCConsoleMessage);
				CFDictionarySetValue(anIPCMessage, kIPCConsoleMessageKey, aConsoleMessage);
				CFRelease(aConsoleMessage);
			}
			if (anArg)
				CFRelease(anArg);

			{
				CFDataRef       aData = CFPropertyListCreateXMLData(NULL, anIPCMessage);
				if (aData) {
					CFDataRef       aResultData = sendIPCMessage(CFSTR(kSystemStarterMessagePort), aData, kCFRunLoopDefaultMode);

					/* aResultData should be ASCIZ */
					if (aResultData) {
						fprintf(stdout, "%s", CFDataGetBytePtr(aResultData));
						CFRelease(aResultData);
					} else {
						char           *aConsoleMessageCStr = argv[0];
						fprintf(stdout, "%s\n", aConsoleMessageCStr);

						if (aVerboseFlag)
							fprintf(stderr, "%s could not connect to SystemStarter.\n", aProgram);
						anExitCode = 0;
					}
					CFRelease(aData);
				} else {
					if (aVerboseFlag)
						fprintf(stderr, "%s: not enough memory to create IPC message.\n", aProgram);
					anExitCode = 1;
				}
			}
		} else {
			if (aVerboseFlag)
				fprintf(stderr, "%s: not enough memory to create IPC message.\n", aProgram);
			anExitCode = 1;
		}
	}
	exit(anExitCode);
}


static void 
dummyCallback(void)
{
}

static void     replyCallback(CFMachPortRef port __attribute__((unused)), void *aPtr, CFIndex aSize __attribute__((unused)), CFDataRef * aReply) {
	SystemStarterIPCMessage *aMessage = (SystemStarterIPCMessage *) aPtr;

	if (aReply != NULL &&
	    aMessage->aProtocol == kIPCProtocolVersion &&
	    aMessage->aByteLength >= 0) {
		*aReply = CFDataCreate(NULL, (char *) aMessage + aMessage->aByteLength, aMessage->aByteLength);
	} else if (aReply != NULL) {
		*aReply = NULL;
	}
}


static CFDataRef 
sendIPCMessage(CFStringRef aPortName, CFDataRef aData, CFStringRef aRunLoopMode)
{
	SystemStarterIPCMessage *aMessage = NULL;
	CFRunLoopSourceRef aSource = NULL;
	CFMachPortRef   aMachPort = NULL, aReplyPort = NULL;
	CFMachPortContext aContext;
	kern_return_t   aKernReturn = KERN_FAILURE;
	mach_port_t     aBootstrapPort, aNativePort;
	char           *aPortNameUTF8String;
	CFDataRef       aReply = NULL;
	SInt32          aStrLen;

	aContext.version = 0;
	aContext.info = (void *) NULL;
	aContext.retain = 0;
	aContext.release = 0;
	aContext.copyDescription = 0;

	/* Look up the remote port by name */

	aStrLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(aPortName) + 1, kCFStringEncodingUTF8);
	aPortNameUTF8String = malloc(aStrLen);
	if (aPortNameUTF8String) {
		CFStringGetCString(aPortName, aPortNameUTF8String, aStrLen, kCFStringEncodingUTF8);
		task_get_bootstrap_port(mach_task_self(), &aBootstrapPort);
		aKernReturn = bootstrap_look_up(aBootstrapPort, aPortNameUTF8String, &aNativePort);
		aMachPort = (KERN_SUCCESS == aKernReturn) ? CFMachPortCreateWithPort(NULL, aNativePort, (CFMachPortCallBack) dummyCallback, &aContext, NULL) : NULL;
		free(aPortNameUTF8String);
	}
	/* Create a reply port and associated run loop source */
	aContext.info = &aReply;
	aReplyPort = CFMachPortCreate(NULL, (CFMachPortCallBack) replyCallback, &aContext, NULL);
	if (aReplyPort) {
		aSource = CFMachPortCreateRunLoopSource(NULL, aReplyPort, 0);
		if (aSource) {
			CFRunLoopAddSource(CFRunLoopGetCurrent(), aSource, aRunLoopMode);
		}
	}
	/* Allocate a buffer for the message */
	if (aData && aMachPort && aReplyPort) {
		SInt32          aSize = (sizeof(SystemStarterIPCMessage) + (CFDataGetLength(aData) + 3)) & ~0x3;
		aMessage = (SystemStarterIPCMessage *) malloc(aSize);
		if (aMessage) {
			aMessage->aHeader.msgh_id = 1;
			aMessage->aHeader.msgh_size = aSize;
			aMessage->aHeader.msgh_remote_port = CFMachPortGetPort(aMachPort);
			aMessage->aHeader.msgh_local_port = CFMachPortGetPort(aReplyPort);
			aMessage->aHeader.msgh_reserved = 0;
			aMessage->aHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
			aMessage->aBody.msgh_descriptor_count = 0;
			aMessage->aProtocol = kIPCProtocolVersion;
			aMessage->aByteLength = CFDataGetLength(aData);
			memmove((uint8_t *) aMessage + sizeof(SystemStarterIPCMessage),
				CFDataGetBytePtr(aData),
				CFDataGetLength(aData));
		}
	}
	/* Wait up to 1 second to send the message */
	if (aMessage) {
		aKernReturn = mach_msg((mach_msg_header_t *) aMessage, MACH_SEND_MSG | MACH_SEND_TIMEOUT, aMessage->aHeader.msgh_size, 0, MACH_PORT_NULL, 1000.0, MACH_PORT_NULL);
		free(aMessage);
	}
	/* Wait up to 30 seconds for the reply */
	if (aSource && aKernReturn == MACH_MSG_SUCCESS) {
		CFRetain(aReplyPort);
		CFRunLoopRunInMode(aRunLoopMode, 30.0, true);
		/*
		 * aReplyPort's replyCallback will set the local aReply
		 * variable
		 */
		CFRelease(aReplyPort);
	}
	if (aMachPort)
		CFRelease(aMachPort);
	if (aReplyPort)
		CFRelease(aReplyPort);
	if (aSource) {
		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), aSource, aRunLoopMode);
		CFRelease(aSource);
	}
	return aReply;
}
