File monitoring algorithm

Fri, Jan 19 2024 18:05:25 KST

I’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;
}