310 lines
8.2 KiB
C
310 lines
8.2 KiB
C
/* Демон для отслеживания изменений в файлах в заданной директории
|
|
|
|
Формат конфигурационного файла (по умолчанию /etc/my-daemon/config):
|
|
directory=/home/arity
|
|
interval=20
|
|
|
|
Компиляция и запуск:
|
|
gcc -o daemon daemon.c
|
|
./daemon
|
|
./daemon /home/arity/daemon/config
|
|
|
|
Просмотр логов:
|
|
sudo journalctl -f -t MY-DAEMON
|
|
sudo journalctl -f -t MY-DAEMON --since "now" -p info
|
|
|
|
Узнать PID демона:
|
|
ps -u arity | grep daemon
|
|
|
|
Перечитать конфигурационный файл:
|
|
kill -SIGHUP <pid>
|
|
|
|
Завершить работу демона:
|
|
kill <pid>
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <syslog.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
pid_t pid, sid;
|
|
const char * CONFIG_PATH;
|
|
char * DIR_PATH;
|
|
int INTERVAL;
|
|
time_t LAST_CHECK_TIME;
|
|
int sighup = 0;
|
|
int stop = 0;
|
|
|
|
void update_parameter(char * key, char * value) {
|
|
syslog(LOG_DEBUG, "Found parameter %s = %s", key, value);
|
|
|
|
if (strcmp(key, "directory") == 0) {
|
|
free(DIR_PATH);
|
|
DIR_PATH = strdup(value);
|
|
syslog(LOG_DEBUG, "Updated directory path to %s", DIR_PATH);
|
|
} else if (strcmp(key, "interval") == 0) {
|
|
int new_interval = atoi(value);
|
|
if (new_interval <= 0) {
|
|
syslog(LOG_CRIT, "Invalid interval %s in config file %s",
|
|
value, CONFIG_PATH);
|
|
return;
|
|
}
|
|
INTERVAL = new_interval;
|
|
syslog(LOG_DEBUG, "Updated interval to %d", INTERVAL);
|
|
} else {
|
|
syslog(LOG_CRIT, "Unknown parameter %s in config file %s",
|
|
key, CONFIG_PATH);
|
|
}
|
|
}
|
|
|
|
void read_config() {
|
|
syslog(LOG_INFO, "Read config file %s", CONFIG_PATH);
|
|
|
|
/* Открываем файловый дескриптор */
|
|
int fd = open(CONFIG_PATH, O_RDONLY);
|
|
|
|
if (fd < 0) {
|
|
syslog(LOG_CRIT,
|
|
"Unable to read config file %s",
|
|
CONFIG_PATH);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Парсим конфигурационный файл */
|
|
char c;
|
|
char * key = NULL;
|
|
char * value = NULL;
|
|
size_t key_len = 0;
|
|
size_t val_len = 0;
|
|
int in_value = 0;
|
|
|
|
while (read(fd, & c, 1) == 1) {
|
|
if (c == '\n' || c == '\r') {
|
|
if (key_len > 0 && val_len > 0) {
|
|
key[key_len] = '\0';
|
|
value[val_len] = '\0';
|
|
|
|
update_parameter(key, value);
|
|
}
|
|
|
|
// сбрасываем под следующую строку
|
|
free(key);
|
|
free(value);
|
|
key = value = NULL;
|
|
key_len = val_len = 0;
|
|
in_value = 0;
|
|
} else if (c == '=') {
|
|
in_value = 1;
|
|
} else {
|
|
if (!in_value) {
|
|
key = realloc(key, key_len + 2);
|
|
if (key == NULL) {
|
|
syslog(LOG_CRIT, "Unable to allocate memory");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
key[key_len++] = c;
|
|
} else {
|
|
value = realloc(value, val_len + 2);
|
|
if (value == NULL) {
|
|
syslog(LOG_CRIT, "Unable to allocate memory");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
value[val_len++] = c;
|
|
}
|
|
}
|
|
}
|
|
close(fd);
|
|
|
|
// обработка последней строки без \n
|
|
if (key_len > 0 && val_len > 0) {
|
|
key[key_len] = '\0';
|
|
value[val_len] = '\0';
|
|
|
|
update_parameter(key, value);
|
|
}
|
|
|
|
free(key);
|
|
free(value);
|
|
|
|
syslog(LOG_DEBUG, "Read values from config file successfully");
|
|
}
|
|
|
|
void signal_handler(int sig) {
|
|
switch (sig) {
|
|
case SIGHUP:
|
|
syslog(LOG_DEBUG, "hangup signal");
|
|
sighup = 1;
|
|
break;
|
|
case SIGTERM:
|
|
syslog(LOG_INFO, "Terminate signal catched. Stopping daemon...");
|
|
stop = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void check_file(const char * path) {
|
|
struct stat st;
|
|
if (lstat(path, &st) != 0) {
|
|
syslog(LOG_WARNING, "Cannot stat %s", path);
|
|
return;
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "Found file %s, size=%ld", path, st.st_size);
|
|
|
|
// Обязательно нестрогое сравнение, иначе не заметим изменения,
|
|
// которые произошли в секунду обновления LAST_CHECK_TIME
|
|
if (st.st_mtime >= LAST_CHECK_TIME) {
|
|
if (st.st_mode & S_IFDIR) {
|
|
syslog(LOG_INFO, "Directory %s was modified at %s", path, ctime(&st.st_mtime));
|
|
} else {
|
|
syslog(LOG_INFO, "File %s was modified at %s", path, ctime(&st.st_mtime));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void check_directory(const char * path) {
|
|
syslog(LOG_DEBUG, "Recursively checking directory %s", path);
|
|
|
|
DIR * dir = opendir(path);
|
|
if (dir == NULL) {
|
|
syslog(LOG_CRIT, "Unable to open directory %s", path);
|
|
return;
|
|
}
|
|
|
|
struct dirent * entry;
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
if (strcmp(entry -> d_name, ".") == 0 || strcmp(entry -> d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
size_t path_len = strlen(path);
|
|
size_t name_len = strlen(entry -> d_name);
|
|
size_t full_path_len = path_len + name_len + 2;
|
|
char * full_path = malloc(full_path_len);
|
|
if (full_path == NULL) {
|
|
syslog(LOG_CRIT, "Unable to allocate memory");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
memcpy(full_path, path, path_len);
|
|
full_path[path_len] = '/';
|
|
memcpy(full_path + path_len + 1, entry -> d_name, name_len);
|
|
full_path[full_path_len - 1] = '\0';
|
|
|
|
struct stat st;
|
|
if (lstat(full_path, & st) != 0) {
|
|
syslog(LOG_WARNING, "Cannot stat %s", full_path);
|
|
} else {
|
|
// Рекурсивно проверяем подиректории
|
|
check_file(full_path);
|
|
if (st.st_mode & S_IFDIR) {
|
|
check_directory(full_path);
|
|
}
|
|
}
|
|
|
|
free(full_path);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
void do_daemons_job() {
|
|
// Сохраняем время до начала проверки, иначе в теории можем незаметить изменения,
|
|
// которые произошли в течение проверки.
|
|
int new_check_time = time(NULL);
|
|
|
|
check_directory(DIR_PATH);
|
|
|
|
LAST_CHECK_TIME = new_check_time;
|
|
}
|
|
|
|
void daemonize() {
|
|
openlog("MY-DAEMON", 0, LOG_DAEMON);
|
|
|
|
/* Закрываем стандартные файловые дескприторы */
|
|
close(STDIN_FILENO);
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
|
|
pid = fork();
|
|
|
|
if (pid < 0) {
|
|
syslog(LOG_CRIT, "Unable to fork process!");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Если fork получился, то родительский процесс можно завершить */
|
|
if (pid > 0) {
|
|
syslog(LOG_DEBUG, "Killed parent process");
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
umask(0);
|
|
|
|
/* Sid для дочернего процесса */
|
|
sid = setsid();
|
|
|
|
if (sid < 0) {
|
|
syslog(LOG_CRIT, "Unable to set session id");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Изменяем текущий рабочий каталог */
|
|
if ((chdir("/")) < 0) {
|
|
syslog(LOG_CRIT, "Unable to change working directory");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char * argv[]) {
|
|
syslog(LOG_DEBUG, " --- STARTING DAEMON --- ");
|
|
daemonize();
|
|
syslog(LOG_DEBUG, "Daemon started successfully");
|
|
signal(SIGHUP, signal_handler);
|
|
signal(SIGTERM, signal_handler);
|
|
|
|
DIR_PATH = NULL;
|
|
INTERVAL = 0;
|
|
|
|
if (argc > 1) {
|
|
CONFIG_PATH = argv[1];
|
|
} else {
|
|
CONFIG_PATH = "/etc/my-daemon/config";
|
|
}
|
|
|
|
read_config();
|
|
|
|
if (DIR_PATH == NULL || INTERVAL == 0) {
|
|
syslog(LOG_CRIT, "Directory path or interval is not set");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
LAST_CHECK_TIME = time(NULL);
|
|
|
|
while (!stop) {
|
|
if (sighup) {
|
|
read_config();
|
|
sighup = 0;
|
|
}
|
|
|
|
do_daemons_job();
|
|
sleep(INTERVAL);
|
|
}
|
|
|
|
if (DIR_PATH) { free(DIR_PATH); DIR_PATH = NULL; }
|
|
|
|
syslog(LOG_INFO, "Daemon stopped");
|
|
closelog();
|
|
|
|
return 0;
|
|
} |