darling-Libnotify/notifyutil/notifyutil.c
2022-11-20 18:42:59 -08:00

757 lines
16 KiB
C

/*
* Copyright (c) 2006-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 <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <mach/mach.h>
#include <notify.h>
#include <notify_private.h>
#include <signal.h>
#include <dispatch/dispatch.h>
#include <os/variant_private.h>
#define forever for(;;)
#define IndexNull ((uint32_t)-1)
#define PRINT_QUIET 0x00000000
#define PRINT_KEY 0x00000001
#define PRINT_STATE 0x00000002
#define PRINT_TIME 0x00000004
#define PRINT_TYPE 0x00000008
#define PRINT_TOKEN 0x00000010
#define PRINT_VERBOSE 0xffffffff
#ifndef USEC_PER_SEC
#define USEC_PER_SEC 1000000
#endif
#define TYPE_NULL 0
#define TYPE_PORT 1
#define TYPE_FILE 2
#define TYPE_DISPATCH 3
#define TYPE_SIGNAL 4
#define TYPE_CHECK 5
#define TYPE_PLAIN 6
static const char *typename[] =
{
"unknown",
"port",
"file",
"dispatch",
"signal",
"check",
"plain"
};
typedef struct
{
uint32_t token;
uint32_t type;
uint32_t signum;
uint32_t count;
char *name;
} reg_entry_t;
static reg_entry_t *reg;
static uint32_t reg_count = 0;
static int printopt;
static int port_flag;
static int file_flag;
static int watch_file;
static mach_port_t watch_port;
static dispatch_source_t timer_src;
static dispatch_source_t port_src;
static dispatch_source_t file_src;
static dispatch_source_t sig_src[__DARWIN_NSIG];
static dispatch_queue_t watch_queue;
static void
usage(const char *name)
{
fprintf(stderr, "usage: %s [-q] [-v] [-z msec] [-M] [-R] [command ...]\n", name);
fprintf(stderr, " -q quiet mode\n");
fprintf(stderr, " -v verbose - prints time, key, state value, and type\n");
fprintf(stderr, " -z msec pause msec milliseconds after posting [default 100]\n");
fprintf(stderr, " -M multiplex notifications from notifyd over a single mach port\n");
fprintf(stderr, " -R regenerate registrations if notifyd restarts\n");
fprintf(stderr, "commands:\n");
fprintf(stderr, " -port switch to mach port for subsequent registrations [default]\n");
fprintf(stderr, " -file switch to file descriptor for subsequent registrations\n");
fprintf(stderr, " -check switch to shared memory for subsequent registrations\n");
fprintf(stderr, " -signal [#] switch to signal [#] for subsequent registrations\n");
fprintf(stderr, " initial default for signal is 1 (SIGHUP)\n");
fprintf(stderr, " -dispatch switch to dispatch for subsequent registrations\n");
fprintf(stderr, " -p key post a notification for key\n");
fprintf(stderr, " -w key register for key and report notifications\n");
fprintf(stderr, " -# key (# is an integer value, eg \"-1\") register for key and report # notifications\n");
fprintf(stderr, " -g key get state value for key\n");
fprintf(stderr, " -s key val set state value for key\n");
if(os_variant_has_internal_diagnostics(NULL))
{
fprintf(stderr, " --dump dumps metadata to a file in /var/run/\n");
}
}
// Triggers a notifyd dump
static void
notifyutil_dump()
{
int ret;
ret = notify_dump_status("/var/run/notifyd.status");
if(ret == NOTIFY_STATUS_OK)
{
fprintf(stdout, "Notifyd dump success! New file created at /var/run/notifyd.status\n");
} else {
fprintf(stdout, "Notifyd dump failed with %x\n", ret);
}
}
static void
reg_add(uint32_t tid, uint32_t type, uint32_t signum, uint32_t count, const char *name)
{
if (name == NULL) return;
reg = (reg_entry_t *)reallocf(reg, (reg_count + 1) * sizeof(reg_entry_t));
if (reg == NULL)
{
fprintf(stderr, "Can't allocate memory!\n");
reg_count = 0;
return;
}
reg[reg_count].token = tid;
reg[reg_count].type = type;
reg[reg_count].signum = signum;
reg[reg_count].count = count;
reg[reg_count].name = strdup(name);
if (reg[reg_count].name == NULL)
{
fprintf(stderr, "Can't allocate memory!\n");
reg = NULL;
reg_count = 0;
return;
}
reg_count++;
}
static void
reg_delete(uint32_t index)
{
uint32_t i;
if (index == IndexNull) return;
if (index >= reg_count) return;
free(reg[index].name);
for (i = index + 1; i < reg_count; i++) reg[i - 1] = reg[i];
reg_count--;
if (reg_count == 0)
{
free(reg);
reg = NULL;
}
else
{
reg = (reg_entry_t *)reallocf(reg, reg_count * sizeof(reg_entry_t));
if (reg == NULL)
{
fprintf(stderr, "Can't allocate memory!\n");
reg_count = 0;
}
}
}
static uint32_t
reg_find_token(uint32_t tid)
{
uint32_t i;
for (i = 0; i < reg_count; i++) if (tid == reg[i].token) return i;
return IndexNull;
}
static void
process_event(int tid)
{
struct timeval now;
char tstr[32];
int status, needspace;
uint32_t index;
uint64_t state;
gettimeofday(&now, NULL);
index = reg_find_token(tid);
if (index == IndexNull) return;
needspace = 0;
if (printopt & PRINT_TOKEN)
{
printf("[%d]", tid);
needspace = 1;
}
if (printopt & PRINT_TIME)
{
if (needspace) printf(" ");
snprintf(tstr, sizeof(tstr), "%llu", now.tv_usec + USEC_PER_SEC + 500);
tstr[4] = '\0';
printf("%d.%s", (int)now.tv_sec, tstr+1);
needspace = 1;
}
if (printopt & PRINT_KEY)
{
if (needspace) printf(" ");
printf("%s", reg[index].name);
needspace = 1;
}
if (printopt & PRINT_STATE)
{
if (needspace) printf(" ");
state = 0;
status = notify_get_state(tid, &state);
if (status == NOTIFY_STATUS_OK) printf("%llu",(unsigned long long)state);
else printf(": Failed with code %d", status);
needspace = 1;
}
if (printopt & PRINT_TYPE)
{
if (needspace) printf(" ");
printf("%s", typename[reg[index].type]);
}
if (printopt != PRINT_QUIET) printf("\n");
if ((reg[index].count != IndexNull) && (reg[index].count != 0)) reg[index].count--;
if (reg[index].count == 0)
{
(void)notify_cancel(tid);
reg_delete(index);
}
fflush(stdout);
if (reg_count == 0) exit(0);
}
static void
file_handler(int fd)
{
ssize_t i;
int tid;
if (fd < 0) return;
i = read(fd, &tid, sizeof(tid));
if (i < 0) return;
tid = ntohl(tid);
process_event(tid);
if (reg_count == 0) exit(0);
}
static void
port_handler(mach_port_t port)
{
int tid;
mach_msg_empty_rcv_t msg;
kern_return_t status;
if (port == MACH_PORT_NULL) return;
memset(&msg, 0, sizeof(msg));
status = mach_msg(&msg.header, MACH_RCV_MSG, 0, sizeof(msg), port, 0, MACH_PORT_NULL);
if (status != KERN_SUCCESS) return;
tid = msg.header.msgh_id;
process_event(tid);
if (reg_count == 0) exit(0);
}
static void
signal_handler(uint32_t sig)
{
uint32_t i, status;
int check;
if (printopt != PRINT_QUIET) printf("SIGNAL %u\n", sig);
for (i = 0; i < reg_count; i++)
{
if ((reg[i].type == TYPE_SIGNAL) && (reg[i].signum == sig))
{
check = 0;
status = notify_check(reg[i].token, &check);
if ((status == NOTIFY_STATUS_OK) && (check != 0)) process_event(reg[i].token);
}
}
if (reg_count == 0) exit(0);
}
static void
dispatch_handler(int x)
{
uint32_t index = reg_find_token(x);
if (index == IndexNull) return;
process_event(reg[index].token);
}
static void
timer_handler(void)
{
uint32_t i, status;
int check;
for (i = 0; i < reg_count; i++)
{
if ((reg[i].type == TYPE_CHECK) || (reg[i].type == TYPE_PLAIN))
{
check = 0;
status = notify_check(reg[i].token, &check);
if ((status == NOTIFY_STATUS_OK) && (check != 0)) process_event(reg[i].token);
}
}
if (reg_count == 0) exit(0);
}
static uint32_t
do_register(const char *name, uint32_t type, uint32_t signum, uint32_t count)
{
int tid, check;
uint32_t status;
switch (type)
{
case TYPE_PORT:
{
status = notify_register_mach_port(name, &watch_port, port_flag, &tid);
if (status != NOTIFY_STATUS_OK) return status;
port_flag = NOTIFY_REUSE;
if (port_src == NULL)
{
port_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, watch_port, 0, watch_queue);
dispatch_source_set_event_handler(port_src, ^{
port_handler(watch_port);
});
dispatch_resume(port_src);
}
break;
}
case TYPE_FILE:
{
status = notify_register_file_descriptor(name, &watch_file, file_flag, &tid);
if (status != NOTIFY_STATUS_OK) return status;
file_flag = NOTIFY_REUSE;
if (file_src == NULL)
{
file_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)watch_file, 0, watch_queue);
dispatch_source_set_event_handler(file_src, ^{
file_handler(watch_file);
});
dispatch_resume(file_src);
}
break;
}
case TYPE_SIGNAL:
{
signal(signum, SIG_IGN);
status = notify_register_signal(name, signum, &tid);
if (status != NOTIFY_STATUS_OK) return status;
(void)notify_check(tid, &check);
if (sig_src[signum] == NULL)
{
sig_src[signum] = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t)signum, 0, watch_queue);
dispatch_source_set_event_handler(sig_src[signum], ^{
signal_handler(signum);
});
dispatch_resume(sig_src[signum]);
}
break;
}
case TYPE_DISPATCH:
{
status = notify_register_dispatch(name, &tid, watch_queue, ^(int x){ dispatch_handler(x); });
if (status != NOTIFY_STATUS_OK) return status;
break;
}
case TYPE_CHECK:
{
status = notify_register_check(name, &tid);
if (status != NOTIFY_STATUS_OK) return status;
(void)notify_check(tid, &check);
if (timer_src == NULL)
{
timer_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, watch_queue);
dispatch_source_set_event_handler(timer_src, ^{
timer_handler();
});
dispatch_source_set_timer(timer_src, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), NSEC_PER_SEC / 10, 0);
dispatch_resume(timer_src);
}
break;
}
case TYPE_PLAIN:
{
status = notify_register_plain(name, &tid);
if (status != NOTIFY_STATUS_OK) return status;
(void)notify_check(tid, &check);
if (timer_src == NULL)
{
timer_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, watch_queue);
dispatch_source_set_event_handler(timer_src, ^{
timer_handler();
});
dispatch_source_set_timer(timer_src, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), NSEC_PER_SEC / 10, 0);
dispatch_resume(timer_src);
}
break;
}
default: return NOTIFY_STATUS_FAILED;
}
reg_add(tid, type, signum, count, name);
return NOTIFY_STATUS_OK;
}
int
main(int argc, const char *argv[])
{
const char *name;
int i;
uint32_t n, signum, ntype, status, opts, nap;
int tid;
uint64_t state;
for (i = 0; i < __DARWIN_NSIG; i++) sig_src[i] = NULL;
ntype = TYPE_PORT;
signum = 1;
watch_file = -1;
watch_port = MACH_PORT_NULL;
printopt = PRINT_KEY;
opts = 0;
nap = 100000;
watch_queue = dispatch_queue_create("Watch Q", NULL);
name = strrchr(argv[0], '/');
if (name == NULL) name = argv[0];
else name++;
for (i = 1; i < argc; i++)
{
if ((!strcmp(argv[i], "-help")) || (!strcmp(argv[i], "-h")))
{
usage(name);
exit(0);
}
else if (!strcmp(argv[i], "-q"))
{
printopt = PRINT_QUIET;
}
else if (!strcmp(argv[i], "-v"))
{
printopt |= PRINT_VERBOSE;
}
else if (!strcmp(argv[i], "-M"))
{
opts |= NOTIFY_OPT_DISPATCH;
}
else if (!strcmp(argv[i], "-R"))
{
opts |= NOTIFY_OPT_REGEN;
}
else if (!strcmp(argv[i], "-z"))
{
if ((i + 1) >= argc)
{
fprintf(stderr, "timer value must be supplied following -z\n");
usage(name);
exit(1);
}
i++;
if ((argv[i][0] < '0') || (argv[i][1] > '9'))
{
fprintf(stderr, "-z %s is invalid\n", argv[i]);
fprintf(stderr, "timer value must be an integer\n");
usage(name);
exit(1);
}
nap = 1000 * atoi(argv[i]);
}
else if (!strcmp(argv[i], "-port"))
{}
else if (!strcmp(argv[i], "-file"))
{}
else if ((!strcmp(argv[i], "-sig")) || (!strcmp(argv[i], "-signal")))
{
if ((i + 1) >= argc) continue;
if (argv[i + 1][0] == '-') continue;
i++;
if ((argv[i][0] < '0') || (argv[i][1] > '9'))
{
fprintf(stderr, "-signal %s is invalid\n", argv[i]);
fprintf(stderr, "signals must be specified as integer values\n");
usage(name);
exit(1);
}
}
else if (!strcmp(argv[i], "-dispatch"))
{}
else if (!strcmp(argv[i], "-check"))
{}
else if (!strcmp(argv[i], "-plain"))
{}
else if (!strcmp(argv[i], "-p"))
{
if ((i + 1) >= argc)
{
fprintf(stderr, "name required following -p\n");
usage(name);
exit(1);
}
i++;
}
else if ((argv[i][0] == '-') && ((argv[i][1] == 'w') || ((argv[i][1] >= '0') && (argv[i][1] <= '9'))))
{
if ((i + 1) >= argc)
{
fprintf(stderr, "name required following %s\n", argv[i]);
usage(name);
exit(1);
}
i++;
}
else if (!strcmp(argv[i], "-g"))
{
if ((i + 1) >= argc)
{
fprintf(stderr, "name required following -g\n");
usage(name);
exit(1);
}
i++;
}
else if (!strcmp(argv[i], "-s"))
{
if ((i + 1) >= argc)
{
fprintf(stderr, "name required following -s\n");
usage(name);
exit(1);
}
i++;
if ((i + 1) >= argc)
{
fprintf(stderr, "value required following -s name\n");
usage(name);
exit(1);
}
i++;
state = atoll(argv[i]);
if ((state == 0) && (strcmp(argv[i], "0")))
{
fprintf(stderr, "value following -s name must be a 64-bit integer\n");
}
}
else if (!strcmp(argv[i], "--dump") && os_variant_has_internal_diagnostics(NULL))
{
notifyutil_dump();
exit(0);
}
else
{
fprintf(stderr, "unrecognized option: %s\n", argv[i]);
usage(name);
exit(1);
}
}
if (opts != 0) notify_set_options(opts);
for (i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "-port"))
{
ntype = TYPE_PORT;
}
else if (!strcmp(argv[i], "-file"))
{
ntype = TYPE_FILE;
}
else if ((!strcmp(argv[i], "-sig")) || (!strcmp(argv[i], "-signal")))
{
ntype = TYPE_SIGNAL;
if (((i + 1) < argc) && (argv[i + 1][0] != '-'))
{
i++;
signum = atoi(argv[i]);
}
}
else if (!strcmp(argv[i], "-dispatch"))
{
ntype = TYPE_DISPATCH;
}
else if (!strcmp(argv[i], "-check"))
{
ntype = TYPE_CHECK;
}
else if (!strcmp(argv[i], "-plain"))
{
ntype = TYPE_PLAIN;
}
else if (!strcmp(argv[i], "-p"))
{
if ((i + 1) >= argc)
{
usage(name);
exit(1);
}
i++;
status = notify_post(argv[i]);
if (status != NOTIFY_STATUS_OK) printf("%s: Failed with code %d\n", argv[i], status);
else if (nap > 0) usleep(nap);
}
else if ((argv[i][0] == '-') && ((argv[i][1] == 'w') || ((argv[i][1] >= '0') && (argv[i][1] <= '9'))))
{
if ((i + 1) >= argc)
{
usage(name);
exit(1);
}
n = IndexNull;
if (argv[i][1] != 'w') n = atoi(argv[i] + 1);
i++;
tid = IndexNull;
status = do_register(argv[i], ntype, signum, n);
if (status != NOTIFY_STATUS_OK) printf("%s: Failed with code %d\n", argv[i], status);
}
else if (!strcmp(argv[i], "-g"))
{
if ((i + 1) >= argc)
{
usage(name);
exit(1);
}
i++;
state = 0;
tid = IndexNull;
status = notify_register_plain(argv[i], &tid);
if (status == NOTIFY_STATUS_OK)
{
status = notify_get_state(tid, &state);
notify_cancel(tid);
}
if (status == NOTIFY_STATUS_OK) printf("%s %llu\n", argv[i], (unsigned long long)state);
else printf("%s: Failed with code %d\n", argv[i], status);
}
else if (!strcmp(argv[i], "-s"))
{
if ((i + 2) >= argc)
{
usage(name);
exit(1);
}
i++;
tid = IndexNull;
status = notify_register_plain(argv[i], &tid);
if (status == NOTIFY_STATUS_OK)
{
state = atoll(argv[i + 1]);
status = notify_set_state(tid, state);
notify_cancel(tid);
}
if (status != NOTIFY_STATUS_OK) printf("%s: Failed with code %d\n", argv[i], status);
i++;
}
}
if (reg_count == 0) exit(0);
dispatch_main();
}