1 /*
2 * Copyright (c) 2017 Balabit
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation, or (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 *
17 * As an additional exemption you are allowed to compile & link against the
18 * OpenSSL libraries as published by the OpenSSL project. See the file
19 * COPYING for details.
20 *
21 */
22
23 #include "directory-monitor.h"
24 #include "timeutils/misc.h"
25
26 #include <stdio.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <messages.h>
32 #include <iv.h>
33
34 gchar *
build_filename(const gchar * basedir,const gchar * path)35 build_filename(const gchar *basedir, const gchar *path)
36 {
37 gchar *result;
38
39 if (!path)
40 return NULL;
41
42 if (basedir)
43 {
44 result = g_build_path(G_DIR_SEPARATOR_S, basedir, path, NULL);
45 }
46 else
47 {
48 result = g_strdup(path);
49 }
50
51 return result;
52 }
53
54 #define PATH_MAX_GUESS 1024
55
56 static inline long
get_path_max(void)57 get_path_max(void)
58 {
59 static long path_max = 0;
60 if (path_max == 0)
61 {
62 #ifdef PATH_MAX
63 path_max = PATH_MAX;
64 #else
65 /* This code based on example from the Advanced Programming in the UNIX environment
66 * on how to determine the max path length
67 */
68 static long posix_version = 0;
69 static long xsi_version = 0;
70 if (posix_version == 0)
71 posix_version = sysconf(_SC_VERSION);
72
73 if (xsi_version == 0)
74 xsi_version = sysconf(_SC_XOPEN_VERSION);
75
76 if ((path_max = pathconf("/", _PC_PATH_MAX)) < 0)
77 path_max = PATH_MAX_GUESS;
78 else
79 path_max++; /* add one since it's relative to root */
80
81 /*
82 * Before POSIX.1-2001, we aren't guaranteed that PATH_MAX includes
83 * the terminating null byte. Same goes for XPG3.
84 */
85 if ((posix_version < 200112L) && (xsi_version < 4))
86 path_max++;
87
88 #endif
89 }
90 return path_max;
91 }
92
93 /*
94 Resolve . and ..
95 Resolve symlinks
96 Resolve tricki symlinks like a -> ../a/../a/./b
97 */
98 gchar *
resolve_to_absolute_path(const gchar * path,const gchar * basedir)99 resolve_to_absolute_path(const gchar *path, const gchar *basedir)
100 {
101 long path_max = get_path_max();
102 gchar *res;
103 gchar *w_name;
104
105 w_name = build_filename(basedir, path);
106 res = (char *)g_malloc(path_max);
107
108 if (!realpath(w_name, res))
109 {
110 g_free(res);
111 if (errno == ENOENT)
112 {
113 res = g_strdup(path);
114 }
115 else
116 {
117 msg_error("Can't resolve to absolute path",
118 evt_tag_str("path", path),
119 evt_tag_error("error"));
120 res = NULL;
121 }
122 }
123 g_free(w_name);
124 return res;
125 }
126
127 static gchar *
_get_real_path(DirectoryMonitor * self)128 _get_real_path(DirectoryMonitor *self)
129 {
130 gchar *dir_real_path = NULL;
131 if (!g_path_is_absolute(self->dir))
132 {
133 gchar *wd = g_get_current_dir();
134 dir_real_path = resolve_to_absolute_path(self->dir, wd);
135 g_free(wd);
136 }
137 else
138 {
139 dir_real_path = resolve_to_absolute_path(self->dir, NULL);
140 }
141 return dir_real_path;
142 }
143
144 void
directory_monitor_stop(DirectoryMonitor * self)145 directory_monitor_stop(DirectoryMonitor *self)
146 {
147 if (iv_timer_registered(&self->check_timer))
148 {
149 iv_timer_unregister(&self->check_timer);
150 }
151 if (iv_task_registered(&self->scheduled_destructor))
152 {
153 iv_task_unregister(&self->scheduled_destructor);
154 }
155 if (self->stop_watches && self->watches_running)
156 {
157 self->stop_watches(self);
158 }
159 self->watches_running = FALSE;
160 }
161
162 static void
_collect_all_files(DirectoryMonitor * self,GDir * directory)163 _collect_all_files(DirectoryMonitor *self, GDir *directory)
164 {
165 const gchar *filename = g_dir_read_name(directory);
166 while (filename)
167 {
168 DirectoryMonitorEvent event =
169 { .name = filename };
170 gchar *filename_real_path = resolve_to_absolute_path(filename, self->real_path);
171 event.full_path = build_filename(self->real_path, filename);
172 event.event_type = g_file_test(filename_real_path, G_FILE_TEST_IS_DIR) ? DIRECTORY_CREATED : FILE_CREATED;
173 self->callback(&event, self->callback_data);
174 g_free(filename_real_path);
175 g_free(event.full_path);
176 filename = g_dir_read_name(directory);
177 }
178 }
179
180 static void
_arm_recheck_timer(DirectoryMonitor * self)181 _arm_recheck_timer(DirectoryMonitor *self)
182 {
183 iv_validate_now();
184 self->check_timer.cookie = self;
185 self->check_timer.handler = (GDestroyNotify) directory_monitor_start;
186 self->check_timer.expires = iv_now;
187 timespec_add_msec(&self->check_timer.expires, self->recheck_time);
188 iv_timer_register(&self->check_timer);
189 }
190
191 static void
_set_real_path(DirectoryMonitor * self)192 _set_real_path(DirectoryMonitor *self)
193 {
194 if (self->real_path)
195 g_free(self->real_path);
196 self->real_path = _get_real_path(self);
197 }
198
199 void
directory_monitor_start(DirectoryMonitor * self)200 directory_monitor_start(DirectoryMonitor *self)
201 {
202 GDir *directory = NULL;
203 GError *error = NULL;
204 if (self->watches_running)
205 {
206 return;
207 }
208 _set_real_path(self);
209 directory = g_dir_open(self->real_path, 0, &error);
210 if (!directory)
211 {
212 msg_error("Can not open directory",
213 evt_tag_str("base_dir", self->real_path),
214 evt_tag_str("error", error->message));
215 _arm_recheck_timer(self);
216 g_error_free(error);
217 return;
218 }
219 _collect_all_files(self, directory);
220 g_dir_close(directory);
221 if (self->start_watches)
222 {
223 self->start_watches(self);
224 }
225 self->watches_running = TRUE;
226 return;
227 }
228
229 void
directory_monitor_set_callback(DirectoryMonitor * self,DirectoryMonitorEventCallback callback,gpointer user_data)230 directory_monitor_set_callback(DirectoryMonitor *self, DirectoryMonitorEventCallback callback, gpointer user_data)
231 {
232 self->callback = callback;
233 self->callback_data = user_data;
234 }
235
236 void
directory_monitor_schedule_destroy(DirectoryMonitor * self)237 directory_monitor_schedule_destroy(DirectoryMonitor *self)
238 {
239 if (!iv_task_registered(&self->scheduled_destructor))
240 {
241 iv_task_register(&self->scheduled_destructor);
242 }
243 }
244
245 void
directory_monitor_stop_and_destroy(DirectoryMonitor * self)246 directory_monitor_stop_and_destroy(DirectoryMonitor *self)
247 {
248 msg_debug("Stopping directory monitor", evt_tag_str("dir", self->dir));
249 directory_monitor_stop(self);
250 directory_monitor_free(self);
251 }
252
253 void
directory_monitor_init_instance(DirectoryMonitor * self,const gchar * dir,guint recheck_time)254 directory_monitor_init_instance(DirectoryMonitor *self, const gchar *dir, guint recheck_time)
255 {
256 self->dir = g_strdup(dir);
257 self->recheck_time = recheck_time;
258 IV_TIMER_INIT(&self->check_timer);
259 IV_TASK_INIT(&self->scheduled_destructor);
260 self->scheduled_destructor.cookie = self;
261 self->scheduled_destructor.handler = (GDestroyNotify)directory_monitor_stop_and_destroy;
262 }
263
264 DirectoryMonitor *
directory_monitor_new(const gchar * dir,guint recheck_time)265 directory_monitor_new(const gchar *dir, guint recheck_time)
266 {
267 DirectoryMonitor *self = g_new0(DirectoryMonitor, 1);
268 directory_monitor_init_instance(self, dir, recheck_time);
269 return self;
270 }
271
272 void
directory_monitor_free(DirectoryMonitor * self)273 directory_monitor_free(DirectoryMonitor *self)
274 {
275 if (self)
276 {
277 msg_debug("Free directory monitor",
278 evt_tag_str("dir", self->dir));
279 if (self->free_fn)
280 {
281 self->free_fn(self);
282 }
283 g_free(self->real_path);
284 g_free(self->dir);
285 g_free(self);
286 }
287 }
288