1/*
2 * Copyright (c) 2003-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 <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28#include <assert.h>
29#include <asl.h>
30#include "notify.h"
31#include "notifyd.h"
32#include "service.h"
33#include "pathwatch.h"
34#include "timer.h"
35
36#define NOTIFY_PATH_SERVICE "path:"
37#define NOTIFY_PATH_SERVICE_LEN 5
38#define NOTIFY_TIMER_SERVICE "timer:"
39#define NOTIFY_TIMER_SERVICE_LEN 6
40
41/* Libinfo global */
42extern uint32_t gL1CacheEnabled;
43
44static uint32_t
45service_type(const char *name)
46{
47	uint32_t len;
48
49	len = SERVICE_PREFIX_LEN;
50	if (strncmp(name, SERVICE_PREFIX, len)) return SERVICE_TYPE_NONE;
51	else if (!strncmp(name + len, NOTIFY_PATH_SERVICE, NOTIFY_PATH_SERVICE_LEN)) return SERVICE_TYPE_PATH_PRIVATE;
52	else if (!strncmp(name + len, NOTIFY_TIMER_SERVICE, NOTIFY_TIMER_SERVICE_LEN)) return SERVICE_TYPE_TIMER_PRIVATE;
53
54	return SERVICE_TYPE_NONE;
55}
56
57/*
58 * Request notifications for changes on a filesystem path.
59 * This creates a new pathwatch node and sets it to post notifications for
60 * the specified name.
61 *
62 * If the notify name already has a pathwatch node for this path, this routine
63 * does nothing and allows the client to piggypack on the existing path watcher.
64 *
65 * Note that this routine is only called for path monitoring as directed by
66 * a "monitor" command in /etc/notify.conf, so only an admin can set up a path
67 * that gets public notifications.  A client being serviced by the server-side
68 * routines in notify_proc.c will only be able to register for a private
69 * (per-client) notification for a path.  This prevents a client from
70 * piggybacking on another client's notifications, and thus prevents the client
71 * from getting notifications for a path to which they don't have access.
72 */
73int
74service_open_path(const char *name, const char *path, uid_t uid, gid_t gid)
75{
76	name_info_t *n;
77	svc_info_t *info;
78	path_node_t *node;
79
80	call_statistics.service_path++;
81
82	if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST;
83
84	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
85	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
86
87	if (n->private != NULL)
88	{
89		/* a notify key may only have one service associated with it */
90		info = (svc_info_t *)n->private;
91		if (info->type != SERVICE_TYPE_PATH_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST;
92
93		/* the client must be asking for the same path that is being monitored */
94		node = (path_node_t *)info->private;
95		if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST;
96
97		/* the name is already getting notifications for this path */
98		return NOTIFY_STATUS_OK;
99	}
100
101	node = path_node_create(path, uid, gid, PATH_NODE_ALL, dispatch_get_main_queue());
102	if (node == NULL) return NOTIFY_STATUS_FAILED;
103
104	node->contextp = strdup(name);
105
106	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
107	assert(info != NULL);
108
109	info->type = SERVICE_TYPE_PATH_PUBLIC;
110	info->private = node;
111	n->private = info;
112
113	dispatch_source_set_event_handler(node->src, ^{
114		dispatch_async(global.work_q, ^{
115			if (0 == dispatch_source_testcancel(node->src))
116			{
117				daemon_post((const char *)node->contextp, uid, gid);
118			}
119		});
120	});
121
122	dispatch_resume(node->src);
123
124	return NOTIFY_STATUS_OK;
125}
126
127/*
128 * The private (per-client) path watch service.
129 */
130int
131service_open_path_private(const char *name, client_t *c, const char *path, uid_t uid, gid_t gid, uint32_t flags)
132{
133	name_info_t *n;
134	svc_info_t *info;
135	path_node_t *node;
136
137	call_statistics.service_path++;
138
139	if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST;
140
141	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
142	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
143	if (c == NULL) return NOTIFY_STATUS_FAILED;
144
145	if (c->private != NULL)
146	{
147		/* a client may only have one service */
148		info = (svc_info_t *)c->private;
149		if (info->type != SERVICE_TYPE_PATH_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST;
150
151		/* the client must be asking for the same path that is being monitored */
152		node = (path_node_t *)info->private;
153		if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST;
154
155		/* the client is already getting notifications for this path */
156		return NOTIFY_STATUS_OK;
157	}
158
159	if (flags == 0) flags = PATH_NODE_ALL;
160
161	node = path_node_create(path, uid, gid, flags, dispatch_get_main_queue());
162	if (node == NULL) return NOTIFY_STATUS_FAILED;
163
164	node->context64 = c->client_id;
165
166	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
167	assert(info != NULL);
168
169	info->type = SERVICE_TYPE_PATH_PRIVATE;
170	info->private = node;
171	c->private = info;
172
173	dispatch_source_set_event_handler(node->src, ^{
174		dispatch_async(global.work_q, ^{
175			if (0 == dispatch_source_testcancel(node->src))
176			{
177				daemon_post_client(node->context64);
178			}
179		});
180	});
181
182	dispatch_resume(node->src);
183
184	return NOTIFY_STATUS_OK;
185}
186
187/* format: [+]nnnn[s|m|h|d] */
188static int
189parse_single_arg(const char *arg, int relative_ok, time_t *t)
190{
191	const char *p, *q;
192	time_t now, val;
193
194	if (arg == NULL) return -1;
195	p = arg;
196
197	now = 0;
198
199	if ((relative_ok != 0) && ((*p == '+') || (*p == '-')))
200	{
201		p++;
202		now = time(NULL);
203	}
204
205	if ((*p < '0') || (*p > '9')) return -1;
206
207	q = strchr(p, '.');
208	if (q != NULL) q--;
209	else q = arg + strlen(arg) - 1;
210
211#ifdef __LP64__
212	val = (time_t)atoll(p);
213#else
214	val = (time_t)atoi(p);
215#endif
216
217	if ((*q >= '0') && (*q <= '9'))
218	{}
219	else if (*q == 's')
220	{}
221	else if (*q == 'm')
222	{
223		val *= 60;
224	}
225	else if (*q == 'h')
226	{
227		val *= 3600;
228	}
229	else if (*q == 'd')
230	{
231		val *= 86400;
232	}
233	else
234	{
235		return -1;
236	}
237
238	if (*arg == '-') *t = now - val;
239	else *t = now + val;
240
241	return 0;
242}
243
244static uint32_t
245parse_timer_args(const char *args, time_t *s, time_t *f, time_t *e, int32_t *d)
246{
247	char *p;
248	uint32_t t;
249
250	if (args == NULL) return TIME_EVENT_NONE;
251
252	/* first arg is start time */
253	if (parse_single_arg(args, 1, s) != 0) return TIME_EVENT_NONE;
254	t = TIME_EVENT_ONESHOT;
255
256	p = strchr(args, '.');
257	if (p != NULL)
258	{
259		/* second arg is frequency */
260		p++;
261		if (parse_single_arg(p, 0, f) != 0) return TIME_EVENT_NONE;
262		t = TIME_EVENT_CLOCK;
263
264		p = strchr(p, '.');
265		if (p != NULL)
266		{
267			/* third arg is end time */
268			p++;
269			if (parse_single_arg(args, 1, e) != 0) return TIME_EVENT_NONE;
270
271			p = strchr(p, '.');
272			if (p != NULL)
273			{
274				/* fourth arg is day number */
275				p++;
276				*d = atoi(p);
277				t = TIME_EVENT_CAL;
278			}
279		}
280	}
281
282	if (f == 0) t = TIME_EVENT_ONESHOT;
283
284	return t;
285}
286
287int
288service_open_timer(const char *name, const char *args)
289{
290	uint32_t t;
291	time_t s, f, e;
292	int32_t d;
293	name_info_t *n;
294	svc_info_t *info;
295	timer_t *timer;
296
297	call_statistics.service_timer++;
298
299	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
300	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
301
302	s = f = e = 0;
303	d = 0;
304
305	t = parse_timer_args(args, &s, &f, &e, &d);
306	if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST;
307
308	if (n->private != NULL)
309	{
310		/* a notify key may only have one service associated with it */
311		info = (svc_info_t *)n->private;
312		if (info->type != SERVICE_TYPE_TIMER_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST;
313
314		/* the client must be asking for the same timer that is active */
315		timer = (timer_t *)info->private;
316		if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST;
317
318		/* the name is already getting notifications for this timer */
319		return NOTIFY_STATUS_OK;
320	}
321
322	switch (t)
323	{
324		case TIME_EVENT_ONESHOT:
325		{
326			timer = timer_oneshot(s, dispatch_get_main_queue());
327			break;
328		}
329		case TIME_EVENT_CLOCK:
330		{
331			timer = timer_clock(s, f, e, dispatch_get_main_queue());
332			break;
333		}
334		case TIME_EVENT_CAL:
335		{
336			timer = timer_calendar(s, f, d, e, dispatch_get_main_queue());
337			break;
338		}
339		default:
340		{
341			return NOTIFY_STATUS_FAILED;
342		}
343	}
344
345	if (timer == NULL) return NOTIFY_STATUS_FAILED;
346	timer->contextp = strdup(name);
347
348	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
349	assert(info != NULL);
350
351	info->type = SERVICE_TYPE_TIMER_PUBLIC;
352	info->private = timer;
353	n->private = info;
354
355	dispatch_source_set_event_handler(timer->src, ^{
356		dispatch_async(global.work_q, ^{
357			if (0 == dispatch_source_testcancel(timer->src))
358			{
359				daemon_post((const char *)timer->contextp, 0, 0);
360			}
361		});
362	});
363
364	dispatch_resume(timer->src);
365
366	return NOTIFY_STATUS_OK;
367}
368
369int
370service_open_timer_private(const char *name, client_t *c, const char *args)
371{
372	uint32_t t;
373	time_t s, f, e;
374	int32_t d;
375	name_info_t *n;
376	svc_info_t *info;
377	timer_t *timer;
378
379	call_statistics.service_timer++;
380
381	n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name);
382	if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
383	if (c == NULL) return NOTIFY_STATUS_FAILED;
384
385	s = f = e = 0;
386	d = 0;
387
388	t = parse_timer_args(args, &s, &f, &e, &d);
389	if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST;
390
391	if (c->private != NULL)
392	{
393		/* a client may only have one service */
394		info = (svc_info_t *)c->private;
395		if (info->type != SERVICE_TYPE_TIMER_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST;
396
397		/* the client must be asking for the same timer that is active */
398		timer = (timer_t *)info->private;
399		if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST;
400
401		/* the client is already getting notifications for this timer */
402		return NOTIFY_STATUS_OK;
403	}
404
405	switch (t)
406	{
407		case TIME_EVENT_ONESHOT:
408		{
409			timer = timer_oneshot(s, dispatch_get_main_queue());
410			break;
411		}
412		case TIME_EVENT_CLOCK:
413		{
414			timer = timer_clock(s, f, e, dispatch_get_main_queue());
415			break;
416		}
417		case TIME_EVENT_CAL:
418		{
419			timer = timer_calendar(s, f, d, e, dispatch_get_main_queue());
420			break;
421		}
422		default:
423		{
424			return NOTIFY_STATUS_FAILED;
425		}
426	}
427
428	if (timer == NULL) return NOTIFY_STATUS_FAILED;
429	timer->context64 = c->client_id;
430
431	info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
432	assert(info != NULL);
433
434	info->type = SERVICE_TYPE_TIMER_PRIVATE;
435	info->private = timer;
436	c->private = info;
437
438	dispatch_source_set_event_handler(timer->src, ^{
439		dispatch_async(global.work_q, ^{
440			if (0 == dispatch_source_testcancel(timer->src))
441			{
442				daemon_post_client(timer->context64);
443			}
444		});
445	});
446
447	dispatch_resume(timer->src);
448
449	return NOTIFY_STATUS_OK;
450}
451
452/* called from server-side routines in notify_proc - services are private to the client */
453int
454service_open(const char *name, client_t *client, uint32_t uid, uint32_t gid)
455{
456	uint32_t t, flags;
457    char *p, *q;
458
459	t = service_type(name);
460
461	switch (t)
462	{
463		case SERVICE_TYPE_NONE:
464		{
465			return NOTIFY_STATUS_OK;
466		}
467		case SERVICE_TYPE_PATH_PRIVATE:
468		{
469			p = strchr(name, ':');
470			if (p != NULL) p++;
471
472			flags = 0;
473
474			q = strchr(p, ':');
475			if (q != NULL)
476			{
477				flags = strtol(p, NULL, 0);
478				p = q + 1;
479			}
480
481			return service_open_path_private(name, client, p, uid, gid, flags);
482		}
483		case SERVICE_TYPE_TIMER_PRIVATE:
484		{
485			p = strchr(name, ':');
486			if (p != NULL) p++;
487			return service_open_timer_private(name, client, p);
488		}
489		default:
490		{
491			return NOTIFY_STATUS_INVALID_REQUEST;
492		}
493	}
494
495	return NOTIFY_STATUS_INVALID_REQUEST;
496}
497
498void
499service_close(svc_info_t *info)
500{
501	if (info == NULL) return;
502
503	switch (info->type)
504	{
505		case SERVICE_TYPE_PATH_PUBLIC:
506		case SERVICE_TYPE_PATH_PRIVATE:
507		{
508			path_node_close((path_node_t *)info->private);
509			break;
510		}
511		case SERVICE_TYPE_TIMER_PUBLIC:
512		case SERVICE_TYPE_TIMER_PRIVATE:
513		{
514			timer_close((timer_t *)info->private);
515			break;
516		}
517		default:
518		{
519		}
520	}
521
522	free(info);
523}
524