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