Lets keep our version of opftpd in the CVS
[oftpd.git] / src / watchdog.c
1 /*
2  * $Id$
3  */
4
5 #include <config.h>
6 #include <unistd.h>
7 #include "daemon_assert.h"
8 #include "watchdog.h"
9
10 static int invariant(watchdog_t *w);
11 static void insert(watchdog_t *w, watched_t *watched);
12 static void delete(watchdog_t *w, watched_t *watched);
13 static void *watcher(void *void_w);
14
15 int watchdog_init(watchdog_t *w, int inactivity_timeout, error_t *err)
16 {
17     pthread_t thread_id;
18     int error_code;
19
20     daemon_assert(w != NULL);
21     daemon_assert(inactivity_timeout > 0);
22     daemon_assert(err != NULL);
23
24     pthread_mutex_init(&w->mutex, NULL);
25     w->inactivity_timeout = inactivity_timeout;
26     w->oldest = NULL;
27     w->newest = NULL;
28
29     error_code = pthread_create(&thread_id, NULL, watcher, w);
30     if (error_code != 0) {
31         error_init(err, error_code, "error %d from pthread_create()", 
32           error_code);
33         return 0;
34     }
35     pthread_detach(thread_id);
36
37     daemon_assert(invariant(w));
38
39     return 1;
40 }
41
42 void watchdog_add_watched(watchdog_t *w, watched_t *watched)
43 {
44     daemon_assert(invariant(w));
45
46     pthread_mutex_lock(&w->mutex);
47
48     watched->watched_thread = pthread_self();
49     watched->watchdog = w;
50     insert(w, watched);
51
52     pthread_mutex_unlock(&w->mutex);
53
54     daemon_assert(invariant(w));
55 }
56
57 void watchdog_defer_watched(watched_t *watched)
58 {
59     watchdog_t *w;
60
61     daemon_assert(invariant(watched->watchdog));
62
63     w = watched->watchdog;
64     pthread_mutex_lock(&w->mutex);
65
66     delete(w, watched);
67     insert(w, watched);
68
69     pthread_mutex_unlock(&w->mutex);
70     daemon_assert(invariant(w));
71 }
72
73 void watchdog_remove_watched(watched_t *watched)
74 {
75     watchdog_t *w;
76
77     daemon_assert(invariant(watched->watchdog));
78
79     w = watched->watchdog;
80     pthread_mutex_lock(&w->mutex);
81
82     delete(w, watched);
83
84     pthread_mutex_unlock(&w->mutex);
85     daemon_assert(invariant(w));
86 }
87
88 static void insert(watchdog_t *w, watched_t *watched)
89 {
90    /*********************************************************************
91     Set alarm to current time + timeout duration.  Note that this is not 
92     strictly legal, since time_t is an abstract data type.
93     *********************************************************************/
94     watched->alarm_time = time(NULL) + w->inactivity_timeout;
95
96    /*********************************************************************
97     If the system clock got set backwards, we really should search for 
98     the correct location, instead of just inserting at the end.  However, 
99     this happens very rarely (ntp and other synchronization protocols 
100     speed up or slow down the clock to adjust the time), so we'll just 
101     set our alarm to the time of the newest alarm - giving any watched
102     processes added some extra time.
103     *********************************************************************/
104     if (w->newest != NULL) {
105         if (w->newest->alarm_time > watched->alarm_time) {
106             watched->alarm_time = w->newest->alarm_time;
107         }
108     }
109
110     /* set our pointers */
111     watched->older = w->newest;
112     watched->newer = NULL;
113
114     /* add to list */
115     if (w->oldest == NULL) {
116         w->oldest = watched;
117     } else {
118         w->newest->newer = watched;
119     }
120     w->newest = watched;
121     watched->in_list = 1;
122 }
123
124 static void delete(watchdog_t *w, watched_t *watched)
125 {
126     if (!watched->in_list) {
127         return;
128     }
129
130     if (watched->newer == NULL) {
131         daemon_assert(w->newest == watched);
132         w->newest = w->newest->older;
133         if (w->newest != NULL) {
134             w->newest->newer = NULL;
135         }
136     } else {
137         daemon_assert(w->newest != watched);
138         watched->newer->older = watched->older;
139     }
140
141     if (watched->older == NULL) {
142         daemon_assert(w->oldest == watched);
143         w->oldest = w->oldest->newer;
144         if (w->oldest != NULL) {
145             w->oldest->older = NULL;
146         }
147     } else {
148         daemon_assert(w->oldest != watched);
149         watched->older->newer = watched->newer;
150     }
151
152     watched->older = NULL;
153     watched->newer = NULL;
154     watched->in_list = 0;
155 }
156
157 static void *watcher(void *void_w)
158 {
159     watchdog_t *w;
160     struct timeval tv;
161     time_t now;
162     watched_t *watched;
163
164     w = (watchdog_t *)void_w;
165     for (;;) {
166         tv.tv_sec = 1;
167         tv.tv_usec = 0;
168         select(0, NULL, NULL, NULL, &tv);
169
170         time(&now);
171
172         pthread_mutex_lock(&w->mutex);
173         while ((w->oldest != NULL) && 
174             (difftime(now, w->oldest->alarm_time) > 0))
175         {
176             watched = w->oldest;
177
178             /*******************************************************
179              This might seem like a memory leak, but in oftpd the 
180              watched_t structure is held in the thread itself, so 
181              canceling the thread effectively frees the memory.  I'm
182              not sure whether this is elegant or a hack.  :)
183              *******************************************************/
184             delete(w, watched);
185
186             pthread_cancel(watched->watched_thread);
187         }
188         pthread_mutex_unlock(&w->mutex);
189     }
190 }
191
192 #ifndef NDEBUG
193 static int invariant(watchdog_t *w)
194 {
195     int ret_val;
196
197     watched_t *ptr;
198     int old_to_new_count;
199     int new_to_old_count;
200
201
202     if (w == NULL) {
203         return 0;
204     }
205
206     ret_val = 0;
207     pthread_mutex_lock(&w->mutex);
208
209     if (w->inactivity_timeout <= 0) {
210         goto exit_invariant;
211     }
212
213     /* either oldest and newest are both NULL, or neither is */
214     if (w->oldest != NULL) {
215         if (w->newest == NULL) {
216             goto exit_invariant;
217         }
218
219         /* check list from oldest to newest */
220         old_to_new_count = 0;
221         ptr = w->oldest;
222         while (ptr != NULL) {
223             old_to_new_count++;
224             if (ptr->older != NULL) {
225                 if (ptr->alarm_time < ptr->older->alarm_time) {
226                     goto exit_invariant;
227                 }
228             }
229             if (ptr->newer != NULL) {
230                 if (ptr->alarm_time > ptr->newer->alarm_time) {
231                     goto exit_invariant;
232                 }
233             }
234             ptr = ptr->newer;
235         }
236
237         /* check list from newest to oldest */
238         new_to_old_count = 0;
239         ptr = w->newest;
240         while (ptr != NULL) {
241             new_to_old_count++;
242             if (ptr->older != NULL) {
243                 if (ptr->alarm_time < ptr->older->alarm_time) {
244                     goto exit_invariant;
245                 }
246             }
247             if (ptr->newer != NULL) {
248                 if (ptr->alarm_time > ptr->newer->alarm_time) {
249                     goto exit_invariant;
250                 }
251             }
252             ptr = ptr->older;
253         }
254
255         /* verify forward and backward lists at least have the same count */
256         if (old_to_new_count != new_to_old_count) {
257             goto exit_invariant;
258         }
259
260     } else {
261         if (w->newest != NULL) {
262             goto exit_invariant;
263         }
264     }
265
266     /* at this point, we're probably okay */
267     ret_val = 1;
268
269 exit_invariant:
270     pthread_mutex_unlock(&w->mutex);
271     return ret_val;
272 }
273 #endif /* NDEBUG */
274