Newer
Older
advsyssoft / inotify / exec_on_modify.c
@Motoki Miura Motoki Miura on 30 Nov 2020 6 KB inotify sample
/**
 * Original:
 * https://blpr.net/?p=210
 *
 * 20201130 @miura added #include<unistd.h> to prevent warning of implicit declaration of functions:
 * read / close.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <sys/select.h>
#include <limits.h>
// @miura added
#include <unistd.h>
#include <time.h>
#include <sys/wait.h>

#define WATCH_DIR "."

//イベントサイズは16バイト境界
#define INOTIFY_EVENT_MAX (((sizeof(struct inotify_event)+NAME_MAX+1)+16)&~16)

typedef struct _WD_INFO
{
   struct _WD_INFO* prev;
   struct _WD_INFO* next;
   int wd;
   char* path;
} WD_INFO;

static WD_INFO* topWdInfo = NULL;

static void getWdInfo(int fd, char* dirname)
{
   DIR* dir = NULL;
   struct dirent* entry;
   struct stat st;
   int wd;
   int dirname_len;
   int entname_len;
   char* fullpath = NULL;
   WD_INFO* newWdInfo;

   newWdInfo = (WD_INFO*)malloc(sizeof(WD_INFO));

   if(topWdInfo == NULL){
      // first
      topWdInfo = newWdInfo;
      topWdInfo->prev = topWdInfo;
      topWdInfo->next = topWdInfo;
   }else{
      newWdInfo->prev = topWdInfo->prev;
      topWdInfo->prev->next = newWdInfo;
      topWdInfo->prev = newWdInfo;
      newWdInfo->next = topWdInfo;
   }

   newWdInfo->wd = inotify_add_watch(fd, dirname, IN_ALL_EVENTS);
   newWdInfo->path = strdup(dirname);

   //Search Sub directry
   dir = opendir(dirname);

   dirname_len = strlen(dirname);

   while((entry = readdir(dir)) != NULL){

      if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0){
         continue;
      }

      //エントリのタイプ種別を非標準のd_typeを使わず、statで取得
      entname_len = strlen(entry->d_name);
      fullpath = (char*)malloc(dirname_len + 1 + entname_len + 1);
      strcpy(fullpath, dirname);
      strcat(fullpath, "/");
      strcat(fullpath, entry->d_name);
      stat(fullpath, &st);

      if(S_ISDIR(st.st_mode)){
         //再帰呼び出し
         getWdInfo(fd, fullpath);
      }

      free(fullpath);
   }

   closedir(dir);
}


static char* wd2path(int wd)
{
   WD_INFO* p;

   if(topWdInfo == NULL){
      return NULL;
   }

   p = topWdInfo;
   do{
      if(p->wd == wd){
         return p->path;
      }
      p = p->next;
   }while(p != topWdInfo);

   return NULL;
}

static void closeAllWdInfo(int fd)
{
   WD_INFO* p;

   if(topWdInfo == NULL){
      return;
   }

   p = topWdInfo;
   do{
      WD_INFO* del;
      del = p;
      p = p->next;
      free(del->path);
      inotify_rm_watch(fd, del->wd);
      free(del);
   }while(p != topWdInfo);
   topWdInfo = NULL;
}

static void deleteWdInfo(int wd)
{
   WD_INFO* p;

   if(topWdInfo == NULL){
      return;
   }

   p = topWdInfo;
   do{
      if(p->wd == wd){
         if(p->next == p->prev){
            topWdInfo = NULL;
         }else{
            if(p == topWdInfo){
               topWdInfo = p->next;
            }
            p->next->prev = p->prev;
            p->prev->next = p->next;
         }
         free(p->path);
         free(p);

         return;
      }
      p = p->next;
   }while(p != topWdInfo);

   return;
}

/** @miura added */
void print_datetime()
{
   time_t t = time(NULL);
   struct tm *lcl = localtime(&t);
   printf("%02d/%02d %02d:%02d:%02d ", lcl->tm_mon+1, lcl->tm_mday,
         lcl->tm_hour, lcl->tm_min, lcl->tm_sec);
}

