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