]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/jcr.c
Nic's watchdog code + cleanup jcr locking/use_count
[bacula/bacula] / bacula / src / lib / jcr.c
1 /*
2  * Manipulation routines for Job Control Records
3  *
4  *  Kern E. Sibbald, December 2000
5  *
6  *  Version $Id$
7  *
8  *  These routines are thread safe.
9  *
10  */
11 /*
12    Copyright (C) 2000-2003 Kern Sibbald and John Walker
13
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of
17    the License, or (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public
25    License along with this program; if not, write to the Free
26    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27    MA 02111-1307, USA.
28
29  */
30
31 #include "bacula.h"
32 #include "jcr.h"
33
34 /* External variables we reference */
35 extern time_t watchdog_time;
36
37 /* Forward referenced functions */
38 static void timeout_handler(int sig);
39 static void jcr_timeout_check(watchdog_t *self);
40
41 struct s_last_job last_job;    /* last job run by this daemon */
42 dlist *last_jobs;
43 #define MAX_LAST_JOBS 10
44
45 static JCR *jobs = NULL;              /* pointer to JCR chain */
46
47 /* Mutex for locking various jcr chains while updating */
48 static pthread_mutex_t jcr_chain_mutex = PTHREAD_MUTEX_INITIALIZER;
49
50 void init_last_jobs_list()
51 {
52    struct s_last_job *job_entry;
53    last_jobs = new dlist(job_entry,  &job_entry->link);
54    memset(&last_job, 0, sizeof(last_job));
55 }
56
57 void term_last_jobs_list()
58 {
59    for (void *je=NULL; (je=last_jobs->next(je)); ) {
60       free(je);                     
61    }
62    delete last_jobs;
63 }
64
65 void lock_last_jobs_list() 
66 {
67    /* Use jcr chain mutex */
68    P(jcr_chain_mutex);
69 }
70
71 void unlock_last_jobs_list() 
72 {
73    /* Use jcr chain mutex */
74    V(jcr_chain_mutex);
75 }
76
77 /*
78  * Create a Job Control Record and link it into JCR chain
79  * Returns newly allocated JCR
80  * Note, since each daemon has a different JCR, he passes
81  *  us the size.
82  */
83 JCR *new_jcr(int size, JCR_free_HANDLER *daemon_free_jcr)
84 {
85    JCR *jcr;
86    struct sigaction sigtimer;
87
88    Dmsg0(200, "Enter new_jcr\n");
89    jcr = (JCR *)malloc(size);
90    memset(jcr, 0, size);
91    jcr->my_thread_id = pthread_self();
92    jcr->sched_time = time(NULL);
93    jcr->daemon_free_jcr = daemon_free_jcr;    /* plug daemon free routine */
94    jcr->use_count = 1;
95    pthread_mutex_init(&(jcr->mutex), NULL);
96    jcr->JobStatus = JS_Created;       /* ready to run */
97    jcr->VolumeName = get_pool_memory(PM_FNAME);
98    jcr->VolumeName[0] = 0;
99    jcr->errmsg = get_pool_memory(PM_MESSAGE);
100    jcr->errmsg[0] = 0;
101    strcpy(jcr->Job, "*Console*");     /* default */
102
103    sigtimer.sa_flags = 0;
104    sigtimer.sa_handler = timeout_handler;
105    sigfillset(&sigtimer.sa_mask);
106    sigaction(TIMEOUT_SIGNAL, &sigtimer, NULL);
107
108    P(jcr_chain_mutex);
109    jcr->prev = NULL;
110    jcr->next = jobs;
111    if (jobs) {
112       jobs->prev = jcr;
113    }
114    jobs = jcr;
115    V(jcr_chain_mutex);
116    return jcr;
117 }
118
119
120 /*
121  * Remove a JCR from the chain
122  * NOTE! The chain must be locked prior to calling
123  *       this routine.
124  */
125 static void remove_jcr(JCR *jcr)
126 {
127    Dmsg0(150, "Enter remove_jcr\n");
128    if (!jcr) {
129       Emsg0(M_ABORT, 0, "NULL jcr.\n");
130    }
131    if (!jcr->prev) {                  /* if no prev */
132       jobs = jcr->next;               /* set new head */
133    } else {
134       jcr->prev->next = jcr->next;    /* update prev */
135    }
136    if (jcr->next) {
137       jcr->next->prev = jcr->prev;
138    }
139    Dmsg0(150, "Leave remove_jcr\n");
140 }
141
142 /*
143  * Free stuff common to all JCRs.  N.B. Be careful to include only
144  *  generic stuff in the common part of the jcr. 
145  */
146 static void free_common_jcr(JCR *jcr)
147 {
148    /* Keep some statistics */
149    switch (jcr->JobType) {
150    case JT_BACKUP:
151    case JT_VERIFY:
152    case JT_RESTORE:
153    case JT_ADMIN:
154       last_job.NumJobs++;
155       last_job.JobType = jcr->JobType;
156       last_job.JobId = jcr->JobId;
157       last_job.VolSessionId = jcr->VolSessionId;
158       last_job.VolSessionTime = jcr->VolSessionTime;
159       bstrncpy(last_job.Job, jcr->Job, sizeof(last_job.Job));
160       last_job.JobFiles = jcr->JobFiles;
161       last_job.JobBytes = jcr->JobBytes;
162       last_job.JobStatus = jcr->JobStatus;
163       last_job.JobLevel = jcr->JobLevel;
164       last_job.start_time = jcr->start_time;
165       last_job.end_time = time(NULL);
166       break;
167    default:
168       break;
169    }
170    pthread_mutex_destroy(&jcr->mutex);
171
172    close_msg(jcr);                    /* close messages for this job */
173
174    /* do this after closing messages */
175    if (jcr->client_name) {
176       free_pool_memory(jcr->client_name);
177       jcr->client_name = NULL;
178    }
179
180    if (jcr->sd_auth_key) {
181       free(jcr->sd_auth_key);
182       jcr->sd_auth_key = NULL;
183    }
184    if (jcr->VolumeName) {
185       free_pool_memory(jcr->VolumeName);
186       jcr->VolumeName = NULL;
187    }
188
189    if (jcr->dir_bsock) {
190       bnet_close(jcr->dir_bsock);
191       jcr->dir_bsock = NULL;
192    }
193    if (jcr->errmsg) {
194       free_pool_memory(jcr->errmsg);
195       jcr->errmsg = NULL;
196    }
197    if (jcr->where) {
198       free(jcr->where);
199       jcr->where = NULL;
200    }
201    if (jcr->cached_path) {
202       free_pool_memory(jcr->cached_path);
203       jcr->cached_path = NULL;
204       jcr->cached_pnl = 0;
205    }
206    free_getuser_cache();
207    free_getgroup_cache();
208    free(jcr);
209 }
210
211 /* 
212  * Global routine to free a jcr
213  */
214 #ifdef DEBUG
215 void b_free_jcr(char *file, int line, JCR *jcr)
216 {
217    Dmsg3(200, "Enter free_jcr 0x%x from %s:%d\n", jcr, file, line);
218
219 #else
220
221 void free_jcr(JCR *jcr)
222 {
223
224    Dmsg1(200, "Enter free_jcr 0x%x\n", jcr);
225
226 #endif
227    struct s_last_job *je;
228
229    P(jcr_chain_mutex);
230    jcr->use_count--;                  /* decrement use count */
231    Dmsg3(200, "Dec jcr 0x%x use_count=%d jobid=%d\n", jcr, jcr->use_count, jcr->JobId);
232    if (jcr->use_count > 0) {          /* if in use */
233       V(jcr_chain_mutex);
234       Dmsg2(200, "jcr 0x%x use_count=%d\n", jcr, jcr->use_count);
235       return;
236    }
237    remove_jcr(jcr);
238
239    Dmsg1(200, "End job=%d\n", jcr->JobId);
240    if (jcr->daemon_free_jcr) {
241       jcr->daemon_free_jcr(jcr);      /* call daemon free routine */
242    }
243
244    free_common_jcr(jcr);
245
246    /* Keep list of last jobs, but not Console where JobId==0 */
247    if (last_job.JobId > 0) {
248       je = (struct s_last_job *)malloc(sizeof(struct s_last_job));
249       memcpy((char *)je, (char *)&last_job, sizeof(last_job));
250       last_jobs->append(je);
251       if (last_jobs->size() > MAX_LAST_JOBS) {
252          last_jobs->remove(last_jobs->first());
253       }
254       last_job.JobId = 0;             /* zap last job */
255    }
256    close_msg(NULL);                   /* flush any daemon messages */
257    V(jcr_chain_mutex);
258    Dmsg0(200, "Exit free_jcr\n");
259 }
260
261
262 /* 
263  * Global routine to free a jcr
264  *  JCR chain is already locked
265  */
266 void free_locked_jcr(JCR *jcr)
267 {
268    jcr->use_count--;                  /* decrement use count */
269    Dmsg2(200, "Dec jcr 0x%x use_count=%d\n", jcr, jcr->use_count);
270    if (jcr->use_count > 0) {          /* if in use */
271       return;
272    }
273    remove_jcr(jcr);
274    jcr->daemon_free_jcr(jcr);         /* call daemon free routine */
275    free_common_jcr(jcr);
276 }
277
278
279
280
281 /*
282  * Given a JobId, find the JCR      
283  *   Returns: jcr on success
284  *            NULL on failure
285  */
286 JCR *get_jcr_by_id(uint32_t JobId)
287 {
288    JCR *jcr;       
289
290    P(jcr_chain_mutex);                  /* lock chain */
291    for (jcr = jobs; jcr; jcr=jcr->next) {
292       if (jcr->JobId == JobId) {
293          P(jcr->mutex);
294          jcr->use_count++;
295          V(jcr->mutex);
296          Dmsg2(200, "Inc jcr 0x%x use_count=%d\n", jcr, jcr->use_count);
297          break;
298       }
299    }
300    V(jcr_chain_mutex);
301    return jcr; 
302 }
303
304 /*
305  * Given a SessionId and SessionTime, find the JCR      
306  *   Returns: jcr on success
307  *            NULL on failure
308  */
309 JCR *get_jcr_by_session(uint32_t SessionId, uint32_t SessionTime)
310 {
311    JCR *jcr;       
312
313    P(jcr_chain_mutex);
314    for (jcr = jobs; jcr; jcr=jcr->next) {
315       if (jcr->VolSessionId == SessionId && 
316           jcr->VolSessionTime == SessionTime) {
317          P(jcr->mutex);
318          jcr->use_count++;
319          V(jcr->mutex);
320          Dmsg2(200, "Inc jcr 0x%x use_count=%d\n", jcr, jcr->use_count);
321          break;
322       }
323    }
324    V(jcr_chain_mutex);
325    return jcr; 
326 }
327
328
329 /*
330  * Given a Job, find the JCR      
331  *  compares on the number of characters in Job
332  *  thus allowing partial matches.
333  *   Returns: jcr on success
334  *            NULL on failure
335  */
336 JCR *get_jcr_by_partial_name(char *Job)
337 {
338    JCR *jcr;       
339    int len;
340
341    if (!Job) {
342       return NULL;
343    }
344    P(jcr_chain_mutex);
345    len = strlen(Job);
346    for (jcr = jobs; jcr; jcr=jcr->next) {
347       if (strncmp(Job, jcr->Job, len) == 0) {
348          P(jcr->mutex);
349          jcr->use_count++;
350          V(jcr->mutex);
351          Dmsg2(200, "Inc jcr 0x%x use_count=%d\n", jcr, jcr->use_count);
352          break;
353       }
354    }
355    V(jcr_chain_mutex);
356    return jcr; 
357 }
358
359
360 /*
361  * Given a Job, find the JCR      
362  *  requires an exact match of names.
363  *   Returns: jcr on success
364  *            NULL on failure
365  */
366 JCR *get_jcr_by_full_name(char *Job)
367 {
368    JCR *jcr;       
369
370    if (!Job) {
371       return NULL;
372    }
373    P(jcr_chain_mutex);
374    for (jcr = jobs; jcr; jcr=jcr->next) {
375       if (strcmp(jcr->Job, Job) == 0) {
376          P(jcr->mutex);
377          jcr->use_count++;
378          V(jcr->mutex);
379          Dmsg2(200, "Inc jcr 0x%x use_count=%d\n", jcr, jcr->use_count);
380          break;
381       }
382    }
383    V(jcr_chain_mutex);
384    return jcr; 
385 }
386
387 void set_jcr_job_status(JCR *jcr, int JobStatus)
388 {
389    /*
390     * For a set of errors, ... keep the current status
391     *   so it isn't lost. For all others, set it.
392     */
393    switch (jcr->JobStatus) {
394    case JS_ErrorTerminated:
395    case JS_Error:
396    case JS_FatalError:
397    case JS_Differences:
398    case JS_Canceled:
399       break;
400    default:
401       jcr->JobStatus = JobStatus;
402    }
403 }
404
405 /* 
406  * Lock the chain
407  */
408 void lock_jcr_chain()
409 {
410    P(jcr_chain_mutex);
411 }
412
413 /*
414  * Unlock the chain
415  */
416 void unlock_jcr_chain()
417 {
418    V(jcr_chain_mutex);
419 }
420
421
422 JCR *get_next_jcr(JCR *jcr)
423 {
424    JCR *rjcr;
425
426    if (jcr == NULL) {
427       rjcr = jobs;
428    } else {
429       rjcr = jcr->next;
430    }
431    if (rjcr) {
432       P(rjcr->mutex);
433       rjcr->use_count++;
434       V(rjcr->mutex);
435       Dmsg1(200, "Inc jcr use_count=%d\n", rjcr->use_count);
436    }
437    return rjcr;
438 }
439
440 bool init_jcr_subsystem(void)
441 {
442    watchdog_t *wd = watchdog_new();
443
444    wd->one_shot = false;
445    wd->interval = 30;   /* FIXME: should be configurable somewhere, even
446                          if only with a #define */
447    wd->callback = jcr_timeout_check;
448
449    register_watchdog(wd);
450
451    return true;
452 }
453
454 static void jcr_timeout_check(watchdog_t *self)
455 {
456    JCR *jcr;
457    BSOCK *fd;
458    time_t timer_start, now;
459
460    Dmsg0(200, "Start JCR timeout checks\n");
461
462    /* Walk through all JCRs checking if any one is 
463     * blocked for more than specified max time.
464     */
465    lock_jcr_chain();
466    for (jcr=NULL; (jcr=get_next_jcr(jcr)); ) {
467       free_locked_jcr(jcr);           /* OK to free now cuz chain is locked */
468       if (jcr->JobId == 0) {
469          continue;
470       }
471       fd = jcr->store_bsock;
472       if (fd) {
473          timer_start = fd->timer_start;
474          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
475             fd->timer_start = 0;      /* turn off timer */
476             fd->timed_out = TRUE;
477             Jmsg(jcr, M_ERROR, 0, _(
478 "Watchdog sending kill after %d secs to thread stalled reading Storage daemon.\n"),
479                  watchdog_time - timer_start);
480             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
481          }
482       }
483       fd = jcr->file_bsock;
484       if (fd) {
485          timer_start = fd->timer_start;
486          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
487             fd->timer_start = 0;      /* turn off timer */
488             fd->timed_out = TRUE;
489             Jmsg(jcr, M_ERROR, 0, _(
490 "Watchdog sending kill after %d secs to thread stalled reading File daemon.\n"),
491                  watchdog_time - timer_start);
492             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
493          }
494       }
495       fd = jcr->dir_bsock;
496       if (fd) {
497          timer_start = fd->timer_start;
498          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
499             fd->timer_start = 0;      /* turn off timer */
500             fd->timed_out = TRUE;
501             Jmsg(jcr, M_ERROR, 0, _(
502 "Watchdog sending kill after %d secs to thread stalled reading Director.\n"),
503                  watchdog_time - timer_start);
504             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
505          }
506       }
507
508    }
509    unlock_jcr_chain();
510
511    Dmsg0(200, "Finished JCR timeout checks\n");
512 }
513
514 /*
515  * Timeout signal comes here
516  */
517 void timeout_handler(int sig)
518 {
519    return;                            /* thus interrupting the function */
520 }