File monitoring algorithm
Fri, Jan 19 2024 18:05:25 KSTI’m currently writing file monitoring source code.
This is the source code implemented in C.
I plan to integrate this code into Cloop
.
In the future, I plan to apply it to the client side of Nimf and operate it in both a communication-based singleton instance method and a non-communication-based multi-instance method.
// To use nullptr , use the -std=c23 option.
// cc -std=c23 monitor.c -o monitor
// This source code is an example file monitoring source code.
// This source code may contain errors or bugs.
// I plan to further refine this source code in the future and
// integrate it into nimf and clair.
#include <stdio.h>
#include <string.h>
#include <sys/event.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/stat.h>
#include "c-str.h"
#include "c-array.h"
#include "c-mem.h"
#include <limits.h>
#define C_MON_EVS_MIN_CAPA 16
/*
This function creates a virtual absolute path.
One thing to consider is that the path does not exceed PATH_MAX.
If you input a relative path, it will be combined with the result of
the getcwd () function.
* .. is resolved.
* Duplicate slashes are converted into one.
* The trailing slash is removed.
To use nullptr , use the -std=c23 option.
cc -std=c23 vpath.c -o vpath
*/
char* vpath (const char* path)
{
if (path == nullptr || path[0] == '\0')
return nullptr;
CArray* array = c_array_new (nullptr, false);
if (path[0] != '/')
{
char* cwd = getcwd (nullptr, 0);
char** strv = c_str_split (cwd, '/');
for (int i = 0; strv[i]; i++)
c_array_add (array, strv[i]);
free (strv);
free (cwd);
}
char** strv = c_str_split (path, '/');
for (int i = 0; strv[i]; i++)
{
if (strv[i][0] == '\0')
{
if (array->len == 0)
c_array_add (array, strv[i]);
else
free (strv[i]);
}
else if (c_str_equal (strv[i], "."))
{
free (strv[i]);
}
else if (c_str_equal (strv[i], ".."))
{
free (strv[i]);
if (array->len > 1)
{
free (array->data[array->len - 1]);
c_array_remove_index (array, array->len - 1);
}
}
else
{
c_array_add (array, strv[i]);
}
}
c_array_add (array, nullptr);
free (strv);
strv = (char**) c_array_free (array);
char* path2 = c_strv_join ((const char**) strv, "/");
c_strv_free (strv);
if (strlen (path2) > PATH_MAX)
{
free (path2);
return nullptr;
}
return path2;
}
char* flag_to_str (u_int flags)
{
static char str[256];
char* p = str;
if (flags & NOTE_ATTRIB)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_ATTRIB"); }
if (flags & NOTE_CLOSE)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_CLOSE"); }
if (flags & NOTE_CLOSE_WRITE)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_CLOSE_WRITE"); }
if (flags & NOTE_DELETE)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_DELETE"); }
if (flags & NOTE_EXTEND)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_EXTEND"); }
if (flags & NOTE_LINK)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_LINK"); }
if (flags & NOTE_OPEN)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_OPEN"); }
if (flags & NOTE_READ)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_READ"); }
if (flags & NOTE_RENAME)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_RENAME"); }
if (flags & NOTE_REVOKE)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_REVOKE"); }
if (flags & NOTE_WRITE)
{ if (p != str) p = stpcpy (p, " | "); p = stpcpy (p, "NOTE_WRITE"); }
return str;
}
typedef struct _CMonitor CMonitor;
struct _CMonitor {
int kq;
struct kevent* evs;
int evs_capa;
char* path;
CArray* params;
bool exist;
};
typedef struct {
int fd;
char* path;
} Param;
void param_free (Param* param)
{
printf ("param_free: %d, %s\n", param->fd, param->path);
close (param->fd);
free (param->path);
free (param);
}
CMonitor* c_monitor_new ()
{
int kq;
kq = kqueue ();
if (kq < 0)
{
printf ("kqueue() failed: %s.\n", strerror (errno));
return nullptr;
}
CMonitor* mon = c_malloc (sizeof (CMonitor));
mon->evs_capa = C_MON_EVS_MIN_CAPA;
mon->evs = c_malloc (mon->evs_capa * sizeof (struct kevent));
mon->kq = kq;
mon->params = c_array_new ((CFreeFunc) param_free, true);
return mon;
}
void c_monitor_free (CMonitor* mon)
{
close (mon->kq);
c_array_free (mon->params);
free (mon->evs);
free (mon->path);
free (mon);
}
void c_monitor_resize_capa (CMonitor* mon, size_t n_evs)
{
size_t old_capa = mon->evs_capa;
while (n_evs > mon->evs_capa)
mon->evs_capa *= 2;
while (n_evs + C_MON_EVS_MIN_CAPA <= mon->evs_capa / 2)
mon->evs_capa = mon->evs_capa / 2;
if (mon->evs_capa != old_capa)
mon->evs = c_realloc (mon->evs, mon->evs_capa * sizeof (struct kevent));
}
bool cb_param_str_equal (Param* param, const char* str)
{
return c_str_equal (param->path, str);
}
bool cb_param_int_equal (Param* param, const int* i)
{
return param->fd == C_VOIDP_TO_INT (i);
}
static bool c_monitor_add_watch (CMonitor* mon, const char* path)
{
if (!mon->path)
mon->path = vpath (path);
bool success = true;
u_int flags = NOTE_ATTRIB | NOTE_DELETE | NOTE_EXTEND | NOTE_LINK |
NOTE_RENAME | NOTE_REVOKE | NOTE_WRITE;
char* p = mon->path;
int n_evs = 0;
while (true)
{
p = strchr (p, '/');
if (p)
{
char* tmp;
if (p - mon->path == 0)
tmp = c_strndup (mon->path, 1);
else
tmp = c_strndup (mon->path, p - mon->path);
if (!c_array_find (mon->params, tmp, (CEqualFunc) cb_param_str_equal, nullptr))
{
int fd = open (tmp, O_RDONLY);
if (fd < 0)
{
printf ("open %s failed: %s\n", tmp, strerror (errno));
break;
}
Param* param = c_malloc (sizeof (Param));
param->fd = fd;
param->path = tmp;
c_array_add (mon->params, param);
c_monitor_resize_capa (mon, n_evs + 1);
EV_SET (&mon->evs[n_evs], fd, EVFILT_VNODE,
EV_ADD | EV_CLEAR, flags, 0, tmp);
printf ("fd %d opened %s\n", fd, tmp);
n_evs++;
}
else
{
printf ("PASS: %s\n", tmp);
free (tmp);
}
if (*p == 0)
break;
p++;
}
else
{
char* tmp = c_strdup (mon->path);
if (!c_array_find (mon->params, tmp, (CEqualFunc) cb_param_str_equal, nullptr))
{
int fd = open (mon->path, O_RDONLY);
if (fd < 0)
{
printf ("open %s failed: %s\n", mon->path, strerror (errno));
break;
}
Param* param = c_malloc (sizeof (Param));
param->fd = fd;
param->path = tmp;
c_array_add (mon->params, param);
c_monitor_resize_capa (mon, n_evs + 1);
EV_SET (&mon->evs[n_evs], fd, EVFILT_VNODE,
EV_ADD | EV_CLEAR, flags, 0, tmp);
printf ("fd %d opened %s\n", fd, tmp);
mon->exist = true;
n_evs++;
}
else
{
free (tmp);
}
break;
}
}
int ret = kevent (mon->kq, mon->evs, n_evs, nullptr, 0, nullptr);
if (ret == -1)
{
printf ("1 kevent register failed: %s\n", strerror (errno));
c_array_clear (mon->params);
close (mon->kq);
mon->kq = -1;
success = false;
}
return success;
}
bool c_monitor_delete (CMonitor* mon, int fd)
{
struct kevent ev;
EV_SET (&ev, fd, EVFILT_VNODE, EV_DELETE, 0, 0, 0);
int status = kevent (mon->kq, &ev, 1, NULL, 0, NULL);
if (status == -1)
{
printf ("1 kevent unregister failed: %s, fd: %d\n", strerror (errno), fd);
return false;
}
int i;
if (c_array_find (mon->params, C_INT_TO_VOIDP (fd),
(CEqualFunc) cb_param_int_equal, &i))
{
c_array_remove_index (mon->params, i);
return true;
}
return false;
}
int cb_compare (const void *a, const void *b)
{
struct kevent* ev_a = (struct kevent*) a;
struct kevent* ev_b = (struct kevent*) b;
return strlen (ev_b->udata) - strlen (ev_a->udata);
}
int main (int argc, char** argv)
{
int status = 0;
if (argc != 2)
{
printf ("Usage: monitor <file_path>\n");
return 1;
}
CMonitor* mon;
mon = c_monitor_new ();
status = !c_monitor_add_watch (mon, argv[1]);
if (status)
goto finally;
while (true)
{
struct kevent tev[mon->params->len];
int n_events = kevent (mon->kq, nullptr, 0, tev, mon->params->len, nullptr);
// int n_events = kevent (mon->kq, nullptr, 0, &mon->ev, 1, nullptr);
if (n_events < 0)
{
printf ("kevent failed: %s\n", strerror (errno));
break;
}
qsort (&tev, n_events, sizeof (struct kevent), cb_compare);
printf ("n_events: %d\n", n_events);
for (int i = 0; i < n_events; i++)
{
printf ("ident: %" PRIuPTR ", filter: %" PRId16 ", flags: %d,\nfflags: %s, data: %s\n",
tev[i].ident, // uintptr_t
tev[i].filter, // short
tev[i].flags, // u_short
flag_to_str (tev[i].fflags), // u_int
(char*) tev[i].udata); // int64_t
if (tev[i].fflags & NOTE_DELETE)
{
printf ("%s is DELETED\n", (char*) tev[i].udata);
if (c_str_equal (tev[i].udata, mon->path))
{
mon->exist = false;
}
int index;
if (c_array_find (mon->params, C_UINT_TO_VOIDP (tev[i].ident),
(CEqualFunc) cb_param_int_equal, &index))
{
c_array_remove_index (mon->params, index);
}
}
else if (tev[i].fflags & NOTE_WRITE)
{
if (c_str_equal (tev[i].udata, mon->path))
{
printf ("MODIFIED: %s\n", (char*) tev[i].udata);
}
else
{
struct stat sb;
if (!lstat (mon->path, &sb))
{
if (!mon->exist)
{
printf ("CREATED: %s\n", mon->path);
mon->exist = true;
}
}
else
{
if (mon->exist)
{
printf ("2 DELETED: %s\n", mon->path);
mon->exist = false;
int index;
if (c_array_find (mon->params, mon->path,
(CEqualFunc) cb_param_str_equal, &index))
{
//c_array_remove_index (mon->params, index);
}
}
}
c_monitor_add_watch (mon, mon->path);
}
}
else if (tev[i].fflags & NOTE_RENAME)
{
int index = 0;
while (index < mon->params->len)
{
Param* param = mon->params->data[index];
if (c_str_starts_with (param->path, (char*) tev[i].udata))
c_array_remove_index (mon->params, index);
else
index++;
}
puts ("---Remaining--------------------");
for (int j = 0; j < mon->params->len; j++)
{
Param* param = mon->params->data[j];
puts (param->path);
}
puts ("-----------------------");
}
}
}
finally:
c_monitor_free (mon);
return status;
}