isync/inotify.c

345 lines
8.3 KiB
C

/***************************************************************************
* Copyright (C) 10/2006 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <sys/ioctl.h>
#include "sys/inotify.h"
#include "logging.h"
#define MAX_WATCHES 8192
#define WATCH_MASK (IN_DONT_FOLLOW | IN_ONLYDIR | IN_MOVE | IN_DELETE | IN_CREATE)
struct watch_entry {
int parent;
char *path;
};
static struct watch_entry *watch_arr;
/*
* adds a watch
* - updates watch_arr info (parent & path)
*
* returns watch discriptor or -1 on failure
*/
static int add_watch(int inotify_fd, char *path, int parent)
{
int wd = inotify_add_watch(inotify_fd, path, WATCH_MASK);
if (wd < 0) {
log_print(LOG_ERROR, "inotify_add_watch('%s')", path);
return -1;
}
if (watch_arr[wd].path != NULL) {
log_print(LOG_DEBUG, "add_watch(): removing old data: '%s' = %d",
watch_arr[wd].path, wd);
watch_arr[wd].parent = -1;
free(watch_arr[wd].path);
}
watch_arr[wd].parent = parent;
watch_arr[wd].path = strdup(path);
log_print(LOG_DEBUG, "add_watch('%s') = %d", path, wd);
return wd;
}
/*
* removes a watch, and all childrens!
* - updates watch_arr info
*
* returns -1 on failure
*/
static int remove_watch_recursive(int inotify_fd, int wd)
{
log_print(LOG_DEBUG, "remove_watch('%s')", watch_arr[wd].path);
int i;
for (i = 0; i < MAX_WATCHES; i++)
if (watch_arr[i].parent == wd)
remove_watch_recursive(inotify_fd, i);
int ret = inotify_rm_watch(inotify_fd, wd);
if (ret < 0)
log_print(LOG_ERROR, "inotify_rm_watch('%s')", watch_arr[wd].path);
watch_arr[wd].parent = -1;
free(watch_arr[wd].path);
watch_arr[wd].path = NULL;
return ret;
}
static int add_watch_recursive_real(int inotify_fd, char *path, int parent)
{
struct stat statbuf;
if (stat(path, &statbuf) == -1) {
log_print(LOG_ERROR, "add_watch_recursive_real(): stat('%s')", path);
return 0;
} else if (!S_ISDIR (statbuf.st_mode)) {
return 0;
}
int wd = add_watch(inotify_fd, path, parent);
if (wd < 0)
return 0;
int watches = 1;
DIR *dp = opendir(path);
if (dp == NULL) {
log_print(LOG_ERROR, "add_watch_recursive_real(): opendir('%s')", path);
} else {
char *end = path + strlen(path);
struct dirent *dentry;
while ((dentry = readdir(dp)) != NULL) {
if (!strcmp(dentry->d_name, "."))
continue;
if (!strcmp(dentry->d_name, ".."))
continue;
*end = '/';
strcpy(end +1, dentry->d_name);
watches += add_watch_recursive_real(inotify_fd, path, wd);
}
closedir(dp);
*end = '\0';
}
return watches;
}
static char * prepare_path(int parent, char *name)
{
char *tmppath = malloc(PATH_MAX);
if (tmppath == NULL) {
log_print(LOG_ERROR, "add_recursive_watch(): out of memory");
return 0;
}
if (parent != -1) {
char *tmp = tmppath + strlen(watch_arr[parent].path);
strcpy(tmppath, watch_arr[parent].path);
*tmp++ = '/';
strcpy(tmp, name);
} else {
strcpy(tmppath, name);
}
return tmppath;
}
static int find_child_by_name(int parent, char *name)
{
char *path = prepare_path(parent, name);
int i;
for (i = 0; i < MAX_WATCHES; i++) {
if (watch_arr[i].parent == parent && !strcmp(watch_arr[i].path, path)) {
free(path);
return i;
}
}
log_print(LOG_ERROR, "find_child_by_name: '%s' not found", path);
free(path);
return -1;
}
int add_watch_recursive(int fd, char *name)
{
char *tmppath = prepare_path(-1, name);
int num = add_watch_recursive_real(fd, tmppath, -1);
free(tmppath);
return num;
}
static char * mask_to_str(int mask)
{
char *retval = malloc(256);
*retval = '\0';
if (mask & IN_ACCESS)
strcat(retval, "IN_ACCESS ");
if (mask & IN_MODIFY)
strcat(retval, "IN_MODIFY ");
if (mask & IN_ATTRIB)
strcat(retval, "IN_ATTRIB ");
if (mask & IN_CLOSE_WRITE)
strcat(retval, "IN_CLOSE_WRITE ");
if (mask & IN_CLOSE_NOWRITE)
strcat(retval, "IN_CLOSE_NOWRITE ");
if (mask & IN_OPEN)
strcat(retval, "IN_OPEN ");
if (mask & IN_MOVED_FROM)
strcat(retval, "IN_MOVED_FROM ");
if (mask & IN_MOVED_TO)
strcat(retval, "IN_MOVED_TO ");
if (mask & IN_CREATE)
strcat(retval, "IN_CREATE ");
if (mask & IN_DELETE)
strcat(retval, "IN_DELETE ");
if (mask & IN_DELETE_SELF)
strcat(retval, "IN_DELETE_SELF ");
if (mask & IN_MOVE_SELF)
strcat(retval, "IN_MOVE_SELF ");
if (mask & IN_UNMOUNT)
strcat(retval, "IN_UNMOUNT ");
if (mask & IN_Q_OVERFLOW)
strcat(retval, "IN_Q_OVERFLOW ");
if (mask & IN_IGNORED)
strcat(retval, "IN_IGNORED ");
if (mask & IN_ISDIR)
strcat(retval, "IN_ISDIR ");
if (mask & IN_ONESHOT)
strcat(retval, "IN_ONESHOT ");
return retval;
}
int watch_callback(int fd, void *privdata)
{
unsigned int size;
int i = ioctl(fd, FIONREAD, &size);
if (i < 0) {
perror ("ioctl()");
return -1;
}
char *buf = malloc(size);
if (buf == NULL) {
perror("malloc()");
return -1;
}
int len, j = 0;
len = read(fd, buf, size);
while (j < len) {
struct inotify_event *event = (struct inotify_event *) &buf[j];
if (event->len)
log_print(LOG_INFO, "wd=%03d mask=%08X cookie=%08X name=%s/%s %s",
event->wd, event->mask, event->cookie, watch_arr[event->wd].path, event->name,
mask_to_str(event->mask));
else
log_print(LOG_INFO, "wd=%03d mask=%08X cookie=%08X (name=%s) %s",
event->wd, event->mask, event->cookie, watch_arr[event->wd].path,
mask_to_str(event->mask));
/* new directory created -> watch it */
if ((event->mask & (IN_CREATE | IN_ISDIR)) == (IN_CREATE | IN_ISDIR)) {
char *tmp = prepare_path(event->wd, event->name);
add_watch_recursive_real(fd, tmp, event->wd);
free(tmp);
}
/* directory removed -> remove watch info (the watch was removed by the kernel) */
if (event->mask & IN_IGNORED) {
if (watch_arr[event->wd].path != NULL) {
log_print(LOG_DEBUG, "kernel removed watch ('%s')", watch_arr[event->wd].path);
watch_arr[event->wd].parent = -1;
free(watch_arr[event->wd].path);
watch_arr[event->wd].path = NULL;
}
}
/* directory moved away -> find child by name-serach, then remove all his children rekursive */
if ((event->mask & (IN_MOVED_FROM | IN_ISDIR)) == (IN_MOVED_FROM | IN_ISDIR)) {
int wd = find_child_by_name(event->wd, event->name);
remove_watch_recursive(fd, wd);
}
/* directory moved to us -> add watches */
if ((event->mask & (IN_MOVED_TO | IN_ISDIR)) == (IN_MOVED_TO | IN_ISDIR)) {
char *tmp = prepare_path(event->wd, event->name);
add_watch_recursive_real(fd, tmp, event->wd);
free(tmp);
}
j += sizeof(struct inotify_event) + event->len;
}
free(buf);
return 0;
}
int inotify_open()
{
int fd = inotify_init();
if (fd < 0)
log_print(LOG_ERROR, "inotify_open()");
if (watch_arr == NULL) {
watch_arr = malloc(MAX_WATCHES * sizeof(struct watch_entry));
if (watch_arr == NULL) {
log_print(LOG_ERROR, "inotify_open(): out of memory");
return 0;
}
int i;
for (i = 0; i < MAX_WATCHES; i++) {
watch_arr[i].parent = -1;
watch_arr[i].path = NULL;
}
}
return fd;
}