/*
* wakelist.c - dnssec-trigger OSX sleep and wake listener.
*
* Copyright (c) 2013, NLnet Labs. All rights reserved.
*
* This software is open source.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the NLNET LABS nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* \file
*
* This file contains the OSX sleep and wakeup listener service.
*/
#include "config.h"
#include "riggerd/cfg.h"
#include "riggerd/log.h"
#include "osx/wakelist.h"
#include <pthread.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <mach/mach_port.h>
#include <mach/mach_interface.h>
#include <mach/mach_init.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
/* a reference to the Root Power Domain IOService */
static io_connect_t root_port;
static char* unbound_control;
/* perform unbound command, from OSX thread */
static void
osx_ub_ctrl(const char* cmd)
{
char s[12000];
int r;
verbose(VERB_ALGO, "system %s %s", unbound_control, cmd);
snprintf(s, sizeof(s), "%s %s", unbound_control, cmd);
r = system(s);
if(r == -1) {
log_err("system(%s) failed: %s", s, strerror(errno));
} else if(r != 0) {
log_warn("unbound-control exited with status %d, cmd: %s", r, s);
}
}
/* called when OSX power status changes notifications happen */
static void
sleepcallback(void* ATTR_UNUSED(arg), io_service_t ATTR_UNUSED(service),
natural_t messageType, void* messageArgument )
{
verbose(VERB_ALGO, "OSX ioservice messageType %08lx, arg %08lx\n",
(long unsigned int)messageType,
(long unsigned int)messageArgument );
if(messageType == kIOMessageCanSystemSleep) {
/* allow the system to sleep */
IOAllowPowerChange(root_port, (long)messageArgument);
} else if(messageType == kIOMessageSystemWillSleep) {
/* do not delay sleep */
IOAllowPowerChange(root_port, (long)messageArgument);
} else if(messageType == kIOMessageSystemWillPowerOn) {
/* system has started the wake up process */
/* assume we are on a new network,
* the ping times, and timeouts are no longer valid.
* bogus information (due to timeouts) may no longer be,
* and long-running timeouts on the request list removed. */
osx_ub_ctrl("flush_infra all");
osx_ub_ctrl("flush_bogus");
osx_ub_ctrl("flush_requestlist");
/* not sure if the ethernet device is already available
* at this stage, but the finishwake is about 2 seconds
* away, so this fits within 'normal' timeouts.
* we flush the caches now so that unbound starts
* giving responses during the wakeup.
* The DNS cache in unbound is left intact. */
} else if(messageType == kIOMessageSystemHasPoweredOn) {
/* system has finished the wake up process */
}
}
static void*
osx_wakesleep_thread(void* ATTR_UNUSED(arg))
{
/* attach sleep and wake listener as described in apple note QA1340.
* http://developer.apple.com/library/mac/#qa/qa1340/_index.html
* This creates a thread that listens to the sleep and wake up
* notifications (it allows sleep), and notifies dnssec-trigger about
* these events. The thread is killed when the process exits.
*/
/* notification port allocated by IORegisterForSystemPower */
IONotificationPortRef notifyPortRef;
/* notifier object, used to deregister later,
* but we let the system cleanup this thread */
io_object_t notifierObject;
/* register to receive system sleep notifications */
root_port = IORegisterForSystemPower(NULL/*myarg*/, ¬ifyPortRef,
sleepcallback, ¬ifierObject);
if(!root_port) {
/* could this be a permission issue? if so, just exit thread,
* and do not monitor for system power changes */
log_err("IORegisterForSystemPower failed: %s", strerror(errno));
return NULL;
}
/* add notification port to the runloop */
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes );
/* run the loop to get notifications. */
CFRunLoopRun();
/* free(unbound_control) */
/* deregister and cleanup notification */
/* ENOTREACH */
return NULL;
}
void osx_wakelistener_start(struct cfg* cfg)
{
/* create the thread */
/* marks the thread as detached, so that on exit() the thread can
* get removed by the operating system quickly */
pthread_attr_t attr;
pthread_t id;
if(cfg->noaction)
return;
/* copy cfg elements of interest for threadsafe access.
* The cfg itself can be destroyed when sighup causes a reload. */
if(cfg->unbound_control)
unbound_control = strdup(cfg->unbound_control);
else unbound_control = strdup("unbound-control");
if(!unbound_control)
fatal_exit("malloc failure");
if(pthread_attr_init(&attr) != 0)
fatal_exit("osxsleepwake: could not pthread_attr_init");
if(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0)
fatal_exit("osxsleepwake: could not pthread_attr_setdetach");
if(pthread_create(&id, &attr, osx_wakesleep_thread, NULL) != 0)
fatal_exit("osxsleepwake: could not pthread_create");
if(pthread_attr_destroy(&attr) != 0)
fatal_exit("osxsleepwake: could not pthread_attr_destroy");
}