/**
 * 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@
 * 
 * Copyright (c) 1999-2003 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 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ 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, QUIET ENJOYMENT 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: load the display bundle at the specified path\n"
                     "\t-u: unload the display bundle\n");
    exit(1);
}

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

int main (int argc, char *argv[])
{
    int		anExitCode   = 0;
    int		aVerboseFlag = 0;
    int		anAction     = kActionConsoleMessage;
    char*	aProgram     = argv[0];
    char*	anArgCStr    = NULL;
    
    /**
     * Handle command line.
     **/
    {
        char c;
        while ((c = getopt(argc, argv, "?vSFs:f:q:b:u")) != -1) {
            switch (c) {
		/* Usage */
                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':
                    anAction  = kActionLoadDisplayBundle;
                    anArgCStr = optarg;
                    break;
                
                case 'u':
                    anAction  = kActionUnloadDisplayBundle;
                    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) ||
        (anAction == kActionLoadDisplayBundle                      && argc != 0) ||
        (anAction == kActionUnloadDisplayBundle                    && 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 == kActionLoadDisplayBundle && anArg)
              {
                CFDictionarySetValue(anIPCMessage, kIPCMessageKey, kIPCLoadDisplayBundleMessage);
                CFDictionarySetValue(anIPCMessage, kIPCDisplayBundlePathKey, anArg);
              }
            else if (anAction == kActionUnloadDisplayBundle)
              {
                CFDictionarySetValue(anIPCMessage, kIPCMessageKey, kIPCUnloadDisplayBundleMessage);
              }
            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(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(CFMachPortRef port, void *aPtr, CFIndex aSize, void *aReply)
{
	/* Does nothing. */
}

static void replyCallback(CFMachPortRef port, void *aPtr, CFIndex aSize, 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, 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;
}

