/* * Copyright (c) 2003-2010 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include "notify.h" #include "notifyd.h" #include "service.h" #include "pathwatch.h" #include "timer.h" #define NOTIFY_PATH_SERVICE "path:" #define NOTIFY_PATH_SERVICE_LEN 5 #define NOTIFY_TIMER_SERVICE "timer:" #define NOTIFY_TIMER_SERVICE_LEN 6 /* Libinfo global */ extern uint32_t gL1CacheEnabled; static uint32_t service_type(const char *name) { uint32_t len; len = SERVICE_PREFIX_LEN; if (strncmp(name, SERVICE_PREFIX, len)) return SERVICE_TYPE_NONE; else if (!strncmp(name + len, NOTIFY_PATH_SERVICE, NOTIFY_PATH_SERVICE_LEN)) return SERVICE_TYPE_PATH_PRIVATE; else if (!strncmp(name + len, NOTIFY_TIMER_SERVICE, NOTIFY_TIMER_SERVICE_LEN)) return SERVICE_TYPE_TIMER_PRIVATE; return SERVICE_TYPE_NONE; } /* * Request notifications for changes on a filesystem path. * This creates a new pathwatch node and sets it to post notifications for * the specified name. * * If the notify name already has a pathwatch node for this path, this routine * does nothing and allows the client to piggypack on the existing path watcher. * * Note that this routine is only called for path monitoring as directed by * a "monitor" command in /etc/notify.conf, so only an admin can set up a path * that gets public notifications. A client being serviced by the server-side * routines in notify_proc.c will only be able to register for a private * (per-client) notification for a path. This prevents a client from * piggybacking on another client's notifications, and thus prevents the client * from getting notifications for a path to which they don't have access. */ int service_open_path(const char *name, const char *path, uid_t uid, gid_t gid) { name_info_t *n; svc_info_t *info; path_node_t *node; call_statistics.service_path++; if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST; n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; if (n->private != NULL) { /* a notify key may only have one service associated with it */ info = (svc_info_t *)n->private; if (info->type != SERVICE_TYPE_PATH_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST; /* the client must be asking for the same path that is being monitored */ node = (path_node_t *)info->private; if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST; /* the name is already getting notifications for this path */ return NOTIFY_STATUS_OK; } node = path_node_create(path, uid, gid, PATH_NODE_ALL, dispatch_get_main_queue()); if (node == NULL) return NOTIFY_STATUS_FAILED; node->contextp = strdup(name); info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); assert(info != NULL); info->type = SERVICE_TYPE_PATH_PUBLIC; info->private = node; n->private = info; dispatch_source_set_event_handler(node->src, ^{ dispatch_async(global.work_q, ^{ if (0 == dispatch_source_testcancel(node->src)) { daemon_post((const char *)node->contextp, uid, gid); } }); }); dispatch_resume(node->src); return NOTIFY_STATUS_OK; } /* * The private (per-client) path watch service. */ int service_open_path_private(const char *name, client_t *c, const char *path, uid_t uid, gid_t gid, uint32_t flags) { name_info_t *n; svc_info_t *info; path_node_t *node; call_statistics.service_path++; if (path == NULL) return NOTIFY_STATUS_INVALID_REQUEST; n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; if (c == NULL) return NOTIFY_STATUS_FAILED; if (c->private != NULL) { /* a client may only have one service */ info = (svc_info_t *)c->private; if (info->type != SERVICE_TYPE_PATH_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST; /* the client must be asking for the same path that is being monitored */ node = (path_node_t *)info->private; if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST; /* the client is already getting notifications for this path */ return NOTIFY_STATUS_OK; } if (flags == 0) flags = PATH_NODE_ALL; node = path_node_create(path, uid, gid, flags, dispatch_get_main_queue()); if (node == NULL) return NOTIFY_STATUS_FAILED; node->context64 = c->client_id; info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); assert(info != NULL); info->type = SERVICE_TYPE_PATH_PRIVATE; info->private = node; c->private = info; dispatch_source_set_event_handler(node->src, ^{ dispatch_async(global.work_q, ^{ if (0 == dispatch_source_testcancel(node->src)) { daemon_post_client(node->context64); } }); }); dispatch_resume(node->src); return NOTIFY_STATUS_OK; } /* format: [+]nnnn[s|m|h|d] */ static int parse_single_arg(const char *arg, int relative_ok, time_t *t) { const char *p, *q; time_t now, val; if (arg == NULL) return -1; p = arg; now = 0; if ((relative_ok != 0) && ((*p == '+') || (*p == '-'))) { p++; now = time(NULL); } if ((*p < '0') || (*p > '9')) return -1; q = strchr(p, '.'); if (q != NULL) q--; else q = arg + strlen(arg) - 1; #ifdef __LP64__ val = (time_t)atoll(p); #else val = (time_t)atoi(p); #endif if ((*q >= '0') && (*q <= '9')) {} else if (*q == 's') {} else if (*q == 'm') { val *= 60; } else if (*q == 'h') { val *= 3600; } else if (*q == 'd') { val *= 86400; } else { return -1; } if (*arg == '-') *t = now - val; else *t = now + val; return 0; } static uint32_t parse_timer_args(const char *args, time_t *s, time_t *f, time_t *e, int32_t *d) { char *p; uint32_t t; if (args == NULL) return TIME_EVENT_NONE; /* first arg is start time */ if (parse_single_arg(args, 1, s) != 0) return TIME_EVENT_NONE; t = TIME_EVENT_ONESHOT; p = strchr(args, '.'); if (p != NULL) { /* second arg is frequency */ p++; if (parse_single_arg(p, 0, f) != 0) return TIME_EVENT_NONE; t = TIME_EVENT_CLOCK; p = strchr(p, '.'); if (p != NULL) { /* third arg is end time */ p++; if (parse_single_arg(args, 1, e) != 0) return TIME_EVENT_NONE; p = strchr(p, '.'); if (p != NULL) { /* fourth arg is day number */ p++; *d = atoi(p); t = TIME_EVENT_CAL; } } } if (f == 0) t = TIME_EVENT_ONESHOT; return t; } int service_open_timer(const char *name, const char *args) { uint32_t t; time_t s, f, e; int32_t d; name_info_t *n; svc_info_t *info; timer_t *timer; call_statistics.service_timer++; n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; s = f = e = 0; d = 0; t = parse_timer_args(args, &s, &f, &e, &d); if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST; if (n->private != NULL) { /* a notify key may only have one service associated with it */ info = (svc_info_t *)n->private; if (info->type != SERVICE_TYPE_TIMER_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST; /* the client must be asking for the same timer that is active */ timer = (timer_t *)info->private; if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST; /* the name is already getting notifications for this timer */ return NOTIFY_STATUS_OK; } switch (t) { case TIME_EVENT_ONESHOT: { timer = timer_oneshot(s, dispatch_get_main_queue()); break; } case TIME_EVENT_CLOCK: { timer = timer_clock(s, f, e, dispatch_get_main_queue()); break; } case TIME_EVENT_CAL: { timer = timer_calendar(s, f, d, e, dispatch_get_main_queue()); break; } default: { return NOTIFY_STATUS_FAILED; } } if (timer == NULL) return NOTIFY_STATUS_FAILED; timer->contextp = strdup(name); info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); assert(info != NULL); info->type = SERVICE_TYPE_TIMER_PUBLIC; info->private = timer; n->private = info; dispatch_source_set_event_handler(timer->src, ^{ dispatch_async(global.work_q, ^{ if (0 == dispatch_source_testcancel(timer->src)) { daemon_post((const char *)timer->contextp, 0, 0); } }); }); dispatch_resume(timer->src); return NOTIFY_STATUS_OK; } int service_open_timer_private(const char *name, client_t *c, const char *args) { uint32_t t; time_t s, f, e; int32_t d; name_info_t *n; svc_info_t *info; timer_t *timer; call_statistics.service_timer++; n = (name_info_t *)_nc_table_find(global.notify_state->name_table, name); if (n == NULL) return NOTIFY_STATUS_INVALID_NAME; if (c == NULL) return NOTIFY_STATUS_FAILED; s = f = e = 0; d = 0; t = parse_timer_args(args, &s, &f, &e, &d); if (t == TIME_EVENT_NONE) return NOTIFY_STATUS_INVALID_REQUEST; if (c->private != NULL) { /* a client may only have one service */ info = (svc_info_t *)c->private; if (info->type != SERVICE_TYPE_TIMER_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST; /* the client must be asking for the same timer that is active */ timer = (timer_t *)info->private; if ((timer->type != t) || (timer->start != s) || (timer->freq != f) || (timer->end != e) || (timer->day != d)) return NOTIFY_STATUS_INVALID_REQUEST; /* the client is already getting notifications for this timer */ return NOTIFY_STATUS_OK; } switch (t) { case TIME_EVENT_ONESHOT: { timer = timer_oneshot(s, dispatch_get_main_queue()); break; } case TIME_EVENT_CLOCK: { timer = timer_clock(s, f, e, dispatch_get_main_queue()); break; } case TIME_EVENT_CAL: { timer = timer_calendar(s, f, d, e, dispatch_get_main_queue()); break; } default: { return NOTIFY_STATUS_FAILED; } } if (timer == NULL) return NOTIFY_STATUS_FAILED; timer->context64 = c->client_id; info = (svc_info_t *)calloc(1, sizeof(svc_info_t)); assert(info != NULL); info->type = SERVICE_TYPE_TIMER_PRIVATE; info->private = timer; c->private = info; dispatch_source_set_event_handler(timer->src, ^{ dispatch_async(global.work_q, ^{ if (0 == dispatch_source_testcancel(timer->src)) { daemon_post_client(timer->context64); } }); }); dispatch_resume(timer->src); return NOTIFY_STATUS_OK; } /* called from server-side routines in notify_proc - services are private to the client */ int service_open(const char *name, client_t *client, uint32_t uid, uint32_t gid) { uint32_t t, flags; char *p, *q; t = service_type(name); switch (t) { case SERVICE_TYPE_NONE: { return NOTIFY_STATUS_OK; } case SERVICE_TYPE_PATH_PRIVATE: { p = strchr(name, ':'); if (p != NULL) p++; flags = 0; q = strchr(p, ':'); if (q != NULL) { flags = strtol(p, NULL, 0); p = q + 1; } return service_open_path_private(name, client, p, uid, gid, flags); } case SERVICE_TYPE_TIMER_PRIVATE: { p = strchr(name, ':'); if (p != NULL) p++; return service_open_timer_private(name, client, p); } default: { return NOTIFY_STATUS_INVALID_REQUEST; } } return NOTIFY_STATUS_INVALID_REQUEST; } void service_close(svc_info_t *info) { if (info == NULL) return; switch (info->type) { case SERVICE_TYPE_PATH_PUBLIC: case SERVICE_TYPE_PATH_PRIVATE: { path_node_close((path_node_t *)info->private); break; } case SERVICE_TYPE_TIMER_PUBLIC: case SERVICE_TYPE_TIMER_PRIVATE: { timer_close((timer_t *)info->private); break; } default: { } } free(info); }