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