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