char cmd[255] = {};
char prefix[63] = {};

/** return true when target ends with find_prefix */
int ends_with(char* target, char* find_prefix){
   char *p = target;
   char *q = find_prefix;
   while( *p ) p++;
   while( *q ) q++;
   while( find_prefix <= q){
      if (*p-- != *q--) return 0;
   }
   return 1;
}
void exec_cmd(char* target)
{
   pid_t pid;
//   printf("target: %s\n", target);
   if (strlen(prefix) == 0 || ends_with(target, prefix)){
      if ((pid = fork()) == 0){
         char *argv[] = { cmd, NULL };
         execvp( cmd, argv );
      } else {
         wait(0);
      }
   }
}

int main(int argc, char** argv)
{
   struct timeval waitval;
   int fd;
   int ret;
   fd_set readfds;

   if (argc < 2){
      printf("Usage: exec_on_modify command [prefix]\n");
      printf("  command is executed when a file ends with [prefix] modified.\n");
      printf("  example1:  exec_on_modify make .c \n");
      printf("  example2:  exec_on_modify platex .tex \n");
      exit(0);
   }
//   printf("%d\n", argc);
   if (argc >= 2){
      strcpy(cmd, argv[1]);
   }
   if (argc == 3){
      strcpy(prefix, argv[2]);
   }
   printf("%s\n", cmd);
   printf("%s\n", prefix);

   fd = inotify_init();

   getWdInfo(fd, (char*)WATCH_DIR);

   while(1){
      FD_ZERO(&readfds);
      FD_SET(fd, &readfds);
      ret = select(fd+1, &readfds, NULL, NULL, NULL);
      if(0 < ret){
         if(FD_ISSET(fd, &readfds)){
            char* buf;
            int len;
            struct inotify_event* event;

            buf = (char*)malloc(INOTIFY_EVENT_MAX);

            //INOTIFY_EVENT_MAXを指定し、最低でも一つのイベントは読み込む。
            len = read(fd, buf, INOTIFY_EVENT_MAX);

            event = (struct inotify_event*)buf;

            //複数イベントがあるかもしれない。全部処理するまでループ。
            while(len > 0){
               char* target;
               if(event->len){
                  target = event->name;
               }else{
                  target = wd2path(event->wd);
               }

               if(event->mask & IN_CLOSE_WRITE){
                  if (target[0] !='.'){ 
                     print_datetime();
                     printf("Writtable [%s] was closed.\n", target);
                     exec_cmd(target);
                  }
               }

               if(event->mask & IN_CREATE && event->name[0]!='.' ){
                  print_datetime();
                  printf("[%s] was created in [%s].\n", event->name, wd2path(event->wd));
                  char* dirname;
                  int dirname_len;
                  int eventname_len;
                  char* fullpath;
                  struct stat st;

                  dirname = wd2path(event->wd);
                  eventname_len = strlen(event->name);
                  fullpath = (char*)malloc(dirname_len + 1 + eventname_len + 1);
                  strcpy(fullpath, dirname);
                  strcat(fullpath, "/");
                  strcat(fullpath, event->name);
                  stat(fullpath, &st);

                  if(S_ISDIR(st.st_mode)){
                     //監視対象追加
                     getWdInfo(fd, fullpath);
                  }

               }

               if(event->mask & IN_IGNORED){
                  // printf("[%s] was ignored.\n", target);
                  //監視対象削除
                  deleteWdInfo(event->wd);
               }

               len -= (sizeof(struct inotify_event) + event->len);
               event = (struct inotify_event*)(((char*)event)+sizeof(struct inotify_event) + event->len);
            }
            free(buf);
         }
      }
   }

   closeAllWdInfo(fd);
   close(fd);

   return 0;
}