#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/syscall.h>
#include <sys/kauth.h>
#include <pwd.h>
#include <fcntl.h>
#include <assert.h>
#include <tzfile.h>
#include "pathwatch.h"
#define forever for(;;)
#define streq(A,B) (strcmp(A,B)==0)
#define DISPATCH_VNODE_ALL 0x7f
#define PATH_STAT_OK 0
#define PATH_STAT_FAILED 1
#define PATH_STAT_ACCESS 2
#define VPATH_NODE_TYPE_REG 0
#define VPATH_NODE_TYPE_LINK 1
#define VPATH_NODE_TYPE_DELETED 2
#define DISPATCH_VNODE_UNAVAIL (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE)
extern uint32_t gL1CacheEnabled;
typedef struct
{
char *path;
uint32_t type;
int fd;
struct timespec mtime;
struct timespec ctime;
dispatch_source_t src;
uint32_t path_node_count;
path_node_t **path_node;
} vnode_t;
static struct
{
dispatch_once_t pathwatch_init;
dispatch_queue_t pathwatch_queue;
uint32_t vnode_count;
vnode_t **vnode;
char *tzdir;
size_t tzdir_len;
} _global = {0};
static void _path_node_update(path_node_t *pnode, uint32_t flags, vnode_t *vnode);
static int
_path_stat(const char *path, int link, uid_t uid, gid_t gid)
{
struct stat sb;
gid_t orig_gidset[NGROUPS_MAX];
int ngroups, status, stat_status;
struct passwd *p;
uint32_t orig_cache_enabled;
orig_cache_enabled = gL1CacheEnabled;
gL1CacheEnabled = 0;
memset(orig_gidset, 0, sizeof(orig_gidset));
ngroups = getgroups(NGROUPS_MAX, orig_gidset);
if (ngroups < 0)
{
return PATH_STAT_FAILED;
}
p = getpwuid(uid);
if (p == NULL)
{
gL1CacheEnabled = orig_cache_enabled;
return PATH_STAT_FAILED;
}
status = initgroups(p->pw_name, gid);
if (status < 0)
{
gL1CacheEnabled = orig_cache_enabled;
return PATH_STAT_FAILED;
}
gL1CacheEnabled = orig_cache_enabled;
pthread_setugid_np(uid, gid);
stat_status = -1;
if (link != 0)
{
stat_status = lstat(path, &sb);
}
else
{
stat_status = stat(path, &sb);
}
pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE);
status = syscall(SYS_initgroups, ngroups, orig_gidset, 0);
if (status < 0)
{
return PATH_STAT_FAILED;
}
if (stat_status == 0)
{
return PATH_STAT_OK;
}
if (errno == EACCES)
{
return PATH_STAT_ACCESS;
}
return PATH_STAT_FAILED;
}
static int
_path_stat_check_access(const char *path, uid_t uid, gid_t gid, uint32_t *ftype)
{
struct stat sb;
char buf[MAXPATHLEN + 1];
int status, t;
if (path == NULL) return PATH_STAT_FAILED;
if (ftype != NULL) *ftype = PATH_NODE_TYPE_GHOST;
if (path[0] != '/') return PATH_STAT_FAILED;
if (path[1] == '\0')
{
if (ftype != NULL) *ftype = PATH_NODE_TYPE_DIR;
return PATH_STAT_OK;
}
memset(&sb, 0, sizeof(struct stat));
status = lstat(path, &sb);
if (status != 0) return PATH_STAT_FAILED;
else if ((sb.st_mode & S_IFMT) == S_IFDIR) t = PATH_NODE_TYPE_DIR;
else if ((sb.st_mode & S_IFMT) == S_IFREG) t = PATH_NODE_TYPE_FILE;
else if ((sb.st_mode & S_IFMT) == S_IFLNK) t = PATH_NODE_TYPE_LINK;
else t = PATH_NODE_TYPE_OTHER;
if (ftype != NULL) *ftype = t;
if (t == PATH_NODE_TYPE_OTHER) return PATH_STAT_FAILED;
if (uid == 0) return 0;
memset(buf, 0, sizeof(buf));
if (realpath(path, buf) == NULL) return PATH_STAT_FAILED;
if ((_global.tzdir != NULL) && (!strncasecmp(buf, _global.tzdir, _global.tzdir_len)))
{
return PATH_STAT_OK;
}
if (t == PATH_NODE_TYPE_FILE)
{
status = _path_stat(path, 0, uid, gid);
if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST;
return status;
}
else if (t == PATH_NODE_TYPE_LINK)
{
status = _path_stat(path, 1, uid, gid);
if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST;
return status;
}
else if (t == PATH_NODE_TYPE_DIR)
{
snprintf(buf, MAXPATHLEN, "%s/.", path);
status = _path_stat(buf, 0, uid, gid);
if ((status == PATH_STAT_ACCESS) && (ftype != NULL)) *ftype = PATH_NODE_TYPE_GHOST;
return status;
}
return PATH_STAT_FAILED;
}
static void
_vnode_add_pnode(vnode_t *vnode, path_node_t *pnode)
{
uint32_t i;
for (i = 0; i < vnode->path_node_count; i++)
{
if (vnode->path_node[i] == pnode) return;
}
for (i = 0; i < vnode->path_node_count; i++)
{
if (vnode->path_node[i] == NULL)
{
vnode->path_node[i] = pnode;
return;
}
}
if (vnode->path_node_count == 0)
{
vnode->path_node = (path_node_t **)calloc(1, sizeof(path_node_t *));
}
else
{
vnode->path_node = (path_node_t **)reallocf(vnode->path_node, (vnode->path_node_count + 1) * sizeof(path_node_t *));
}
assert(vnode->path_node != NULL);
vnode->path_node[vnode->path_node_count++] = pnode;
}
static void
_vnode_free(vnode_t *vnode)
{
dispatch_source_cancel(vnode->src);
dispatch_async(_global.pathwatch_queue, ^{
dispatch_release(vnode->src);
free(vnode->path);
free(vnode->path_node);
free(vnode);
});
}
static void
_vnode_event(vnode_t *vnode)
{
uint32_t i, flags;
unsigned long ulf;
struct stat sb;
if (vnode == NULL) return;
if ((vnode->src != NULL) && (dispatch_source_testcancel(vnode->src))) return;
ulf = dispatch_source_get_data(vnode->src);
flags = ulf;
memset(&sb, 0, sizeof(struct stat));
if (fstat(vnode->fd, &sb) == 0)
{
if ((vnode->mtime.tv_sec != sb.st_mtimespec.tv_sec) || (vnode->mtime.tv_nsec != sb.st_mtimespec.tv_nsec))
{
flags |= PATH_NODE_MTIME;
vnode->mtime = sb.st_mtimespec;
}
if ((vnode->ctime.tv_sec != sb.st_ctimespec.tv_sec) || (vnode->ctime.tv_nsec != sb.st_ctimespec.tv_nsec))
{
flags |= PATH_NODE_CTIME;
vnode->ctime = sb.st_ctimespec;
}
}
if (flags & DISPATCH_VNODE_DELETE) vnode->type = VPATH_NODE_TYPE_DELETED;
for (i = 0; i < vnode->path_node_count; i++)
{
_path_node_update(vnode->path_node[i], flags, vnode);
}
}
static vnode_t *
_vnode_create(const char *path, uint32_t type, path_node_t *pnode)
{
int fd, flags;
uint32_t i;
vnode_t *vnode;
dispatch_source_t src;
struct stat sb;
if (path == NULL) path = "/";
if (path[0] == '\0') path = "/";
for (i = 0; i < _global.vnode_count; i++)
{
vnode = _global.vnode[i];
if (vnode == NULL) continue;
if ((vnode->type == type) && (streq(path, vnode->path)))
{
_vnode_add_pnode(vnode, pnode);
return vnode;
}
}
vnode = NULL;
flags = O_EVTONLY;
if (type == VPATH_NODE_TYPE_LINK) flags |= O_SYMLINK;
fd = open(path, flags, 0);
if (fd < 0) return NULL;
src = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, (uintptr_t)fd, DISPATCH_VNODE_ALL, _global.pathwatch_queue);
if (src == NULL)
{
close(fd);
return NULL;
}
vnode = (vnode_t *)calloc(1, sizeof(vnode_t));
assert(vnode != NULL);
vnode->type = type;
vnode->path = strdup(path);
assert(vnode->path != NULL);
vnode->fd = fd;
vnode->src = src;
memset(&sb, 0, sizeof(struct stat));
if (fstat(fd, &sb) == 0)
{
vnode->mtime = sb.st_mtimespec;
vnode->ctime = sb.st_ctimespec;
}
_vnode_add_pnode(vnode, pnode);
dispatch_source_set_event_handler(src, ^{ _vnode_event(vnode); });
dispatch_source_set_cancel_handler(src, ^{ close(fd); });
if (_global.vnode_count == 0)
{
_global.vnode = (vnode_t **)calloc(1, sizeof(vnode_t *));
}
else
{
_global.vnode = (vnode_t **)reallocf(_global.vnode, (_global.vnode_count + 1) * sizeof(vnode_t *));
}
assert(_global.vnode != NULL);
_global.vnode[_global.vnode_count++] = vnode;
dispatch_resume(src);
return vnode;
}
static vnode_t *
_vnode_create_real_path(const char *path, uint32_t type, path_node_t *pnode)
{
char real[MAXPATHLEN + 1];
if (path == NULL) return _vnode_create(path, type, pnode);
if (NULL != realpath(path, real)) return _vnode_create(real, type, pnode);
return NULL;
}
static void
_vnode_sweep()
{
uint32_t i, j, new_vnode_count, new_path_node_count;
vnode_t **new_source, *vnode;
path_node_t **new_path_node;
new_source = NULL;
for (i = 0; i < _global.vnode_count; i++)
{
vnode = _global.vnode[i];
if (vnode == NULL) continue;
new_path_node_count = 0;
new_path_node = NULL;
for (j = 0; j < vnode->path_node_count; j++)
{
if (vnode->path_node[j] != NULL) new_path_node_count++;
}
if (new_path_node_count == vnode->path_node_count)
{
continue;
}
else if (new_path_node_count > 0)
{
new_path_node = (path_node_t **)calloc(new_path_node_count, sizeof(path_node_t *));
assert(new_path_node != NULL);
new_path_node_count = 0;
for (j = 0; j < vnode->path_node_count; j++)
{
if (vnode->path_node[j] != NULL)
{
new_path_node[new_path_node_count++] = vnode->path_node[j];
}
}
}
free(vnode->path_node);
vnode->path_node = new_path_node;
vnode->path_node_count = new_path_node_count;
}
new_vnode_count = 0;
for (i = 0; i < _global.vnode_count; i++)
{
vnode = _global.vnode[i];
if (vnode == NULL) continue;
if (vnode->path_node_count > 0) new_vnode_count++;
}
if (new_vnode_count == _global.vnode_count)
{
return;
}
else if (new_vnode_count > 0)
{
new_source = (vnode_t **)calloc(new_vnode_count, sizeof(vnode_t *));
assert(new_source != NULL);
new_vnode_count = 0;
for (i = 0; i < _global.vnode_count; i++)
{
vnode = _global.vnode[i];
if (vnode == NULL) continue;
if (vnode->path_node_count > 0)
{
new_source[new_vnode_count++] = vnode;
}
else
{
_vnode_free(vnode);
}
}
}
free(_global.vnode);
_global.vnode = new_source;
_global.vnode_count = new_vnode_count;
}
static void
_vnode_release_for_node(path_node_t *pnode)
{
uint32_t i, j;
vnode_t *vnode;
for (i = 0; i < _global.vnode_count; i++)
{
vnode = _global.vnode[i];
if (vnode == NULL) continue;
for (j = 0; j < vnode->path_node_count; j++)
{
if (vnode->path_node[j] == pnode)
{
vnode->path_node[j] = NULL;
break;
}
}
}
}
static void
_path_node_retain(path_node_t *pnode)
{
if (pnode == NULL) return;
pnode->refcount++;
}
static void
_path_node_free(path_node_t *pnode)
{
uint32_t i, n;
if (pnode == NULL) return;
_vnode_release_for_node(pnode);
_vnode_sweep();
free(pnode->path);
if (pnode->pname != NULL)
{
n = pnode->pname_count;
pnode->pname_count = 0;
for (i = 0; i < n; i++)
{
free(pnode->pname[i]);
pnode->pname[i] = NULL;
}
free(pnode->pname);
}
free(pnode->contextp);
dispatch_release(pnode->src);
dispatch_release(pnode->src_queue);
memset(pnode, 0, sizeof(path_node_t));
free(pnode);
}
static void
_path_node_release(path_node_t *pnode)
{
if (pnode == NULL) return;
dispatch_async(pnode->src_queue, ^{
dispatch_async(_global.pathwatch_queue, ^{
if (pnode->refcount > 0) pnode->refcount--;
if (pnode->refcount == 0) _path_node_free(pnode);
});
});
}
void
path_node_close(path_node_t *pnode)
{
if (pnode == NULL) return;
if (pnode->src != NULL) dispatch_source_cancel(pnode->src);
_path_node_release(pnode);
}
static void
_pathwatch_init()
{
char buf[MAXPATHLEN];
_global.pathwatch_queue = dispatch_queue_create("pathwatch", NULL);
_global.tzdir = NULL;
_global.tzdir_len = 0;
if (realpath(TZDIR, buf) != NULL)
{
_global.tzdir_len = strlen(buf);
_global.tzdir = strdup(buf);
if (_global.tzdir == NULL) _global.tzdir_len = 0;
}
}
static path_node_t *
_path_node_init(const char *path)
{
size_t len;
uint32_t i;
path_node_t *pnode;
const char *start, *end;
char *name;
if (path == NULL) path = "/";
if (path[0] != '/') return NULL;
pnode = (path_node_t *)calloc(1, sizeof(path_node_t));
assert(pnode != NULL);
pnode->plen = 1;
start = path;
while (*start == '/') start++;
forever
{
end = strchr(start, '/');
if (end == NULL) end = strchr(start, '\0');
len = end - start;
if (len == 0) break;
pnode->plen += (len + 1);
name = NULL;
if (end == NULL)
{
name = strdup(start);
}
else
{
name = malloc(len + 1);
assert(name != NULL);
strncpy(name, start, len);
name[len] = '\0';
}
if (pnode->pname_count == 0)
{
pnode->pname = (char **)calloc(1, sizeof(char *));
}
else
{
pnode->pname = (char **)reallocf(pnode->pname, (pnode->pname_count + 1) * sizeof(char *));
}
assert(pnode->pname != NULL);
pnode->pname[pnode->pname_count] = name;
pnode->pname_count++;
start = end;
if (start != NULL)
{
while (*start == '/') start++;
}
}
pnode->path = calloc(1, pnode->plen);
assert(pnode->path != NULL);
for (i = 0; i < pnode->pname_count; i++)
{
strlcat(pnode->path, "/", pnode->plen);
strlcat(pnode->path, pnode->pname[i], pnode->plen);
}
return pnode;
}
static void
_path_node_update(path_node_t *pnode, uint32_t flags, vnode_t *vnode)
{
char *buf, fixed[MAXPATHLEN + 1];
uint32_t i, old_type;
int status;
unsigned long data;
struct stat sb;
if (pnode == NULL) return;
if ((pnode->src != NULL) && (dispatch_source_testcancel(pnode->src))) return;
old_type = pnode->type;
status = _path_stat_check_access(pnode->path, pnode->uid, pnode->gid, &(pnode->type));
if (status == PATH_STAT_ACCESS) flags |= DISPATCH_VNODE_REVOKE;
data = 0;
if (vnode != NULL)
{
if (flags & DISPATCH_VNODE_UNAVAIL)
{
pnode->type = PATH_NODE_TYPE_GHOST;
data |= (flags & DISPATCH_VNODE_UNAVAIL);
data |= DISPATCH_VNODE_DELETE;
}
if ((vnode->path != NULL) && (pnode->path != NULL) && streq(vnode->path, pnode->path))
{
data |= flags;
}
if (old_type == PATH_NODE_TYPE_GHOST)
{
if (pnode->type != PATH_NODE_TYPE_GHOST)
{
data |= PATH_NODE_CREATE;
}
else
{
data = 0;
}
}
else if (pnode->type == PATH_NODE_TYPE_GHOST)
{
data |= PATH_NODE_DELETE;
}
data &= (pnode->flags & PATH_NODE_ALL);
if (data != 0)
{
if ((pnode->flags & PATH_SRC_SUSPENDED) == 0)
{
pnode->flags |= PATH_SRC_SUSPENDED;
dispatch_suspend(pnode->src);
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, PNODE_COALESCE_TIME);
_path_node_retain(pnode);
dispatch_after(delay, _global.pathwatch_queue, ^{
pnode->flags &= ~PATH_SRC_SUSPENDED;
dispatch_resume(pnode->src);
_path_node_release(pnode);
});
}
dispatch_source_merge_data(pnode->src, data);
}
}
buf = NULL;
if (pnode->plen < MAXPATHLEN) buf = fixed;
else buf = malloc(pnode->plen);
assert(buf != NULL);
_vnode_release_for_node(pnode);
_vnode_create(NULL, 0, pnode);
memset(buf, 0, pnode->plen);
for (i = 0; i < pnode->pname_count; i++)
{
assert((strlen(buf) + 1) <= pnode->plen);
strlcat(buf, "/", pnode->plen);
assert(pnode->pname[i] != NULL);
assert((strlen(buf) + strlen(pnode->pname[i])) <= pnode->plen);
strlcat(buf, pnode->pname[i], pnode->plen);
memset(&sb, 0, sizeof(struct stat));
if (lstat(buf, &sb) < 0)
{
break;
}
if ((sb.st_mode & S_IFMT) == S_IFLNK)
{
_vnode_create(buf, VPATH_NODE_TYPE_LINK, pnode);
_vnode_create_real_path(buf, 0, pnode);
}
else
{
_vnode_create(buf, 0, pnode);
}
}
_vnode_sweep();
if (buf != fixed) free(buf);
}
path_node_t *
path_node_create(const char *path, uid_t uid, gid_t gid, uint32_t mask, dispatch_queue_t queue)
{
path_node_t *pnode;
dispatch_once(&(_global.pathwatch_init), ^{ _pathwatch_init(); });
pnode = _path_node_init(path);
if (pnode == NULL) return NULL;
pnode->refcount = 1;
pnode->uid = uid;
pnode->gid = gid;
dispatch_sync(_global.pathwatch_queue, ^{ _path_node_update(pnode, 0, NULL); });
dispatch_retain(queue);
pnode->src = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, queue);
pnode->src_queue = queue;
pnode->flags = mask & PATH_NODE_ALL;
return pnode;
}