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