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