#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <asl.h>
#include "notify.h"
#include "notifyd.h"
#include "service.h"
#include "pathwatch.h"
#include "timer.h"
#include "notify_internal.h"
#define NOTIFY_PATH_SERVICE "path:"
#define NOTIFY_PATH_SERVICE_LEN 5
#define NOTIFY_TIMER_SERVICE "timer:"
#define NOTIFY_TIMER_SERVICE_LEN 6
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;
}
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 = _nc_table_find(&global.notify_state.name_table, name);
if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
if (n->private != NULL)
{
info = (svc_info_t *)n->private;
if (info->type != SERVICE_TYPE_PATH_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST;
node = (path_node_t *)info->private;
if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST;
return NOTIFY_STATUS_OK;
}
{
audit_token_t audit;
memset(&audit, 0, sizeof(audit_token_t));
node = path_node_create(path, audit, true, PATH_NODE_ALL);
}
if (node == NULL) return NOTIFY_STATUS_PATH_NODE_CREATE_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, ^{
daemon_post((const char *)node->contextp, uid, gid);
});
dispatch_activate(node->src);
return NOTIFY_STATUS_OK;
}
static uint16_t service_info_add(void *info)
{
assert(global.service_info_count != UINT16_MAX);
for(int i = 0; i < global.service_info_count; i++)
{
if(global.service_info_list[i] == NULL){
global.service_info_list[i] = info;
return i + 1;
}
}
if(global.service_info_count == 0){
global.service_info_count = 1;
global.service_info_list = malloc(sizeof(void *));
} else {
global.service_info_count++;
global.service_info_list = realloc(global.service_info_list, global.service_info_count * sizeof(void *));
}
global.service_info_list[global.service_info_count - 1] = info;
return global.service_info_count;
}
void *service_info_get(uint16_t index)
{
if(index == 0)
{
return NULL;
}
return global.service_info_list[index - 1];
}
static void *service_info_remove(uint16_t index)
{
if(index == 0)
{
return NULL;
}
void *ret = global.service_info_list[index - 1];
global.service_info_list[index - 1] = NULL;
return ret;
}
int
service_open_path_private(const char *name, client_t *c, const char *path, audit_token_t audit, 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 = _nc_table_find(&global.notify_state.name_table, name);
if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
if (c == NULL) return NOTIFY_STATUS_NULL_INPUT;
if (c->service_index != 0)
{
info = (svc_info_t *)service_info_get(c->service_index);
if (info->type != SERVICE_TYPE_PATH_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST;
node = (path_node_t *)info->private;
if (strcmp(path, node->path)) return NOTIFY_STATUS_INVALID_REQUEST;
return NOTIFY_STATUS_OK;
}
if (flags == 0) flags = PATH_NODE_ALL;
node = path_node_create(path, audit, false, flags);
if (node == NULL) return NOTIFY_STATUS_PATH_NODE_CREATE_FAILED;
node->context64 = c->cid.hash_key;
info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
assert(info != NULL);
info->type = SERVICE_TYPE_PATH_PRIVATE;
info->private = node;
c->service_index = service_info_add(info);
dispatch_source_set_event_handler(node->src, ^{
daemon_post_client(node->context64);
});
dispatch_activate(node->src);
return NOTIFY_STATUS_OK;
}
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;
if (parse_single_arg(args, 1, s) != 0) return TIME_EVENT_NONE;
t = TIME_EVENT_ONESHOT;
p = strchr(args, '.');
if (p != NULL)
{
p++;
if (parse_single_arg(p, 0, f) != 0) return TIME_EVENT_NONE;
t = TIME_EVENT_CLOCK;
p = strchr(p, '.');
if (p != NULL)
{
p++;
if (parse_single_arg(args, 1, e) != 0) return TIME_EVENT_NONE;
p = strchr(p, '.');
if (p != NULL)
{
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 = _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)
{
info = (svc_info_t *)n->private;
if (info->type != SERVICE_TYPE_TIMER_PUBLIC) return NOTIFY_STATUS_INVALID_REQUEST;
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;
return NOTIFY_STATUS_OK;
}
switch (t)
{
case TIME_EVENT_ONESHOT:
{
timer = timer_oneshot(s, global.workloop);
break;
}
case TIME_EVENT_CLOCK:
{
timer = timer_clock(s, f, e, global.workloop);
break;
}
case TIME_EVENT_CAL:
{
timer = timer_calendar(s, f, e, d, global.workloop);
break;
}
default:
{
return NOTIFY_STATUS_INVALID_TIME_EVENT;
}
}
if (timer == NULL) return NOTIFY_STATUS_TIMER_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, ^{
daemon_post((const char *)timer->contextp, 0, 0);
});
dispatch_activate(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 = _nc_table_find(&global.notify_state.name_table, name);
if (n == NULL) return NOTIFY_STATUS_INVALID_NAME;
if (c == NULL) return NOTIFY_STATUS_NULL_INPUT;
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->service_index != 0)
{
info = (svc_info_t *)service_info_get(c->service_index);
if (info->type != SERVICE_TYPE_TIMER_PRIVATE) return NOTIFY_STATUS_INVALID_REQUEST;
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;
return NOTIFY_STATUS_OK;
}
switch (t)
{
case TIME_EVENT_ONESHOT:
{
timer = timer_oneshot(s, global.workloop);
break;
}
case TIME_EVENT_CLOCK:
{
timer = timer_clock(s, f, e, global.workloop);
break;
}
case TIME_EVENT_CAL:
{
timer = timer_calendar(s, f, e, d, global.workloop);
break;
}
default:
{
return NOTIFY_STATUS_INVALID_TIME_EVENT;
}
}
if (timer == NULL) return NOTIFY_STATUS_TIMER_FAILED;
timer->context64 = c->cid.hash_key;
info = (svc_info_t *)calloc(1, sizeof(svc_info_t));
assert(info != NULL);
info->type = SERVICE_TYPE_TIMER_PRIVATE;
info->private = timer;
c->service_index = service_info_add(info);
dispatch_source_set_event_handler(timer->src, ^{
daemon_post_client(timer->context64);
});
dispatch_activate(timer->src);
return NOTIFY_STATUS_OK;
}
int
service_open(const char *name, client_t *client, audit_token_t audit)
{
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 = (uint32_t)strtol(p, NULL, 0);
p = q + 1;
}
return service_open_path_private(name, client, p, audit, 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(uint16_t service_index)
{
if (service_index == 0) return;
svc_info_t *info = service_info_remove(service_index);
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);
}