1 /* vi:ai:et:ts=8 sw=2
2 */
3 /*
4 * wzdftpd - a modular and cool ftp server
5 * Copyright (C) 2002-2004 Pierre Chifflier
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 *
21 * As a special exemption, Pierre Chifflier
22 * and other respective copyright holders give permission to link this program
23 * with OpenSSL, and distribute the resulting executable, without including
24 * the source code for OpenSSL in the source distribution.
25 */
26
27 #include "wzd_all.h"
28
29 #ifndef WZD_USE_PCH
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <time.h>
33 #include <sys/types.h>
34 #include <string.h>
35
36 #include "wzd_structs.h"
37 #include "wzd_libmain.h"
38 #include "wzd_log.h"
39 #include "wzd_mod.h"
40 #include "wzd_threads.h"
41 #include "wzd_crontab.h"
42
43 #include "wzd_debug.h"
44 #endif /* WZD_USE_PCH */
45
46 static int _crontab_running = 0;
47 static wzd_thread_t _crontab_thread;
48
49 static void * _crontab_thread_fund(void *);
50
_crontab_insert_sorted(wzd_cronjob_t * job,wzd_cronjob_t ** crontab)51 static int _crontab_insert_sorted(wzd_cronjob_t * job, wzd_cronjob_t ** crontab)
52 {
53 wzd_cronjob_t * current;
54
55 WZD_ASSERT( job != NULL );
56 WZD_ASSERT( crontab != NULL );
57
58 current = *crontab;
59
60 /* case: list empty, or head insertion */
61 if (!current || job->next_run < current->next_run) {
62 *crontab = job;
63 job->next_cronjob = current;
64 #ifdef WZD_DBG_CRONTAB
65 out_err(LEVEL_HIGH,"cronjob: head insertion for %s\n",job->hook->external_command);
66 #endif
67 return 0;
68 }
69
70 while (current->next_cronjob && job->next_run > current->next_cronjob->next_run) {
71 current = current->next_cronjob;
72 }
73
74 #ifdef WZD_DBG_CRONTAB
75 out_err(LEVEL_HIGH,"cronjob: inserting %s after %s\n",job->hook->external_command, current->hook->external_command);
76 #endif
77 job->next_cronjob = current->next_cronjob;
78 current->next_cronjob = job;
79
80 return 0;
81 }
82
83 /** If \a minutes is the special string "ONCE", then return 0, meaning that the cron job
84 * should not be re-scheduled
85 */
cronjob_find_next_exec_date(time_t start,const char * minutes,const char * hours,const char * day_of_month,const char * month,const char * day_of_week)86 static time_t cronjob_find_next_exec_date(time_t start,
87 const char * minutes, const char * hours, const char * day_of_month,
88 const char * month, const char * day_of_week)
89 {
90 time_t t = start;
91 struct tm * ltm;
92 int num_minutes, num_hours, num_day_of_month, num_month;
93
94 if (strcmp(minutes,"ONCE")==0) return 0;
95
96 if (minutes[0]!='*')
97 num_minutes=strtol(minutes,NULL,10);
98 else
99 num_minutes = -1;
100 if (hours[0]!='*')
101 num_hours=strtol(hours,NULL,10);
102 else
103 num_hours = -1;
104 if (day_of_month[0]!='*')
105 num_day_of_month=strtol(day_of_month,NULL,10);
106 else
107 num_day_of_month = -1;
108 if (month[0]!='*') {
109 num_month=strtol(month,NULL,10);
110 num_month--; /* ltm->tm_mon is in [0,11] */
111 } else
112 num_month = -1;
113
114 ltm = localtime(&t);
115
116 if (num_month != -1)
117 {
118 ltm->tm_sec=0;
119 if (num_minutes>0) ltm->tm_min = num_minutes;
120 else ltm->tm_min = 0;
121 if (num_hours>0) ltm->tm_hour = num_hours;
122 else ltm->tm_hour = 0;
123 if (num_day_of_month>0) ltm->tm_mday = num_day_of_month;
124 else ltm->tm_mday = 0;
125 if (num_month <= ltm->tm_mon) ltm->tm_year++;
126 ltm->tm_mon = num_month;
127 }
128
129 /* here month = '*' */
130
131 else if (num_day_of_month != -1)
132 {
133 ltm->tm_sec=0;
134 if (num_minutes>0) ltm->tm_min = num_minutes;
135 else ltm->tm_min = 0;
136 if (num_hours>0) ltm->tm_hour = num_hours;
137 else ltm->tm_hour = 0;
138 if (num_day_of_month <= ltm->tm_mday) ltm->tm_mon++;
139 ltm->tm_mday = num_day_of_month;
140 }
141
142 /* here month = '*' and day = '*' */
143
144 else if (num_hours != -1)
145 {
146 ltm->tm_sec=0;
147 if (num_minutes>0) ltm->tm_min = num_minutes;
148 else ltm->tm_min = 0;
149 if (num_hours <= ltm->tm_hour) ltm->tm_mday++;
150 ltm->tm_hour = num_hours;
151 }
152
153 /* here month = '*' and day = '*' and hour = '*' */
154
155 else if (num_minutes != -1)
156 {
157 ltm->tm_sec=0;
158 if (num_minutes <= ltm->tm_min) ltm->tm_hour++;
159 ltm->tm_min = num_minutes;
160 }
161 else {
162 /* all is '*' */
163 ltm->tm_min++;
164 }
165
166 #if 0
167 if (ltm->tm_min > 59)
168 {
169 ltm->tm_min=0;
170 ltm->tm_hour++;
171 }
172 if (ltm->tm_hour > 23)
173 {
174 ltm->tm_hour = 0;
175 ltm->tm_mday++;
176 }
177 if (ltm->tm_mday > 31)
178 {
179 ltm->tm_mday = 1;
180 ltm->tm_mon++;
181 }
182 if (ltm->tm_mon > 11)
183 {
184 ltm->tm_mon = 0;
185 ltm->tm_year++;
186 }
187 #endif
188
189 t = mktime(ltm);
190 return t;
191 }
192
cronjob_add(wzd_cronjob_t ** crontab,int (* fn)(void),const char * command,const char * minutes,const char * hours,const char * day_of_month,const char * month,const char * day_of_week)193 int cronjob_add(wzd_cronjob_t ** crontab, int (*fn)(void), const char * command,
194 const char * minutes, const char * hours, const char * day_of_month,
195 const char * month, const char * day_of_week)
196 {
197 wzd_cronjob_t *new;
198 time_t now;
199 int ret;
200
201 if (!fn && !command) return 1;
202 /* if (fn && command) return 1;*/ /* why ?! This forbis to provide a description of functions */
203
204 #ifdef WZD_DBG_CRONTAB
205 out_err(LEVEL_HIGH,"adding job %s\n",command);
206 #endif
207
208 new = malloc(sizeof(wzd_cronjob_t));
209 new->hook = malloc(sizeof(struct _wzd_hook_t));
210 new->hook->mask = EVENT_CRONTAB;
211 new->hook->opt = NULL;
212 new->hook->hook = fn;
213 new->hook->external_command = command?strdup(command):NULL;
214 new->hook->next_hook = NULL;
215 strncpy(new->minutes,minutes,32);
216 strncpy(new->hours,hours,32);
217 strncpy(new->day_of_month,day_of_month,32);
218 strncpy(new->month,month,32);
219 strncpy(new->day_of_week,day_of_week,32);
220 (void)time(&now);
221 new->next_run = cronjob_find_next_exec_date(now,minutes,hours,day_of_month,
222 month,day_of_week);
223 new->next_cronjob = NULL;
224
225 #ifdef WZD_DBG_CRONTAB
226 out_err(LEVEL_CRITICAL,"Now: %s",ctime(&now));
227 out_err(LEVEL_CRITICAL,"Next run: %s",ctime(&new->next_run));
228 #endif
229
230 WZD_MUTEX_LOCK(SET_MUTEX_CRONTAB);
231 ret = _crontab_insert_sorted(new,crontab);
232 WZD_MUTEX_UNLOCK(SET_MUTEX_CRONTAB);
233
234 return ret;
235 }
236
237 /** \brief Add job to be run once, at a specified time
238 * This is similar to the at (1) command
239 */
cronjob_add_once(wzd_cronjob_t ** crontab,int (* fn)(void),const char * command,time_t date)240 int cronjob_add_once(wzd_cronjob_t ** crontab, int (*fn)(void), const char * command, time_t date)
241 {
242 wzd_cronjob_t *new;
243 int ret;
244
245 if (!fn && !command) return 1;
246 /* if (fn && command) return 1;*/ /* why ?! This forbis to provide a description of functions */
247
248 #ifdef WZD_DBG_CRONTAB
249 out_err(LEVEL_HIGH,"adding job (once) %s\n",command);
250 #endif
251
252 new = malloc(sizeof(wzd_cronjob_t));
253 new->hook = malloc(sizeof(struct _wzd_hook_t));
254 new->hook->mask = EVENT_CRONTAB;
255 new->hook->opt = NULL;
256 new->hook->hook = fn;
257 new->hook->external_command = command?strdup(command):NULL;
258 new->hook->next_hook = NULL;
259 strncpy(new->minutes,"ONCE",32);
260 new->hours[0] = '\0';
261 new->day_of_month[0] = '\0';
262 new->month[0] = '\0';
263 new->day_of_week[0] = '\0';
264 new->next_run = date;
265 new->next_cronjob = NULL;
266
267 #ifdef WZD_DBG_CRONTAB
268 {
269 time_t now;
270 (void)time(&now);
271 out_err(LEVEL_CRITICAL,"Now: %s",ctime(&now));
272 out_err(LEVEL_CRITICAL,"Next run: %s",ctime(&new->next_run));
273 }
274 #endif
275
276 WZD_MUTEX_LOCK(SET_MUTEX_CRONTAB);
277 ret = _crontab_insert_sorted(new,crontab);
278 WZD_MUTEX_UNLOCK(SET_MUTEX_CRONTAB);
279
280 return ret;
281 }
282
cronjob_run(wzd_cronjob_t ** crontab)283 int cronjob_run(wzd_cronjob_t ** crontab)
284 {
285 wzd_cronjob_t * job = *crontab;
286 time_t now;
287 int ret;
288 wzd_cronjob_t * jobs_to_free = NULL;
289
290 (void)time(&now);
291
292 if (!job || now < job->next_run) return 0;
293
294 WZD_MUTEX_LOCK(SET_MUTEX_CRONTAB);
295
296 /** list is sorted, so we only need to check the first entries */
297 while (job && now >= job->next_run )
298 {
299 /* run job */
300 typedef int (*cronjob_hook)(unsigned long, const char *, const char*);
301 if (job->hook->hook)
302 ret = (*(cronjob_hook)job->hook->hook)(EVENT_CRONTAB,NULL,job->hook->opt);
303 else {
304 if (job->hook->external_command)
305 ret = hook_call_external(job->hook,-1);
306 }
307 /* mark job as done, its position must be changed in list */
308 job->next_run = 0;
309 #ifdef WZD_DBG_CRONTAB
310 out_err(LEVEL_CRITICAL,"Exec'ed %s\n",job->hook->external_command);
311 out_err(LEVEL_CRITICAL,"Now: %s",ctime(&now));
312 #endif
313 job = job->next_cronjob;
314 }
315
316 while ( (*crontab) && (*crontab)->next_run == 0 ) {
317 job = *crontab;
318 *crontab = job->next_cronjob;
319 job->next_run = cronjob_find_next_exec_date(now,job->minutes,job->hours,
320 job->day_of_month, job->month, job->day_of_week);
321 if (job->next_run != 0) {
322 #ifdef WZD_DBG_CRONTAB
323 out_err(LEVEL_FLOOD,"Next run (%s): %s\n",job->hook->external_command,ctime(&job->next_run));
324 #endif
325 _crontab_insert_sorted(job,crontab);
326 } else {
327 #ifdef WZD_DBG_CRONTAB
328 out_err(LEVEL_FLOOD,"Scheduling job (%s) for removal\n",job->hook->external_command);
329 #endif
330 /* we can't call cronjob_free now because it also locks SET_MUTEX_CRONTAB */
331 job->next_cronjob = jobs_to_free;
332 jobs_to_free = job;
333 }
334 }
335
336 WZD_MUTEX_UNLOCK(SET_MUTEX_CRONTAB);
337
338 /* call cronjob_free now because it also locks SET_MUTEX_CRONTAB */
339 cronjob_free(&jobs_to_free);
340
341 return 0;
342 }
343
cronjob_free(wzd_cronjob_t ** crontab)344 void cronjob_free(wzd_cronjob_t ** crontab)
345 {
346 wzd_cronjob_t * current_job, * next_job;
347
348 current_job = *crontab;
349 WZD_MUTEX_LOCK(SET_MUTEX_CRONTAB);
350
351 while (current_job) {
352 next_job = current_job->next_cronjob;
353
354 if (current_job->hook->external_command)
355 free(current_job->hook->external_command);
356 if (current_job->hook)
357 free(current_job->hook);
358 #ifdef DEBUG
359 current_job->hook = NULL;
360 current_job->next_cronjob = NULL;
361 #endif /* DEBUG */
362 free(current_job);
363
364 current_job = next_job;
365 }
366 *crontab = NULL;
367 WZD_MUTEX_UNLOCK(SET_MUTEX_CRONTAB);
368 }
369
370 /** \brief Start crontab thread */
crontab_start(wzd_cronjob_t ** crontab)371 int crontab_start(wzd_cronjob_t ** crontab)
372 {
373 int ret;
374
375 if (_crontab_running) {
376 out_log(LEVEL_NORMAL,"INFO attempt to start crontab twice\n");
377 return 0;
378 }
379
380 out_log(LEVEL_NORMAL,"INFO starting crontab\n");
381 ret = wzd_thread_create(&_crontab_thread, NULL, _crontab_thread_fund, crontab);
382
383 return ret;
384 }
385
386 /** \brief Stop crontab thread */
crontab_stop(void)387 int crontab_stop(void)
388 {
389 void * ret;
390
391 /** \bug test if crontab is already started */
392 if (_crontab_running == 0) {
393 out_log(LEVEL_INFO,"INFO crontab already stopped\n");
394 return 0;
395 }
396
397 _crontab_running = 0;
398 out_log(LEVEL_INFO,"INFO waiting for crontab thread to exit\n");
399 wzd_thread_join(&_crontab_thread, &ret);
400
401 return 0;
402 }
403
404 /* The main crontab thread.
405 *
406 * Checks for cron jobs each second.
407 * The parameter is the address of the cron job list.
408 *
409 * \todo Optimize the wait time by sleeping the exact time until the next job
410 * (how to deal with crontab additions ?)
411 */
_crontab_thread_fund(void * param)412 static void * _crontab_thread_fund(void *param) {
413 _crontab_running = 1;
414
415 while (_crontab_running) {
416 #ifndef WIN32
417 sleep(1);
418 #else
419 Sleep(1000);
420 #endif
421
422 cronjob_run(param);
423 };
424
425 return NULL;
426 }
427
428