NotifyTool.c
/* |
File: NotifyTool.c |
Contains: Sample showing how to use the BSD notify API <x-man-page://3/notify>. |
Written by: DTS |
Copyright: Copyright (c) 2007-2012 Apple Inc. All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. |
("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of |
these terms. If you do not agree with these terms, please do |
not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following |
terms, and subject to these terms, Apple grants you a personal, |
non-exclusive license, under Apple's copyrights in this |
original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or |
without modifications, in source and/or binary forms; provided |
that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the |
following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks |
or logos of Apple Inc. may be used to endorse or promote |
products derived from the Apple Software without specific prior |
written permission from Apple. Except as expressly stated in |
this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or |
by other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. |
APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING |
WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING |
THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, |
INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY |
OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY |
OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR |
OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF |
SUCH DAMAGE. |
*/ |
#include <assert.h> |
#include <errno.h> |
#include <mach/mach.h> |
#include <notify.h> |
#include <stdbool.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <string.h> |
#include <unistd.h> |
#include <dispatch/dispatch.h> |
#include <CoreFoundation/CoreFoundation.h> |
static void PrintUsage(void) |
{ |
fprintf(stderr, "usage: %s post <name>...\n", getprogname()); |
fprintf(stderr, " %*s listen | listenFD <name>...\n", (int) strlen(getprogname()), ""); |
fprintf(stderr, " %*s listenMach <name>...\n", (int) strlen(getprogname()), ""); |
fprintf(stderr, " %*s listenCF <name>...\n", (int) strlen(getprogname()), ""); |
fprintf(stderr, " %*s listenGCD <name>...\n", (int) strlen(getprogname()), ""); |
} |
static const char * NotifyErrorToString(uint32_t noteErr) |
{ |
const char * result; |
static const char * kErrors[] = { |
"NOTIFY_STATUS_OK", |
"NOTIFY_STATUS_INVALID_NAME", |
"NOTIFY_STATUS_INVALID_TOKEN", |
"NOTIFY_STATUS_INVALID_PORT", |
"NOTIFY_STATUS_INVALID_FILE", |
"NOTIFY_STATUS_INVALID_SIGNAL", |
"NOTIFY_STATUS_INVALID_REQUEST", |
"NOTIFY_STATUS_NOT_AUTHORIZED" |
}; |
if (noteErr < (sizeof(kErrors) / sizeof(*kErrors))) { |
result = kErrors[noteErr]; |
} else if (noteErr == NOTIFY_STATUS_FAILED) { |
result = "NOTIFY_STATUS_FAILED"; |
} else { |
result = "unknown error"; |
} |
return result; |
} |
static void PrintNotifyError(const char *operation, const char *noteName, uint32_t noteErr) |
{ |
fprintf( |
stderr, |
"%s: %s: %s (%u)\n", |
noteName, |
operation, |
NotifyErrorToString(noteErr), |
(unsigned int) noteErr |
); |
} |
static int PostNotifications(size_t noteCount, const char **noteNames) |
// Implements the "post" command. Post the noteCount notifications whose |
// names are in the noteNames array. |
{ |
int retVal; |
uint32_t noteErr; |
size_t noteIndex; |
noteErr = NOTIFY_STATUS_OK; |
for (noteIndex = 0; noteIndex < noteCount; noteIndex++) { |
noteErr = notify_post(noteNames[noteIndex]); |
if (noteErr != NOTIFY_STATUS_OK) { |
break; |
} |
} |
if (noteErr == NOTIFY_STATUS_OK) { |
retVal = EXIT_SUCCESS; |
} else { |
PrintNotifyError("post failed", noteNames[noteIndex], noteErr); |
retVal = EXIT_FAILURE; |
} |
return retVal; |
} |
static void PrintToken( |
int token, |
size_t noteCount, |
const int * tokens, |
const char ** noteNames |
) |
// For a given token, search the tokens array looking for a match. If |
// you find one, print the associated notification name. If you don't |
// find one, print a default string. |
{ |
size_t noteIndex; |
bool found; |
found = false; |
for (noteIndex = 0; noteIndex < noteCount; noteIndex++) { |
if (token == tokens[noteIndex]) { |
fprintf(stdout, "%s (%d)\n", noteNames[noteIndex], token); |
found = true; |
} |
} |
if ( ! found ) { |
fprintf(stdout, "??? (%d)\n", token); |
} |
fflush(stdout); |
} |
static int ListenUsingFileDescriptor(size_t noteCount, const char **noteNames) |
// Implements the "listenFD" command. Register for the noteCount |
// notifications whose names are in the noteNames array. Then read |
// the notification file descriptor, printing information about any |
// notifications that arrive. |
{ |
int retVal; |
uint32_t noteErr; |
size_t noteIndex; |
int noteTokens[noteCount]; |
int fd = -1; |
// Register. The first time around this loop fd == -1 and so we don't |
// specify NOTIFY_REUSE. notify_register_file_descriptor then allocates |
// a file descriptor and returns it in fd. For subsequent iterations |
// we /do/ specify NOTIFY_REUSE and notify_register_file_descriptor just |
// reuses the existing fd. |
noteErr = NOTIFY_STATUS_OK; |
for (noteIndex = 0; noteIndex < noteCount; noteIndex++) { |
noteErr = notify_register_file_descriptor( |
noteNames[noteIndex], |
&fd, |
(fd == -1) ? 0 : NOTIFY_REUSE, |
¬eTokens[noteIndex] |
); |
if (noteErr != NOTIFY_STATUS_OK) { |
break; |
} |
} |
if (noteErr != NOTIFY_STATUS_OK) { |
PrintNotifyError("registration failed", noteNames[noteIndex], noteErr); |
retVal = EXIT_FAILURE; |
} else { |
// Listen for and print any incoming notifications. |
fprintf(stdout, "Listening using a file descriptor:\n"); |
fflush(stdout); |
do { |
ssize_t bytesRead; |
int token; |
bytesRead = read(fd, &token, sizeof(token)); |
if (bytesRead == 0) { |
fprintf(stderr, "end of file on notify file descriptor\n"); |
retVal = EXIT_FAILURE; |
break; |
} else if (bytesRead < 0) { |
fprintf(stderr, "read failed: %s (%d)\n", strerror(errno), errno); |
retVal = EXIT_FAILURE; |
break; |
} else { |
// I'm just not up for handling partial reads at this point. |
assert(bytesRead == sizeof(token)); |
// Have to swap to native endianness <rdar://problem/5352778>. |
token = ntohl(token); |
// Find the string associated with this token and print it. |
PrintToken(token, noteCount, noteTokens, noteNames); |
} |
} while (true); |
} |
return retVal; |
} |
static int ListenUsingMach(size_t noteCount, const char **noteNames) |
// Implements the "listenMach" command. Register for the noteCount |
// notifications whose names are in the noteNames array. Then read |
// the notification Mach port, printing information about any |
// notifications that arrive. |
{ |
int retVal; |
uint32_t noteErr; |
size_t noteIndex; |
int noteTokens[noteCount]; |
mach_port_t port = MACH_PORT_NULL; |
// Register. The first time around this loop fd == -1 and so we don't |
// specify NOTIFY_REUSE. notify_register_mach_port then allocates |
// a Mach port and returns it in port. For subsequent iterations |
// we /do/ specify NOTIFY_REUSE and notify_register_mach_port just |
// reuses the existing port. |
noteErr = NOTIFY_STATUS_OK; |
for (noteIndex = 0; noteIndex < noteCount; noteIndex++) { |
noteErr = notify_register_mach_port( |
noteNames[noteIndex], |
&port, |
(port == MACH_PORT_NULL) ? 0 : NOTIFY_REUSE, |
¬eTokens[noteIndex] |
); |
if (noteErr != NOTIFY_STATUS_OK) { |
break; |
} |
} |
if (noteErr != NOTIFY_STATUS_OK) { |
PrintNotifyError("registration failed", noteNames[noteIndex], noteErr); |
retVal = EXIT_FAILURE; |
} else { |
kern_return_t kr; |
mach_msg_empty_rcv_t msg; |
// Listen for and print any incoming notifications. |
fprintf(stdout, "Listening using Mach:\n"); |
fflush(stdout); |
do { |
msg.header.msgh_local_port = port; |
msg.header.msgh_size = sizeof(msg); |
kr = mach_msg_receive(&msg.header); |
if (kr == KERN_SUCCESS) { |
PrintToken(msg.header.msgh_id, noteCount, noteTokens, noteNames); |
} |
} while (kr == KERN_SUCCESS); |
fprintf(stderr, "error reading Mach message: %s (0x%x)\n", mach_error_string(kr), kr); |
retVal = EXIT_FAILURE; |
} |
return retVal; |
} |
struct MyCFMachPortCallBackInfo { |
OSType magic; // must be 'CFpI' |
size_t noteCount; |
const int * noteTokens; |
const char ** noteNames; |
}; |
typedef struct MyCFMachPortCallBackInfo MyCFMachPortCallBackInfo; |
static void MyCFMachPortCallBack( |
CFMachPortRef port, |
void * msg, |
CFIndex size, |
void * info |
) |
// The callback associated with the CFMachPort. This get called out of the |
// runloop when a message arrives on the notification port. We just |
// extrac the token (msgh_id) and call print it. |
{ |
#pragma unused(port) |
#pragma unused(size) |
const MyCFMachPortCallBackInfo * myInfo; |
myInfo = (const MyCFMachPortCallBackInfo *) info; |
assert(myInfo->magic == 'CFpI'); |
PrintToken( |
((const mach_msg_header_t *) msg)->msgh_id, |
myInfo->noteCount, |
myInfo->noteTokens, |
myInfo->noteNames |
); |
} |
static int ListenUsingCoreFoundation(size_t noteCount, const char **noteNames) |
// Implements the "listenCF" command. Register for the noteCount |
// notifications whose names are in the noteNames array. Then wrap the |
// notification Mach port in a CFMachPort and use CF to read the notification |
// messages, printing the information about any notifications that arrive |
// from our CFMachPort callback. |
{ |
int retVal; |
uint32_t noteErr; |
size_t noteIndex; |
int noteTokens[noteCount]; |
mach_port_t port = MACH_PORT_NULL; |
// Register. The first time around this loop fd == -1 and so we don't |
// specify NOTIFY_REUSE. notify_register_mach_port then allocates |
// a Mach port and returns it in port. For subsequent iterations |
// we /do/ specify NOTIFY_REUSE and notify_register_mach_port just |
// reuses the existing port. |
noteErr = NOTIFY_STATUS_OK; |
for (noteIndex = 0; noteIndex < noteCount; noteIndex++) { |
noteErr = notify_register_mach_port( |
noteNames[noteIndex], |
&port, |
(port == MACH_PORT_NULL) ? 0 : NOTIFY_REUSE, |
¬eTokens[noteIndex] |
); |
if (noteErr != NOTIFY_STATUS_OK) { |
break; |
} |
} |
if (noteErr != NOTIFY_STATUS_OK) { |
PrintNotifyError("registration failed", noteNames[noteIndex], noteErr); |
retVal = EXIT_FAILURE; |
} else { |
MyCFMachPortCallBackInfo myInfo; |
CFMachPortContext context = { 0 , NULL, NULL, NULL, NULL }; |
CFMachPortRef cfPort; |
Boolean shouldFreeInfo; |
CFRunLoopSourceRef rls; |
// Set up the context structure for MyCFMachPortCallBack. |
myInfo.magic = 'CFpI'; |
myInfo.noteCount = noteCount; |
myInfo.noteTokens = noteTokens; |
myInfo.noteNames = noteNames; |
// Create the CFMachPort. |
context.info = &myInfo; |
cfPort = CFMachPortCreateWithPort( |
NULL, |
port, |
MyCFMachPortCallBack, |
&context, |
&shouldFreeInfo |
); |
assert(cfPort != NULL); |
// There can only be one CFMachPort for a given Mach port name. Thus, |
// if someone had already created a CFMachPort for "port", CFMachPort |
// would not create a new CFMachPort but, rather, return the existing |
// CFMachPort with the retain count bumped. In that case it hasn't |
// taken any 'reference' on the data in context; the context.info |
// on the /previous/ CFMachPort is still in use, but the context.info |
// that we supply is now superfluous. In that case it returns |
// shouldFreeInfo, telling us that we don't need to hold on to this |
// information. |
// |
// In this specific case no one should have already created a CFMachPort |
// for "port", so shouldFreeInfo should never be true. If it is, it's |
// time to worry! |
assert( ! shouldFreeInfo ); |
// Add it to the run loop. |
rls = CFMachPortCreateRunLoopSource(NULL, cfPort, 0); |
assert(rls != NULL); |
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); |
CFRelease(rls); |
// Run the run loop. |
fprintf(stdout, "Listening using Core Foundation:\n"); |
fflush(stdout); |
CFRunLoopRun(); |
fprintf(stderr, "CFRunLoopRun returned\n"); |
retVal = EXIT_FAILURE; |
} |
return retVal; |
} |
static int ListenUsingGCD(size_t noteCount, const char **noteNames) |
{ |
int retVal; |
uint32_t noteErr; |
size_t noteIndex; |
int noteTokens[noteCount]; |
const int * noteTokensPtr; |
// We need to capture the base of the noteTokens array, but the compiler won't let us do that |
// because it's of variable size. In our specific case this isn't a problem because, if things |
// go well, we never leave this routine but rather block forever in dispatch_main(). In a real |
// program you'd have to be a bit more careful (but then again, in a real program you wouldn't be |
// registering for an unbounded number of arbitrary notifications :-). |
noteTokensPtr = ¬eTokens[0]; |
noteErr = NOTIFY_STATUS_OK; |
for (noteIndex = 0; noteIndex < noteCount; noteIndex++) { |
noteErr = notify_register_dispatch(noteNames[noteIndex], ¬eTokens[noteIndex], dispatch_get_main_queue(), ^(int token) { |
PrintToken( |
token, |
noteCount, |
noteTokensPtr, |
noteNames |
); |
}); |
if (noteErr != NOTIFY_STATUS_OK) { |
break; |
} |
} |
if (noteErr != NOTIFY_STATUS_OK) { |
PrintNotifyError("registration failed", noteNames[noteIndex], noteErr); |
retVal = EXIT_FAILURE; |
} else { |
fprintf(stdout, "Listening using GCD:\n"); |
fflush(stdout); |
dispatch_main(); |
assert(0); // dispatch_main() should never return |
retVal = EXIT_FAILURE; |
} |
return retVal; |
} |
int main(int argc, const char **argv) |
{ |
int retVal; |
if (argc < 3) { |
PrintUsage(); |
retVal = EXIT_FAILURE; |
} else { |
if (strcasecmp(argv[1], "post") == 0) { |
retVal = PostNotifications( ((size_t) argc) - 2, &argv[2]); |
} else if ((strcasecmp(argv[1], "listen") == 0) || (strcasecmp(argv[1], "listenFD") == 0)) { |
retVal = ListenUsingFileDescriptor( ((size_t) argc) - 2, &argv[2]); |
} else if (strcasecmp(argv[1], "listenMach") == 0) { |
retVal = ListenUsingMach( ((size_t) argc) - 2, &argv[2]); |
} else if (strcasecmp(argv[1], "listenCF") == 0) { |
retVal = ListenUsingCoreFoundation( ((size_t) argc) - 2, &argv[2]); |
} else if (strcasecmp(argv[1], "listenGCD") == 0) { |
retVal = ListenUsingGCD( ((size_t) argc) - 2, &argv[2]); |
} else { |
PrintUsage(); |
retVal = EXIT_FAILURE; |
} |
} |
return retVal; |
} |
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-08-19