caffeinate.c   [plain text]


/*
 * Copyright (c) 2010 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * 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@
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <dispatch/dispatch.h>
#include <CoreFoundation/CFNumber.h>

#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/pwr_mgt/IOPMLibPrivate.h>

typedef enum {
    kDefaultAssertionFlag   = 0,
    kIdleAssertionFlag      = (1 << 0),
    kDisplayAssertionFlag   = (1 << 1),
    kSystemAssertionFlag    = (1 << 2)
} AssertionFlag;

typedef enum {
   kDefaultPropertyFlag     = 0,
   kAssertionOnBattFlag     = (1 << 0)
} PropertyFlag;
typedef struct {
    AssertionFlag assertionFlag;
    CFStringRef assertionType;
} AssertionMapEntry;


AssertionMapEntry assertionMap[] = {
    { kIdleAssertionFlag,       kIOPMAssertionTypePreventUserIdleSystemSleep },
    { kDisplayAssertionFlag,    kIOPMAssertionTypePreventUserIdleDisplaySleep },
    { kSystemAssertionFlag,     kIOPMAssertionTypePreventSystemSleep}};


typedef struct {
    PropertyFlag  propertyFlag;
    CFStringRef   propertyType;
    CFTypeRef     propertyVal;
} PropertyMapEntry;

static CFStringRef        kHumanReadableReason = CFSTR("THE CAFFEINATE TOOL IS PREVENTING SLEEP.");
static CFStringRef        kLocalizationBundlePath = CFSTR("/System/Library/CoreServices/powerd.bundle");

#define kAssertionNameString    "caffeinate command-line tool"

int createAssertions(const char *progname, AssertionFlag flags, PropertyFlag  propertyFlags);
void forkChild(char *argv[], AssertionFlag flag, PropertyFlag  propertyFlags);
void usage(void);

int
main(int argc, char *argv[])
{
    AssertionFlag flags = kDefaultAssertionFlag;
    PropertyFlag  propFlags = kDefaultPropertyFlag;
    char ch;

    while ((ch = getopt(argc, argv, "dhisb")) != -1) {
        switch((char)ch) {
            case 'd':
                flags |= kDisplayAssertionFlag;
                break;
            case 'h':
                usage();
                exit(0);
            case 'i':
                flags |= kIdleAssertionFlag;
                break;
            case 's':
                flags |= kSystemAssertionFlag;
                break;
            case 'b':
                propFlags |= kAssertionOnBattFlag;
                break;
            case '?':
            default:
                usage();
                exit(1);
        }
    }

    if (flags == kDefaultAssertionFlag) {
        flags = kIdleAssertionFlag;
    }

    if (argc - optind) {
        argv += optind;
        (void) forkChild(argv, flags, propFlags);
    } else {
        if (createAssertions(NULL, flags, propFlags)) {
            exit(1);
        }
    }

    dispatch_main();
}

int
createAssertions(const char *progname, AssertionFlag flags, PropertyFlag propFlags)
{
    IOReturn result = 1;
    char assertionDetails[128];
    CFStringRef assertionDetailsString = NULL;
    IOPMAssertionID assertionID = 0;
    u_int i = 0, j = 0;
    PropertyMapEntry propertiesMap[] = {
      {kAssertionOnBattFlag, kIOPMAssertionAppliesToLimitedPowerKey, (CFBooleanRef)kCFBooleanTrue}
    };


    if (progname) {
        (void)snprintf(assertionDetails, sizeof(assertionDetails),
            "caffeinate asserting on behalf of %s", progname);
    } else {
        (void)snprintf(assertionDetails, sizeof(assertionDetails),
            "caffeinate asserting forever");
    }

    assertionDetailsString = CFStringCreateWithCString(kCFAllocatorDefault,
                assertionDetails, kCFStringEncodingMacRoman);
    if (!assertionDetailsString) {
        fprintf(stderr, "Failed to create assertion name %s\n", progname);
        goto finish;
    }

    for (i = 0; i < sizeof(assertionMap)/sizeof(AssertionMapEntry); ++i) 
    {
        AssertionMapEntry *entry = assertionMap + i;

        if (!(flags & entry->assertionFlag)) continue;

        result = IOPMAssertionCreateWithDescription(entry->assertionType, 
                    CFSTR(kAssertionNameString), assertionDetailsString, 
                    kHumanReadableReason, kLocalizationBundlePath, 0.0, NULL, &assertionID);
        
        if (result != kIOReturnSuccess) 
        {
            fprintf(stderr, "Failed to create %s assertion\n",
                CFStringGetCStringPtr(entry->assertionType, kCFStringEncodingMacRoman));
            goto finish;
        }

        for (j = 0; j < sizeof(propertiesMap)/sizeof(PropertyMapEntry); j++) 
        {
           if ( !(propFlags & propertiesMap[j].propertyFlag) ) continue;

           result = IOPMAssertionSetProperty(assertionID, 
                     propertiesMap[j].propertyType,
                     propertiesMap[j].propertyVal);
           if (result != kIOReturnSuccess)
           {
               fprintf(stderr, "Failed to set property %s on assertion %s\n",
                   CFStringGetCStringPtr(propertiesMap[j].propertyType, kCFStringEncodingMacRoman), 
                   CFStringGetCStringPtr(entry->assertionType, kCFStringEncodingMacRoman));
               continue;
           }

        }

    }

    result = kIOReturnSuccess;
finish:
    if (assertionDetailsString) CFRelease(assertionDetailsString);

    return result;
}

void
forkChild(char *argv[], AssertionFlag flags, PropertyFlag propFlags)
{
    pid_t pid;
    dispatch_source_t source;

    switch(pid = fork()) {
        case -1:    /* error */
            perror("");
            exit(1);
            /* NOTREACHED */
        case 0:     /* child */
            if (createAssertions(*argv, flags, propFlags)) {
                _exit(1);
            }
            execvp(*argv, argv);
            perror(*argv);
            _exit((errno == ENOENT) ? 127 : 126);
            /* NOTREACHED */
    }

    /* parent */

    (void)signal(SIGINT, SIG_IGN);
    (void)signal(SIGQUIT, SIG_IGN);

    source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid,
        DISPATCH_PROC_EXIT, dispatch_get_main_queue());
    dispatch_source_set_event_handler(source, ^{
        int status;

        if (waitpid(pid, &status, 0) < 0) {
            perror("");
            exit(1);
        }

        exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
    });
    dispatch_resume(source);

    return;
}

void
usage(void)
{
    fprintf(stderr, "usage: caffeinate [-dis] [command] [arguments]\n");
    return;
}