xref: /trueos/usr.bin/notifyutil/notifyutil.c (revision 30d2c92799792279ca128613f9be4462957730fa)
1 /*
2  * Copyright (c) 2006-2010 Apple Inc. All rights reserved.
3  *
4  * @APPLE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. Please obtain a copy of the License at
10  * http://www.opensource.apple.com/apsl/ and read it before using this
11  * file.
12  *
13  * The Original Code and all software distributed under the License are
14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18  * Please see the License for the specific language governing rights and
19  * limitations under the License.
20  *
21  * @APPLE_LICENSE_HEADER_END@
22  */
23 
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <time.h>
30 #include <sys/time.h>
31 #include <netinet/in.h>
32 #include <mach/mach.h>
33 #include <notify.h>
34 #include <notify_private.h>
35 #include <signal.h>
36 #include <dispatch/dispatch.h>
37 
38 #define forever for(;;)
39 #define IndexNull ((uint32_t)-1)
40 
41 #define PRINT_QUIET    0x00000000
42 #define PRINT_KEY      0x00000001
43 #define PRINT_STATE    0x00000002
44 #define PRINT_TIME     0x00000004
45 #define PRINT_TYPE     0x00000008
46 #define PRINT_VERBOSE  0xffffffff
47 
48 #ifndef USEC_PER_SEC
49 #define USEC_PER_SEC 1000000
50 #endif
51 
52 #define TYPE_NULL 0
53 #define TYPE_PORT 1
54 #define TYPE_FILE 2
55 #define TYPE_DISPATCH 3
56 #define TYPE_SIGNAL 4
57 #define TYPE_CHECK 5
58 #define TYPE_PLAIN 6
59 
60 static const char *typename[] =
61 {
62 	"unknown",
63 	"port",
64 	"file",
65 	"dispatch",
66 	"signal",
67 	"check",
68 	"plain"
69 };
70 
71 extern uint32_t notify_register_plain(const char *name, int *out_token);
72 
73 #define __DARWIN_NSIG NSIG
74 
75 typedef struct
76 {
77 	uint32_t token;
78 	uint32_t type;
79 	uint32_t signum;
80 	uint32_t count;
81 	char *name;
82 } reg_entry_t;
83 
84 static reg_entry_t *reg;
85 static uint32_t reg_count = 0;
86 
87 static int printopt;
88 static int port_flag;
89 static int file_flag;
90 static int watch_file;
91 static mach_port_t watch_port;
92 static dispatch_source_t timer_src;
93 static dispatch_source_t port_src;
94 static dispatch_source_t file_src;
95 static dispatch_source_t sig_src[__DARWIN_NSIG];
96 static dispatch_queue_t watch_queue;
97 
98 static void
usage(const char * name)99 usage(const char *name)
100 {
101 	fprintf(stderr, "usage: %s [-q] [-v] [-z msec] [-M] [-R] [command ...]\n", name);
102 	fprintf(stderr, "    -q             quiet mode\n");
103 	fprintf(stderr, "    -v             verbose - prints time, key, state value, and type\n");
104 	fprintf(stderr, "    -z msec        pause msec milliseconds after posting [default 100]\n");
105 	fprintf(stderr, "    -M             multiplex notifications from notifyd over a single mach port\n");
106 	fprintf(stderr, "    -R             regenerate registrations if notifyd restarts\n");
107 	fprintf(stderr, "commands:\n");
108 	fprintf(stderr, "    -port          switch to mach port for subsequent registrations [default]\n");
109 	fprintf(stderr, "    -file          switch to file descriptor for subsequent registrations\n");
110 	fprintf(stderr, "    -check         switch to shared memory for subsequent registrations\n");
111 	fprintf(stderr, "    -signal [#]    switch to signal [#] for subsequent registrations\n");
112 	fprintf(stderr, "                   initial default for signal is 1 (SIGHUP)\n");
113 	fprintf(stderr, "    -dispatch      switch to dispatch for subsequent registrations\n");
114 	fprintf(stderr, "    -p key         post a notifcation for key\n");
115 	fprintf(stderr, "    -w key         register for key and report notifications\n");
116 	fprintf(stderr, "    -# key         (# is an integer value, eg \"-1\") register for key and report # notifications\n");
117 	fprintf(stderr, "    -g key         get state value for key\n");
118 	fprintf(stderr, "    -s key val     set state value for key\n");
119 }
120 
121 static const char *
notify_status_strerror(int status)122 notify_status_strerror(int status)
123 {
124 	fprintf(stderr, "notify_status_strerror: %d\n", status);
125 	switch (status)
126 	{
127 		case NOTIFY_STATUS_OK: return("OK");
128 		case NOTIFY_STATUS_INVALID_NAME: return "Invalid Name";
129 		case NOTIFY_STATUS_INVALID_TOKEN: return "Invalid Token";
130 		case NOTIFY_STATUS_INVALID_PORT: return "Invalid Port";
131 		case NOTIFY_STATUS_INVALID_FILE: return "Invalid File";
132 		case NOTIFY_STATUS_INVALID_SIGNAL: return "Invalid Signal";
133 		case NOTIFY_STATUS_INVALID_REQUEST: return "Invalid Request";
134 		case NOTIFY_STATUS_NOT_AUTHORIZED: return "Not Authorized";
135 		case NOTIFY_STATUS_FAILED:
136 		default: return "Failed";
137 	}
138 }
139 
140 static void
reg_add(uint32_t tid,uint32_t type,uint32_t signum,uint32_t count,const char * name)141 reg_add(uint32_t tid, uint32_t type, uint32_t signum, uint32_t count, const char *name)
142 {
143 	if (name == NULL) return;
144 
145 	reg = (reg_entry_t *)reallocf(reg, (reg_count + 1) * sizeof(reg_entry_t));
146 	if (reg == NULL)
147 	{
148 		fprintf(stderr, "Can't allocate memory!\n");
149 		reg_count = 0;
150 		return;
151 	}
152 
153 	reg[reg_count].token = tid;
154 	reg[reg_count].type = type;
155 	reg[reg_count].signum = signum;
156 	reg[reg_count].count = count;
157 	reg[reg_count].name = strdup(name);
158 	if (reg[reg_count].name == NULL)
159 	{
160 		fprintf(stderr, "Can't allocate memory!\n");
161 		reg = NULL;
162 		reg_count = 0;
163 		return;
164 	}
165 
166 	reg_count++;
167 }
168 
169 static void
reg_delete(uint32_t index)170 reg_delete(uint32_t index)
171 {
172 	uint32_t i;
173 
174 	if (index == IndexNull) return;
175 	if (index >= reg_count) return;
176 
177 	free(reg[index].name);
178 
179 	for (i = index + 1; i < reg_count; i++) reg[i - 1] = reg[i];
180 	reg_count--;
181 
182 	if (reg_count == 0)
183 	{
184 		free(reg);
185 		reg = NULL;
186 	}
187 	else
188 	{
189 		reg = (reg_entry_t *)reallocf(reg, reg_count * sizeof(reg_entry_t));
190 		if (reg == NULL)
191 		{
192 			fprintf(stderr, "Can't allocate memory!\n");
193 			reg_count = 0;
194 		}
195 	}
196 }
197 
198 static uint32_t
reg_find_name(const char * name)199 reg_find_name(const char *name)
200 {
201 	uint32_t i;
202 
203 	for (i = 0; i < reg_count; i++) if (!strcmp(reg[i].name, name)) return i;
204 	return IndexNull;
205 }
206 
207 static uint32_t
reg_find_token(uint32_t tid)208 reg_find_token(uint32_t tid)
209 {
210 	uint32_t i;
211 
212 	for (i = 0; i < reg_count; i++) if (tid == reg[i].token) return i;
213 	return IndexNull;
214 }
215 
216 static void
process_event(int tid)217 process_event(int tid)
218 {
219 	struct timeval now;
220 	char tstr[32];
221 	int status, index, needspace;
222 	uint64_t state;
223 
224 	gettimeofday(&now, NULL);
225 
226 	index = reg_find_token(tid);
227 	if (index == (int)IndexNull)
228 		return;
229 
230 	needspace = 0;
231 
232 	if (printopt & PRINT_TIME)
233 	{
234 		snprintf(tstr, sizeof(tstr), "%llu", now.tv_usec + USEC_PER_SEC + 500);
235 		tstr[4] = '\0';
236 		printf("%d.%s", (int)now.tv_sec, tstr+1);
237 		needspace = 1;
238 	}
239 
240 	if (printopt & PRINT_KEY)
241 	{
242 		if (needspace) printf(" ");
243 		printf("%s", reg[index].name);
244 		needspace = 1;
245 	}
246 
247 	if (printopt & PRINT_STATE)
248 	{
249 		if (needspace) printf(" ");
250 		state = 0;
251 		status = notify_get_state(tid, &state);
252 		if (status == NOTIFY_STATUS_OK) printf("%llu",(unsigned long long)state);
253 		else printf(": %s", notify_status_strerror(status));
254 		needspace = 1;
255 	}
256 
257 	if (printopt & PRINT_TYPE)
258 	{
259 		if (needspace) printf(" ");
260 		printf("%s", typename[reg[index].type]);
261 		needspace = 1;
262 	}
263 
264 	if (printopt != PRINT_QUIET) printf("\n");
265 
266 	if ((reg[index].count != IndexNull) && (reg[index].count != 0)) reg[index].count--;
267 	if (reg[index].count == 0)
268 	{
269 		status = notify_cancel(tid);
270 		reg_delete(index);
271 	}
272 }
273 
274 static void
file_handler(int fd)275 file_handler(int fd)
276 {
277 	ssize_t i;
278 	int tid;
279 
280 	if (fd < 0) return;
281 
282 	i = read(fd, &tid, sizeof(tid));
283 	if (i < 0) return;
284 
285 	tid = ntohl(tid);
286 	process_event(tid);
287 
288 	if (reg_count == 0) exit(0);
289 }
290 
291 static void
port_handler(mach_port_t port)292 port_handler(mach_port_t port)
293 {
294 	int tid;
295 	mach_msg_empty_rcv_t msg;
296 	kern_return_t status;
297 
298 	if (port == MACH_PORT_NULL) return;
299 
300 	memset(&msg, 0, sizeof(msg));
301 	status = mach_msg(&msg.header, MACH_RCV_MSG, 0, sizeof(msg), port, 0, MACH_PORT_NULL);
302 	if (status != KERN_SUCCESS) return;
303 
304 	tid = msg.header.msgh_id;
305 	process_event(tid);
306 
307 	if (reg_count == 0) exit(0);
308 }
309 
310 static void
signal_handler(uint32_t sig)311 signal_handler(uint32_t sig)
312 {
313 	uint32_t i, status;
314 	int check;
315 
316 	if (printopt != PRINT_QUIET) printf("SIGNAL %u\n", sig);
317 	for (i = 0; i < reg_count; i++)
318 	{
319 		if ((reg[i].type == TYPE_SIGNAL) && (reg[i].signum == sig))
320 		{
321 			check = 0;
322 			status = notify_check(reg[i].token, &check);
323 			if ((status == NOTIFY_STATUS_OK) && (check != 0)) process_event(reg[i].token);
324 		}
325 	}
326 
327 	if (reg_count == 0) exit(0);
328 }
329 
330 static void
dispatch_handler(const char * name)331 dispatch_handler(const char *name)
332 {
333 	uint32_t index = reg_find_name(name);
334 	if (index == IndexNull) return;
335 
336 	process_event(reg[index].token);
337 }
338 
339 static void
timer_handler(void)340 timer_handler(void)
341 {
342 	uint32_t i, status;
343 	int check;
344 
345 	for (i = 0; i < reg_count; i++)
346 	{
347 		if ((reg[i].type == TYPE_CHECK) || (reg[i].type == TYPE_PLAIN))
348 		{
349 			check = 0;
350 			status = notify_check(reg[i].token, &check);
351 			if ((status == NOTIFY_STATUS_OK) && (check != 0)) process_event(reg[i].token);
352 		}
353 	}
354 
355 	if (reg_count == 0) exit(0);
356 }
357 
358 static uint32_t
do_register(const char * name,uint32_t type,uint32_t signum,uint32_t count)359 do_register(const char *name, uint32_t type, uint32_t signum, uint32_t count)
360 {
361 	int tid, check;
362 	uint32_t status;
363 
364 	switch (type)
365 	{
366 		case TYPE_PORT:
367 		{
368 			status = notify_register_mach_port(name, &watch_port, port_flag, &tid);
369 			if (status != NOTIFY_STATUS_OK) return status;
370 
371 			port_flag = NOTIFY_REUSE;
372 			if (port_src == NULL)
373 			{
374 				port_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, watch_port, 0, watch_queue);
375 				dispatch_source_set_event_handler(port_src, ^{
376 					port_handler(watch_port);
377 				});
378 				dispatch_resume(port_src);
379 			}
380 
381 			break;
382 		}
383 
384 		case TYPE_FILE:
385 		{
386 			status = notify_register_file_descriptor(name, &watch_file, file_flag, &tid);
387 			if (status != NOTIFY_STATUS_OK) return status;
388 
389 			file_flag = NOTIFY_REUSE;
390 			if (file_src == NULL)
391 			{
392 				file_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, (uintptr_t)watch_file, 0, watch_queue);
393 				dispatch_source_set_event_handler(file_src, ^{
394 					file_handler(watch_file);
395 				});
396 				dispatch_resume(file_src);
397 			}
398 
399 			break;
400 		}
401 
402 		case TYPE_SIGNAL:
403 		{
404 			signal(signum, SIG_IGN);
405 
406 			status = notify_register_signal(name, signum, &tid);
407 			if (status != NOTIFY_STATUS_OK) return status;
408 
409 			status = notify_check(tid, &check);
410 
411 			if (sig_src[signum] == NULL)
412 			{
413 				sig_src[signum] = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t)signum, 0, watch_queue);
414 				dispatch_source_set_event_handler(sig_src[signum], ^{
415 					signal_handler(signum);
416 				});
417 				dispatch_resume(sig_src[signum]);
418 			}
419 
420 			break;
421 		}
422 
423 		case TYPE_DISPATCH:
424 		{
425 			status = notify_register_dispatch(name, &tid, watch_queue, ^(int x __unused){ dispatch_handler(name); });
426 			if (status != NOTIFY_STATUS_OK) return status;
427 			break;
428 		}
429 
430 		case TYPE_CHECK:
431 		{
432 			status = notify_register_check(name, &tid);
433 			if (status != NOTIFY_STATUS_OK) return status;
434 
435 			status = notify_check(tid, &check);
436 
437 			if (timer_src == NULL)
438 			{
439 				timer_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, watch_queue);
440 				dispatch_source_set_event_handler(timer_src, ^{
441 					timer_handler();
442 				});
443 				dispatch_source_set_timer(timer_src, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), NSEC_PER_SEC / 10, 0);
444 				dispatch_resume(timer_src);
445 			}
446 
447 			break;
448 		}
449 
450 		case TYPE_PLAIN:
451 		{
452 			status = notify_register_plain(name, &tid);
453 			if (status != NOTIFY_STATUS_OK) return status;
454 
455 			status = notify_check(tid, &check);
456 
457 			if (timer_src == NULL)
458 			{
459 				timer_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, watch_queue);
460 				dispatch_source_set_event_handler(timer_src, ^{
461 					timer_handler();
462 				});
463 				dispatch_source_set_timer(timer_src, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), NSEC_PER_SEC / 10, 0);
464 				dispatch_resume(timer_src);
465 			}
466 
467 			break;
468 		}
469 
470 		default: return NOTIFY_STATUS_FAILED;
471 	}
472 
473 	reg_add(tid, type, signum, count, name);
474 	return NOTIFY_STATUS_OK;
475 }
476 
477 int
main(int argc,const char * argv[])478 main(int argc, const char *argv[])
479 {
480 	const char *name;
481 	int i;
482 	uint32_t n, index, signum, ntype, status, opts, nap;
483 	int tid;
484 	uint64_t state;
485 
486 	for (i = 0; i < __DARWIN_NSIG; i++) sig_src[i] = NULL;
487 
488 	ntype = TYPE_PORT;
489 	signum = 1;
490 	watch_file = -1;
491 	watch_port = MACH_PORT_NULL;
492 	printopt = PRINT_KEY;
493 	opts = 0;
494 	nap = 100000;
495 
496 	watch_queue = dispatch_queue_create("Watch Q", NULL);
497 
498 	name = strrchr(argv[0], '/');
499 	if (name == NULL) name = argv[0];
500 	else name++;
501 
502 	for (i = 1; i < argc; i++)
503 	{
504 		if ((!strcmp(argv[i], "-help")) || (!strcmp(argv[i], "-h")))
505 		{
506 			usage(name);
507 			exit(0);
508 		}
509 		else if (!strcmp(argv[i], "-q"))
510 		{
511 			printopt = PRINT_QUIET;
512 		}
513 		else if (!strcmp(argv[i], "-v"))
514 		{
515 			printopt |= PRINT_VERBOSE;
516 		}
517 		else if (!strcmp(argv[i], "-M"))
518 		{
519 			opts |= NOTIFY_OPT_DEMUX;
520 		}
521 		else if (!strcmp(argv[i], "-R"))
522 		{
523 			opts |= NOTIFY_OPT_REGEN;
524 		}
525 		else if (!strcmp(argv[i], "-z"))
526 		{
527 			if ((i + 1) >= argc)
528 			{
529 				fprintf(stderr, "timer value must be supplied following -z\n");
530 				usage(name);
531 				exit(1);
532 			}
533 
534 			i++;
535 
536 			if ((argv[i][0] < '0') || (argv[i][1] > '9'))
537 			{
538 				fprintf(stderr, "-z %s is invalid\n", argv[i]);
539 				fprintf(stderr, "timer value must be an integer\n");
540 				usage(name);
541 				exit(1);
542 			}
543 			nap = 1000 * atoi(argv[i]);
544 		}
545 		else if (!strcmp(argv[i], "-port"))
546 		{}
547 		else if (!strcmp(argv[i], "-file"))
548 		{}
549 		else if ((!strcmp(argv[i], "-sig")) || (!strcmp(argv[i], "-signal")))
550 		{
551 			if ((i + 1) >= argc) continue;
552 			if (argv[i + 1][0] == '-') continue;
553 
554 			i++;
555 
556 			if ((argv[i][0] < '0') || (argv[i][1] > '9'))
557 			{
558 				fprintf(stderr, "-signal %s is invalid\n", argv[i]);
559 				fprintf(stderr, "signals must be specified as integer values\n");
560 				usage(name);
561 				exit(1);
562 			}
563 
564 		}
565 		else if (!strcmp(argv[i], "-dispatch"))
566 		{}
567 		else if (!strcmp(argv[i], "-check"))
568 		{}
569 		else if (!strcmp(argv[i], "-plain"))
570 		{}
571 		else if (!strcmp(argv[i], "-p"))
572 		{
573 			if ((i + 1) >= argc)
574 			{
575 				fprintf(stderr, "name required following -p\n");
576 				usage(name);
577 				exit(1);
578 			}
579 
580 			i++;
581 		}
582 		else if ((argv[i][0] == '-') && ((argv[i][1] == 'w') || ((argv[i][1] >= '0') && (argv[i][1] <= '9'))))
583 		{
584 			if ((i + 1) >= argc)
585 			{
586 				fprintf(stderr, "name required following %s\n", argv[i]);
587 				usage(name);
588 				exit(1);
589 			}
590 
591 			i++;
592 		}
593 		else if (!strcmp(argv[i], "-g"))
594 		{
595 			if ((i + 1) >= argc)
596 			{
597 				fprintf(stderr, "name required following -g\n");
598 				usage(name);
599 				exit(1);
600 			}
601 
602 			i++;
603 		}
604 		else if (!strcmp(argv[i], "-s"))
605 		{
606 			if ((i + 1) >= argc)
607 			{
608 				fprintf(stderr, "name required following -s\n");
609 				usage(name);
610 				exit(1);
611 			}
612 
613 			i++;
614 
615 			if ((i + 1) >= argc)
616 			{
617 				fprintf(stderr, "value required following -s name\n");
618 				usage(name);
619 				exit(1);
620 			}
621 
622 			i++;
623 			state = atoll(argv[i]);
624 			if ((state == 0) && (strcmp(argv[i], "0")))
625 			{
626 				fprintf(stderr, "value following -s name must be a 64-bit integer\n");
627 			}
628 		}
629 		else
630 		{
631 			fprintf(stderr, "unrecognized option: %s\n", argv[i]);
632 			usage(name);
633 			exit(1);
634 		}
635 	}
636 
637     if (opts != 0) notify_set_options(opts);
638 
639 	for (i = 1; i < argc; i++)
640 	{
641 		if (!strcmp(argv[i], "-port"))
642 		{
643 			ntype = TYPE_PORT;
644 		}
645 		else if (!strcmp(argv[i], "-file"))
646 		{
647 			ntype = TYPE_FILE;
648 		}
649 		else if ((!strcmp(argv[i], "-sig")) || (!strcmp(argv[i], "-signal")))
650 		{
651 
652 			ntype = TYPE_SIGNAL;
653 			if (((i + 1) < argc) && (argv[i + 1][0] != '-'))
654 			{
655 				i++;
656 				signum = atoi(argv[i]);
657 			}
658 		}
659 		else if (!strcmp(argv[i], "-dispatch"))
660 		{
661 			ntype = TYPE_DISPATCH;
662 		}
663 		else if (!strcmp(argv[i], "-check"))
664 		{
665 			ntype = TYPE_CHECK;
666 		}
667 		else if (!strcmp(argv[i], "-plain"))
668 		{
669 			ntype = TYPE_PLAIN;
670 		}
671 		else if (!strcmp(argv[i], "-p"))
672 		{
673 			if ((i + 1) >= argc)
674 			{
675 				usage(name);
676 				exit(1);
677 			}
678 
679 			i++;
680 
681 			status = notify_post(argv[i]);
682 			if (status != NOTIFY_STATUS_OK) {
683 				fprintf(stderr, "%s: %s\n", argv[i], notify_status_strerror(status));
684 				exit(1);
685 			}
686 			else if (nap > 0)
687 				usleep(nap);
688 		}
689 		else if ((argv[i][0] == '-') && ((argv[i][1] == 'w') || ((argv[i][1] >= '0') && (argv[i][1] <= '9'))))
690 		{
691 			if ((i + 1) >= argc)
692 			{
693 				usage(name);
694 				exit(1);
695 			}
696 
697 			n = IndexNull;
698 			if (argv[i][1] != 'w') n = atoi(argv[i] + 1);
699 
700 			i++;
701 			tid = IndexNull;
702 
703 			index = reg_find_name(argv[i]);
704 			if (index != IndexNull)
705 			{
706 				fprintf(stderr, "Already watching for %s\n", argv[i]);
707 				continue;
708 			}
709 
710 			status = do_register(argv[i], ntype, signum, n);
711 			if (status != NOTIFY_STATUS_OK) {
712 				printf("%s: %s\n", argv[i], notify_status_strerror(status));
713 				exit(1);
714 			}
715 		}
716 		else if (!strcmp(argv[i], "-g"))
717 		{
718 			if ((i + 1) >= argc)
719 			{
720 				usage(name);
721 				exit(1);
722 			}
723 
724 			i++;
725 			state = 0;
726 			tid = IndexNull;
727 
728 			status = notify_register_plain(argv[i], &tid);
729 			if (status == NOTIFY_STATUS_OK)
730 			{
731 				status = notify_get_state(tid, &state);
732 				notify_cancel(tid);
733 			}
734 
735 			if (status == NOTIFY_STATUS_OK) printf("%s %llu\n", argv[i], (unsigned long long)state);
736 			else {
737 				printf("%s: %s\n", argv[i], notify_status_strerror(status));
738 				exit(1);
739 			}
740 		}
741 		else if (!strcmp(argv[i], "-s"))
742 		{
743 			if ((i + 2) >= argc)
744 			{
745 				usage(name);
746 				exit(1);
747 			}
748 
749 			i++;
750 			tid = IndexNull;
751 			status = notify_register_plain(argv[i], &tid);
752 			if (status == NOTIFY_STATUS_OK)
753 			{
754 				state = atoll(argv[i + 1]);
755 				status = notify_set_state(tid, state);
756 				notify_cancel(tid);
757 			}
758 
759 			if (status != NOTIFY_STATUS_OK) {
760 				printf("%s: %s\n", argv[i], notify_status_strerror(status));
761 				exit(1);
762 			}
763 			i++;
764 		}
765 	}
766 
767 	if (reg_count == 0) exit(0);
768 
769 	dispatch_main();
770 }
771