mirror of
https://github.com/SELinuxProject/selinux
synced 2024-12-12 17:15:00 +00:00
72ea5dec7c
With kernel 2.6.31, restorecond uses 99% of my CPU. This is because removing and readding the watch on utmp triggers inotify to return an IN_IGNORED event for the old watch descriptor. If the watch gets allocated the same wd when it is readded, then restorecond thinks that utmp has changed, so removes and readds the watch again, potentially looping. With kernel <= 2.6.30, this never happened, because the kernel didn't reuse watch descriptors. So the IN_IGNORED event comes with a wd that is no longer in use, and gets ignored. But kernel 2.6.31 reuses the same watch descriptor. The kernel has been fixed to not reuse watch descriptors. However as some kernels do reuse them, and its possible they may again, this patch fixes that by ignoring inotify events whose only bit set is IN_IGNORED. Signed-off-by: Martin Orr <martin@martinorr.name> Signed-off-by: Manoj Srivastava <srivasta@debian.org> Signed-off-by: Laurent Bigonville <bigon@debian.org> Signed-off-by: Eric Paris <eparis@redhat.com> Acked-by: Dan Walsh <dwalsh@redhat.com>
275 lines
5.7 KiB
C
275 lines
5.7 KiB
C
#define _GNU_SOURCE
|
|
#include <sys/inotify.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <syslog.h>
|
|
#include "../setfiles/restore.h"
|
|
#include <glob.h>
|
|
#include <libgen.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <selinux/selinux.h>
|
|
#include "restorecond.h"
|
|
#include "stringslist.h"
|
|
#include "utmpwatcher.h"
|
|
|
|
/* size of the event structure, not counting name */
|
|
#define EVENT_SIZE (sizeof (struct inotify_event))
|
|
/* reasonable guess as to size of 1024 events */
|
|
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
|
|
|
|
|
|
struct watchList {
|
|
struct watchList *next;
|
|
int wd;
|
|
char *dir;
|
|
struct stringsList *files;
|
|
};
|
|
struct watchList *firstDir = NULL;
|
|
|
|
int watch_list_isempty() {
|
|
return firstDir == NULL;
|
|
}
|
|
|
|
void watch_list_add(int fd, const char *path)
|
|
{
|
|
struct watchList *ptr = NULL;
|
|
size_t i = 0;
|
|
struct watchList *prev = NULL;
|
|
glob_t globbuf;
|
|
char *x = strdup(path);
|
|
if (!x) exitApp("Out of Memory");
|
|
char *file = basename(x);
|
|
char *dir = dirname(x);
|
|
ptr = firstDir;
|
|
|
|
if (exclude(path)) goto end;
|
|
|
|
globbuf.gl_offs = 1;
|
|
if (glob(path,
|
|
GLOB_TILDE | GLOB_PERIOD,
|
|
NULL,
|
|
&globbuf) >= 0) {
|
|
for (i=0; i < globbuf.gl_pathc; i++) {
|
|
int len = strlen(globbuf.gl_pathv[i]) -2;
|
|
if (len > 0 && strcmp(&globbuf.gl_pathv[i][len--], "/.") == 0) continue;
|
|
if (len > 0 && strcmp(&globbuf.gl_pathv[i][len], "/..") == 0) continue;
|
|
if (process_one_realpath(globbuf.gl_pathv[i], 0) > 0)
|
|
process_one_realpath(globbuf.gl_pathv[i], 1);
|
|
}
|
|
globfree(&globbuf);
|
|
}
|
|
|
|
while (ptr != NULL) {
|
|
if (strcmp(dir, ptr->dir) == 0) {
|
|
strings_list_add(&ptr->files, file);
|
|
goto end;
|
|
}
|
|
prev = ptr;
|
|
ptr = ptr->next;
|
|
}
|
|
ptr = calloc(1, sizeof(struct watchList));
|
|
|
|
if (!ptr) exitApp("Out of Memory");
|
|
|
|
ptr->wd = inotify_add_watch(fd, dir, IN_CREATE | IN_MOVED_TO);
|
|
if (ptr->wd == -1) {
|
|
free(ptr);
|
|
if (! run_as_user)
|
|
syslog(LOG_ERR, "Unable to watch (%s) %s\n",
|
|
path, strerror(errno));
|
|
goto end;
|
|
}
|
|
|
|
ptr->dir = strdup(dir);
|
|
if (!ptr->dir)
|
|
exitApp("Out of Memory");
|
|
|
|
strings_list_add(&ptr->files, file);
|
|
if (prev)
|
|
prev->next = ptr;
|
|
else
|
|
firstDir = ptr;
|
|
|
|
if (debug_mode)
|
|
printf("%d: Dir=%s, File=%s\n", ptr->wd, ptr->dir, file);
|
|
|
|
end:
|
|
free(x);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
A file was in a direcroty has been created. This function checks to
|
|
see if it is one that we are watching.
|
|
*/
|
|
|
|
int watch_list_find(int wd, const char *file)
|
|
{
|
|
struct watchList *ptr = NULL;
|
|
ptr = firstDir;
|
|
if (debug_mode)
|
|
printf("%d: File=%s\n", wd, file);
|
|
while (ptr != NULL) {
|
|
if (ptr->wd == wd) {
|
|
int exact=0;
|
|
if (strings_list_find(ptr->files, file, &exact) == 0) {
|
|
char *path = NULL;
|
|
if (asprintf(&path, "%s/%s", ptr->dir, file) <
|
|
0)
|
|
exitApp("Error allocating memory.");
|
|
|
|
process_one_realpath(path, 0);
|
|
free(path);
|
|
return 0;
|
|
}
|
|
if (debug_mode)
|
|
strings_list_print(ptr->files);
|
|
|
|
/* Not found in this directory */
|
|
return -1;
|
|
}
|
|
ptr = ptr->next;
|
|
}
|
|
/* Did not find a directory */
|
|
return -1;
|
|
}
|
|
|
|
void watch_list_free(int fd)
|
|
{
|
|
struct watchList *ptr = NULL;
|
|
struct watchList *prev = NULL;
|
|
ptr = firstDir;
|
|
|
|
while (ptr != NULL) {
|
|
inotify_rm_watch(fd, ptr->wd);
|
|
strings_list_free(ptr->files);
|
|
free(ptr->dir);
|
|
prev = ptr;
|
|
ptr = ptr->next;
|
|
free(prev);
|
|
}
|
|
firstDir = NULL;
|
|
}
|
|
|
|
/*
|
|
Inotify watch loop
|
|
*/
|
|
int watch(int fd, const char *watch_file)
|
|
{
|
|
char buf[BUF_LEN];
|
|
int len, i = 0;
|
|
if (firstDir == NULL) return 0;
|
|
|
|
len = read(fd, buf, BUF_LEN);
|
|
if (len < 0) {
|
|
if (terminate == 0) {
|
|
syslog(LOG_ERR, "Read error (%s)", strerror(errno));
|
|
return 0;
|
|
}
|
|
syslog(LOG_ERR, "terminated");
|
|
return -1;
|
|
} else if (!len)
|
|
/* BUF_LEN too small? */
|
|
return -1;
|
|
while (i < len) {
|
|
struct inotify_event *event;
|
|
event = (struct inotify_event *)&buf[i];
|
|
if (debug_mode)
|
|
printf("wd=%d mask=%u cookie=%u len=%u\n",
|
|
event->wd, event->mask,
|
|
event->cookie, event->len);
|
|
if (event->mask & ~IN_IGNORED) {
|
|
if (event->wd == master_wd)
|
|
read_config(fd, watch_file);
|
|
else {
|
|
switch (utmpwatcher_handle(fd, event->wd)) {
|
|
case -1: /* Message was not for utmpwatcher */
|
|
if (event->len)
|
|
watch_list_find(event->wd, event->name);
|
|
break;
|
|
case 1: /* utmp has changed need to reload */
|
|
read_config(fd, watch_file);
|
|
break;
|
|
|
|
default: /* No users logged in or out */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
i += EVENT_SIZE + event->len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void process_config(int fd, FILE * cfg)
|
|
{
|
|
char *line_buf = NULL;
|
|
size_t len = 0;
|
|
|
|
while (getline(&line_buf, &len, cfg) > 0) {
|
|
char *buffer = line_buf;
|
|
while (isspace(*buffer))
|
|
buffer++;
|
|
if (buffer[0] == '#')
|
|
continue;
|
|
int l = strlen(buffer) - 1;
|
|
if (l <= 0)
|
|
continue;
|
|
buffer[l] = 0;
|
|
if (buffer[0] == '~') {
|
|
if (run_as_user) {
|
|
char *ptr=NULL;
|
|
if (asprintf(&ptr, "%s%s", homedir, &buffer[1]) < 0)
|
|
exitApp("Error allocating memory.");
|
|
|
|
watch_list_add(fd, ptr);
|
|
free(ptr);
|
|
} else {
|
|
utmpwatcher_add(fd, &buffer[1]);
|
|
}
|
|
} else {
|
|
watch_list_add(fd, buffer);
|
|
}
|
|
}
|
|
free(line_buf);
|
|
}
|
|
|
|
/*
|
|
Read config file ignoring Comment lines
|
|
Files specified one per line. Files with "~" will be expanded to the logged in users
|
|
homedirs.
|
|
*/
|
|
|
|
void read_config(int fd, const char *watch_file_path)
|
|
{
|
|
|
|
FILE *cfg = NULL;
|
|
if (debug_mode)
|
|
printf("Read Config\n");
|
|
|
|
watch_list_free(fd);
|
|
|
|
cfg = fopen(watch_file_path, "r");
|
|
if (!cfg){
|
|
perror(watch_file_path);
|
|
exitApp("Error reading config file");
|
|
}
|
|
process_config(fd, cfg);
|
|
fclose(cfg);
|
|
|
|
inotify_rm_watch(fd, master_wd);
|
|
master_wd =
|
|
inotify_add_watch(fd, watch_file_path, IN_MOVED_FROM | IN_MODIFY);
|
|
if (master_wd == -1)
|
|
exitApp("Error watching config file.");
|
|
}
|