/* Демон для отслеживания изменений в файлах в заданной директории Формат конфигурационного файла (по умолчанию /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 Завершить работу демона: kill */ #include #include #include #include #include #include #include #include #include #include #include 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); syslog(LOG_DEBUG, " --- STARTING 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[]) { 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; }