166 lines
5.7 KiB
C
166 lines
5.7 KiB
C
#ifndef __DMON_EXTRA_H__
|
|
#define __DMON_EXTRA_H__
|
|
|
|
//
|
|
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
|
|
// License: https://github.com/septag/dmon#license-bsd-2-clause
|
|
//
|
|
// Extra header functionality for dmon.h for the backend based on inotify
|
|
//
|
|
// Add/Remove directory functions:
|
|
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
|
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
|
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
|
|
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
|
|
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
|
|
// will be reached. The default maximum is 8192.
|
|
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
|
|
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
|
|
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
|
|
//
|
|
|
|
#ifndef __DMON_H__
|
|
#error "Include 'dmon.h' before including this file"
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir, dmon_error *error_code);
|
|
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#ifdef DMON_IMPL
|
|
#if DMON_OS_LINUX
|
|
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir, dmon_error *error_code)
|
|
{
|
|
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
|
|
|
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
|
|
|
if (!skip_lock) {
|
|
dmon__mutex_wakeup_lock();
|
|
}
|
|
|
|
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
|
|
|
// check if the directory exists
|
|
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
|
|
// else, we assume that watchdir is correct, so save it as it is
|
|
struct stat st;
|
|
dmon__watch_subdir subdir;
|
|
// FIXME: check if it is a symlink and respect DMON_WATCHFLAGS_FOLLOW_SYMLINKS
|
|
// to resolve the link.
|
|
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
|
|
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
|
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
|
|
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
|
|
}
|
|
} else {
|
|
char fullpath[DMON_MAX_PATH];
|
|
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
|
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
|
|
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
|
|
*error_code = DMON_ERROR_UNSUPPORTED_SYMLINK;
|
|
if (!skip_lock)
|
|
pthread_mutex_unlock(&_dmon.mutex);
|
|
return false;
|
|
}
|
|
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
|
}
|
|
|
|
int dirlen = (int)strlen(subdir.rootdir);
|
|
if (subdir.rootdir[dirlen - 1] != '/') {
|
|
subdir.rootdir[dirlen] = '/';
|
|
subdir.rootdir[dirlen + 1] = '\0';
|
|
}
|
|
|
|
// check that the directory is not already added
|
|
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
|
|
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
|
|
*error_code = DMON_ERROR_SUBDIR_LOCATION;
|
|
if (!skip_lock)
|
|
pthread_mutex_unlock(&_dmon.mutex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
|
|
char fullpath[DMON_MAX_PATH];
|
|
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
|
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
|
|
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
|
|
if (wd == -1) {
|
|
*error_code = DMON_ERROR_WATCH_DIR;
|
|
if (!skip_lock)
|
|
pthread_mutex_unlock(&_dmon.mutex);
|
|
return false;
|
|
}
|
|
|
|
stb_sb_push(watch->subdirs, subdir);
|
|
stb_sb_push(watch->wds, wd);
|
|
|
|
if (!skip_lock)
|
|
pthread_mutex_unlock(&_dmon.mutex);
|
|
|
|
return true;
|
|
}
|
|
|
|
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
|
{
|
|
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
|
|
|
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
|
|
|
if (!skip_lock) {
|
|
dmon__mutex_wakeup_lock();
|
|
}
|
|
|
|
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
|
|
|
char subdir[DMON_MAX_PATH];
|
|
dmon__strcpy(subdir, sizeof(subdir), watchdir);
|
|
if (strstr(subdir, watch->rootdir) == subdir) {
|
|
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
|
|
}
|
|
|
|
int dirlen = (int)strlen(subdir);
|
|
if (subdir[dirlen - 1] != '/') {
|
|
subdir[dirlen] = '/';
|
|
subdir[dirlen + 1] = '\0';
|
|
}
|
|
|
|
int i, c = stb_sb_count(watch->subdirs);
|
|
for (i = 0; i < c; i++) {
|
|
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= c) {
|
|
if (!skip_lock)
|
|
pthread_mutex_unlock(&_dmon.mutex);
|
|
return false;
|
|
}
|
|
inotify_rm_watch(watch->fd, watch->wds[i]);
|
|
|
|
/* Remove entry from subdirs and wds by swapping position with the last entry */
|
|
watch->subdirs[i] = stb_sb_last(watch->subdirs);
|
|
stb_sb_pop(watch->subdirs);
|
|
|
|
watch->wds[i] = stb_sb_last(watch->wds);
|
|
stb_sb_pop(watch->wds);
|
|
|
|
if (!skip_lock)
|
|
pthread_mutex_unlock(&_dmon.mutex);
|
|
return true;
|
|
}
|
|
#endif // DMON_OS_LINUX
|
|
#endif // DMON_IMPL
|
|
|
|
#endif // __DMON_EXTRA_H__
|
|
|