1/*
2 * Copyright (c) 2000-2005, 2008-2014 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/*
25 * Modification History
26 *
27 * June 1, 2001			Allan Nathanson <ajn@apple.com>
28 * - public API conversion
29 *
30 * November 9, 2000		Allan Nathanson <ajn@apple.com>
31 * - initial revision
32 */
33
34#include <pthread.h>
35#include <sys/types.h>
36#include <sys/socket.h>
37#include <sys/time.h>
38#include <sys/un.h>
39#include <unistd.h>
40
41#include "scutil.h"
42#include "notifications.h"
43
44
45static int			osig;
46static struct sigaction		*oact	= NULL;
47
48
49static char *
50elapsed()
51{
52	int			n;
53	static char		str[128];
54	struct tm		tm_diff;
55	struct tm		tm_now;
56	struct timeval		tv_diff;
57	struct timeval		tv_now;
58	static struct timeval	tv_then	= { 0, 0 };
59
60	(void)gettimeofday(&tv_now, NULL);
61
62	(void)localtime_r(&tv_now.tv_sec, &tm_now);
63
64	timersub(&tv_now, &tv_then, &tv_diff);
65	(void)localtime_r(&tv_diff.tv_sec, &tm_diff);
66	n = snprintf(str, sizeof(str), "%2d:%02d:%02d.%03d",
67		     tm_now.tm_hour,
68		     tm_now.tm_min,
69		     tm_now.tm_sec,
70		     tv_now.tv_usec / 1000);
71	if (((tv_then.tv_sec != 0) || (tv_then.tv_usec != 0)) && (n < sizeof(str))) {
72		snprintf(&str[n], sizeof(str) - n, " (+%ld.%03d)",
73			 tv_diff.tv_sec,
74			 tv_diff.tv_usec / 1000);
75	}
76
77	tv_then = tv_now;
78	return str;
79}
80
81
82static CFComparisonResult
83sort_keys(const void *p1, const void *p2, void *context) {
84	CFStringRef key1 = (CFStringRef)p1;
85	CFStringRef key2 = (CFStringRef)p2;
86	return CFStringCompare(key1, key2, 0);
87}
88
89
90__private_extern__
91void
92storeCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info)
93{
94	int		i;
95	CFIndex		n;
96
97	SCPrint(TRUE, stdout, CFSTR("notification callback (store address = %p).\n"), store);
98
99	n = CFArrayGetCount(changedKeys);
100	if (n > 0) {
101		for (i = 0; i < n; i++) {
102			SCPrint(TRUE,
103				stdout,
104				CFSTR("  %s changedKey [%d] = %@\n"),
105				elapsed(),
106				i,
107				CFArrayGetValueAtIndex(changedKeys, i));
108		}
109	} else {
110		SCPrint(TRUE, stdout, CFSTR("  no changed key's.\n"));
111	}
112
113	return;
114}
115
116
117__private_extern__
118void
119do_notify_list(int argc, char **argv)
120{
121	int			i;
122	CFArrayRef		list;
123	CFIndex			listCnt;
124	Boolean			isRegex = FALSE;
125	CFMutableArrayRef	sortedList;
126
127	if (store == NULL) {
128		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(kSCStatusNoStoreSession));
129		return;
130	}
131
132	if (argc == 1)
133		isRegex = TRUE;
134
135	list = isRegex ? watchedPatterns : watchedKeys;
136	if (!list) {
137		SCPrint(TRUE,
138			stdout,
139			CFSTR("  no notifier %s.\n"),
140			isRegex ? "patterns" : "keys");
141		return;
142	}
143
144	listCnt = CFArrayGetCount(list);
145	sortedList = CFArrayCreateMutableCopy(NULL, listCnt, list);
146	CFArraySortValues(sortedList,
147			  CFRangeMake(0, listCnt),
148			  sort_keys,
149			  NULL);
150
151	if (listCnt > 0) {
152		for (i = 0; i < listCnt; i++) {
153			SCPrint(TRUE,
154				stdout,
155				CFSTR("  notifier %s [%d] = %@\n"),
156				isRegex ? "pattern" : "key",
157				i,
158				CFArrayGetValueAtIndex(sortedList, i));
159		}
160	} else {
161		SCPrint(TRUE,
162			stdout,
163			CFSTR("  no notifier %s.\n"),
164			isRegex ? "patterns" : "keys");
165	}
166	CFRelease(sortedList);
167
168	return;
169}
170
171
172__private_extern__
173void
174do_notify_add(int argc, char **argv)
175{
176	CFStringRef		key;
177	CFMutableArrayRef	keys;
178	Boolean			isRegex = FALSE;
179
180	if (store == NULL) {
181		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(kSCStatusNoStoreSession));
182		return;
183	}
184
185	key = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
186
187	if (argc == 2)
188		isRegex = TRUE;
189
190	keys = isRegex ? watchedPatterns : watchedKeys;
191	if (CFArrayContainsValue(keys, CFRangeMake(0, CFArrayGetCount(keys)), key)) {
192		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(kSCStatusKeyExists));
193		CFRelease(key);
194		return;
195	}
196
197	CFArrayAppendValue(keys, key);
198	CFRelease(key);
199
200	if (!SCDynamicStoreSetNotificationKeys(store, watchedKeys, watchedPatterns)) {
201		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
202	}
203
204	return;
205}
206
207
208__private_extern__
209void
210do_notify_remove(int argc, char **argv)
211{
212	CFStringRef		key;
213	CFMutableArrayRef	keys;
214	CFIndex			i;
215	Boolean			isRegex = FALSE;
216
217	if (store == NULL) {
218		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(kSCStatusNoStoreSession));
219		return;
220	}
221
222	key   = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
223
224	if (argc == 2)
225		isRegex = TRUE;
226
227	keys = isRegex ? watchedPatterns : watchedKeys;
228	i = CFArrayGetFirstIndexOfValue(keys, CFRangeMake(0, CFArrayGetCount(keys)), key);
229	CFRelease(key);
230
231	if (i == kCFNotFound) {
232		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(kSCStatusNoKey));
233		return;
234	}
235
236	CFArrayRemoveValueAtIndex(keys, i);
237
238	if (!SCDynamicStoreSetNotificationKeys(store, watchedKeys, watchedPatterns)) {
239		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
240	}
241
242	return;
243}
244
245
246__private_extern__
247void
248do_notify_changes(int argc, char **argv)
249{
250	CFArrayRef	list;
251	CFIndex		listCnt;
252	int		i;
253
254	list = SCDynamicStoreCopyNotifiedKeys(store);
255	if (!list) {
256		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
257		return;
258	}
259
260	listCnt = CFArrayGetCount(list);
261	if (listCnt > 0) {
262		for (i = 0; i < listCnt; i++) {
263			SCPrint(TRUE,
264				stdout,
265				CFSTR("  %s changedKey [%d] = %@\n"),
266				elapsed(),
267				i,
268				CFArrayGetValueAtIndex(list, i));
269		}
270	} else {
271		SCPrint(TRUE, stdout, CFSTR("  no changedKey's.\n"));
272	}
273	CFRelease(list);
274
275	return;
276}
277
278
279static void *
280_watcher(void *arg)
281{
282	notifyRl = CFRunLoopGetCurrent();
283	if (notifyRl == NULL) {
284		SCPrint(TRUE, stdout, CFSTR("  CFRunLoopGetCurrent() failed\n"));
285		return NULL;
286	}
287
288	if (doDispatch) {
289		if (!SCDynamicStoreSetDispatchQueue(store, dispatch_get_main_queue())) {
290			SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
291			notifyRl = NULL;
292			return NULL;
293		}
294		notifyRls = (CFRunLoopSourceRef)kCFNull;
295	} else {
296		notifyRls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
297		if (notifyRls == NULL) {
298			SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
299			notifyRl = NULL;
300			return NULL;
301		}
302		CFRunLoopAddSource(notifyRl, notifyRls, kCFRunLoopDefaultMode);
303	}
304
305	pthread_setname_np("n.watch");
306	CFRunLoopRun();
307	notifyRl = NULL;
308	return NULL;
309}
310
311__private_extern__
312void
313do_notify_watch(int argc, char **argv)
314{
315	pthread_attr_t	tattr;
316	pthread_t	tid;
317
318	if (notifyRl != NULL) {
319		SCPrint(TRUE, stdout, CFSTR("already active\n"));
320		return;
321	}
322
323	pthread_attr_init(&tattr);
324	pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
325	pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
326//      pthread_attr_setstacksize(&tattr, 96 * 1024); // each thread gets a 96K stack
327	pthread_create(&tid, &tattr, _watcher, NULL);
328	pthread_attr_destroy(&tattr);
329
330	return;
331}
332
333
334__private_extern__
335void
336do_notify_wait(int argc, char **argv)
337{
338	if (!SCDynamicStoreNotifyWait(store)) {
339		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
340		return;
341	}
342
343	return;
344}
345
346
347__private_extern__
348void
349do_notify_file(int argc, char **argv)
350{
351	int32_t		reqID = 0;
352	int		fd;
353	union {
354		char	data[4];
355		int32_t	gotID;
356	} buf;
357	char		*bufPtr;
358	int		needed;
359
360	if (argc == 1) {
361		if ((sscanf(argv[0], "%d", &reqID) != 1)) {
362			SCPrint(TRUE, stdout, CFSTR("invalid identifier.\n"));
363			return;
364		}
365	}
366
367	if (!SCDynamicStoreNotifyFileDescriptor(store, reqID, &fd)) {
368		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
369		return;
370	}
371
372	bzero(buf.data, sizeof(buf.data));
373	bufPtr = &buf.data[0];
374	needed = sizeof(buf.gotID);
375	while (needed > 0) {
376		ssize_t	got;
377
378		got = read(fd, bufPtr, needed);
379		if (got == -1) {
380			/* if error detected */
381			SCPrint(TRUE, stdout, CFSTR("read() failed: %s.\n"), strerror(errno));
382			break;
383		}
384
385		if (got == 0) {
386			/* if end of file detected */
387			SCPrint(TRUE, stdout, CFSTR("read(): detected end of file.\n"));
388			break;
389		}
390
391		SCPrint(TRUE, stdout, CFSTR("Received %ld bytes.\n"), got);
392		bufPtr += got;
393		needed -= got;
394	}
395
396	if (needed != sizeof(buf.gotID)) {
397		SCPrint(TRUE, stdout, CFSTR("  Received notification, identifier = %d.\n"), buf.gotID);
398	}
399
400	/* report the keys that changed */
401	do_notify_changes(0, NULL);
402
403	/* this utility only allows processes one notification per "n.file" request */
404	(void) SCDynamicStoreNotifyCancel(store);
405
406	(void) close(fd);	/* close my side of the file descriptor */
407
408	return;
409}
410
411
412static void
413signalCatcher(int signum)
414{
415	static int	n = 0;
416
417	SCPrint(TRUE, stdout, CFSTR("Received sig%s (#%d).\n"), sys_signame[signum], n++);
418	return;
419}
420
421
422__private_extern__
423void
424do_notify_signal(int argc, char **argv)
425{
426	int			sig;
427	pid_t			pid;
428	struct sigaction	nact;
429
430	if (isdigit(*argv[0])) {
431		if ((sscanf(argv[0], "%d", &sig) != 1) || (sig <= 0) || (sig >= NSIG)) {
432			SCPrint(TRUE, stdout, CFSTR("signal must be in the range of 1 .. %d.\n"), NSIG-1);
433			return;
434		}
435	} else {
436		for (sig = 1; sig < NSIG; sig++) {
437			if (strcasecmp(argv[0], sys_signame[sig]) == 0)
438				break;
439		}
440		if (sig >= NSIG) {
441			CFMutableStringRef	str;
442
443			SCPrint(TRUE, stdout, CFSTR("Signal must be one of the following:\n"));
444
445			str = CFStringCreateMutable(NULL, 0);
446			for (sig = 1; sig < NSIG; sig++) {
447				CFStringAppendFormat(str, NULL, CFSTR(" %-6s"), sys_signame[sig]);
448				if ((sig % 10) == 0) {
449					CFStringAppendFormat(str, NULL, CFSTR("\n"));
450				}
451			}
452			if ((sig % 10) != 0) {
453				CFStringAppendFormat(str, NULL, CFSTR("\n"));
454			}
455			SCPrint(TRUE, stdout, CFSTR("%@"), str);
456			CFRelease(str);
457			return;
458		}
459
460	}
461
462	if ((argc != 2) || (sscanf(argv[1], "%d", &pid) != 1)) {
463		pid = getpid();
464	}
465
466	if (oact != NULL) {
467		(void) sigaction(osig, oact, NULL);	/* restore original signal handler */
468	} else {
469		oact = malloc(sizeof(struct sigaction));
470	}
471
472	nact.sa_handler = signalCatcher;
473	sigemptyset(&nact.sa_mask);
474	nact.sa_flags = SA_RESTART;
475	(void) sigaction(sig, &nact, oact);
476	osig = sig;
477	SCPrint(TRUE, stdout, CFSTR("signal handler started.\n"));
478
479	if (!SCDynamicStoreNotifySignal(store, pid, sig)) {
480		SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
481		return;
482	}
483
484	return;
485}
486
487
488__private_extern__
489void
490do_notify_cancel(int argc, char **argv)
491{
492	if (notifyRls != NULL) {
493		if (doDispatch) {
494			if (!SCDynamicStoreSetDispatchQueue(store, NULL)) {
495				SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
496				return;
497			}
498		} else {
499			CFRunLoopSourceInvalidate(notifyRls);
500			CFRelease(notifyRls);
501		}
502		notifyRls = NULL;
503	} else {
504		if (!SCDynamicStoreNotifyCancel(store)) {
505			SCPrint(TRUE, stdout, CFSTR("  %s\n"), SCErrorString(SCError()));
506			return;
507		}
508	}
509
510	if (notifyRl != NULL) {
511		CFRunLoopStop(notifyRl);
512	}
513
514	if (oact != NULL) {
515		(void) sigaction(osig, oact, NULL);	/* restore original signal handler */
516		free(oact);
517		oact = NULL;
518	}
519
520	return;
521}
522