]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/lib/jcr.c
ebl Commit file relocation
[bacula/bacula] / bacula / src / lib / jcr.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation plus additions
11    that are listed in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  * Manipulation routines for Job Control Records and
30  *  handling of last_jobs_list.
31  *
32  *  Kern E. Sibbald, December 2000
33  *
34  *  Version $Id$
35  *
36  *  These routines are thread safe.
37  *
38  *  The job list routines were re-written in May 2005 to
39  *  eliminate the global lock while traversing the list, and
40  *  to use the dlist subroutines.  The locking is now done
41  *  on the list each time the list is modified or traversed.
42  *  That is it is "micro-locked" rather than globally locked.
43  *  The result is that there is one lock/unlock for each entry
44  *  in the list while traversing it rather than a single lock
45  *  at the beginning of a traversal and one at the end.  This
46  *  incurs slightly more overhead, but effectively eliminates 
47  *  the possibilty of race conditions.  In addition, with the
48  *  exception of the global locking of the list during the
49  *  re-reading of the config file, no recursion is needed.
50  *
51  */
52
53 #include "bacula.h"
54 #include "jcr.h"
55
56 /* External variables we reference */
57 extern time_t watchdog_time;
58
59 /* External referenced functions */
60 void free_bregexps(alist *bregexps);
61
62 /* Forward referenced functions */
63 extern "C" void timeout_handler(int sig);
64 static void jcr_timeout_check(watchdog_t *self);
65 #ifdef TRACE_JCR_CHAIN
66 static void b_lock_jcr_chain(const char *filen, int line);
67 static void b_unlock_jcr_chain(const char *filen, int line);
68 #define lock_jcr_chain() b_lock_jcr_chain(__FILE__, __LINE__);
69 #define unlock_jcr_chain() b_unlock_jcr_chain(__FILE__, __LINE__);
70 #else
71 static void lock_jcr_chain();
72 static void unlock_jcr_chain();
73 #endif
74
75
76 int num_jobs_run;
77 dlist *last_jobs = NULL;
78 const int max_last_jobs = 10;
79  
80 static dlist *jcrs = NULL;            /* JCR chain */
81 static pthread_mutex_t jcr_lock = PTHREAD_MUTEX_INITIALIZER;
82
83 static pthread_mutex_t job_start_mutex = PTHREAD_MUTEX_INITIALIZER;
84
85 static pthread_mutex_t last_jobs_mutex = PTHREAD_MUTEX_INITIALIZER;
86
87
88 void lock_jobs()
89 {
90    P(job_start_mutex);
91 }
92
93 void unlock_jobs()
94 {
95    V(job_start_mutex);
96 }
97
98 void init_last_jobs_list()
99 {
100    JCR *jcr = NULL;
101    struct s_last_job *job_entry = NULL;
102    if (!last_jobs) {
103       last_jobs = New(dlist(job_entry, &job_entry->link));
104    }
105    if (!jcrs) {
106       jcrs = New(dlist(jcr, &jcr->link));
107    }
108 }
109
110 void term_last_jobs_list()
111 {
112    if (last_jobs) {
113       while (!last_jobs->empty()) {
114          void *je = last_jobs->first();
115          last_jobs->remove(je);
116          free(je);
117       }
118       delete last_jobs;
119       last_jobs = NULL;
120    }
121    if (jcrs) {
122       delete jcrs;
123       jcrs = NULL;
124    }
125 }
126
127 bool read_last_jobs_list(int fd, uint64_t addr)
128 {
129    struct s_last_job *je, job;
130    uint32_t num;
131
132    Dmsg1(100, "read_last_jobs seek to %d\n", (int)addr);
133    if (addr == 0 || lseek(fd, (off_t)addr, SEEK_SET) < 0) {
134       return false;
135    }
136    if (read(fd, &num, sizeof(num)) != sizeof(num)) {
137       return false;
138    }
139    Dmsg1(100, "Read num_items=%d\n", num);
140    if (num > 4 * max_last_jobs) {  /* sanity check */
141       return false;
142    }
143    for ( ; num; num--) {
144       if (read(fd, &job, sizeof(job)) != sizeof(job)) {
145          Dmsg1(000, "Read job entry. ERR=%s\n", strerror(errno));
146          return false;
147       }
148       if (job.JobId > 0) {
149          je = (struct s_last_job *)malloc(sizeof(struct s_last_job));
150          memcpy((char *)je, (char *)&job, sizeof(job));
151          if (!last_jobs) {
152             init_last_jobs_list();
153          }
154          last_jobs->append(je);
155          if (last_jobs->size() > max_last_jobs) {
156             je = (struct s_last_job *)last_jobs->first();
157             last_jobs->remove(je);
158             free(je);
159          }
160       }
161    }
162    return true;
163 }
164
165 uint64_t write_last_jobs_list(int fd, uint64_t addr)
166 {
167    struct s_last_job *je;
168    uint32_t num;
169
170    Dmsg1(100, "write_last_jobs seek to %d\n", (int)addr);
171    if (lseek(fd, (off_t)addr, SEEK_SET) < 0) {
172       return 0;
173    }
174    if (last_jobs) {
175       /* First record is number of entires */
176       num = last_jobs->size();
177       if (write(fd, &num, sizeof(num)) != sizeof(num)) {
178          Dmsg1(000, "Error writing num_items: ERR=%s\n", strerror(errno));
179          return 0;
180       }
181       foreach_dlist(je, last_jobs) {
182          if (write(fd, je, sizeof(struct s_last_job)) != sizeof(struct s_last_job)) {
183             Dmsg1(000, "Error writing job: ERR=%s\n", strerror(errno));
184             return 0;
185          }
186       }
187    }
188    /* Return current address */
189    ssize_t stat = lseek(fd, 0, SEEK_CUR);
190    if (stat < 0) {
191       stat = 0;
192    }
193    return stat;
194
195 }
196
197 void lock_last_jobs_list()
198 {
199    P(last_jobs_mutex);
200 }
201
202 void unlock_last_jobs_list()
203 {
204    V(last_jobs_mutex);
205 }
206
207 /*
208  * Push a subroutine address into the job end callback stack
209  */
210 void job_end_push(JCR *jcr, void job_end_cb(JCR *jcr,void *), void *ctx)
211 {
212    jcr->job_end_push.append((void *)job_end_cb);
213    jcr->job_end_push.append(ctx);
214 }
215
216 /* Pop each job_end subroutine and call it */
217 static void job_end_pop(JCR *jcr)
218 {
219    void (*job_end_cb)(JCR *jcr, void *ctx);
220    void *ctx;
221    for (int i=jcr->job_end_push.size()-1; i > 0; ) {
222       ctx = jcr->job_end_push.get(i--);
223       job_end_cb = (void (*)(JCR *,void *))jcr->job_end_push.get(i--);
224       job_end_cb(jcr, ctx);
225    }
226 }
227
228 /*
229  * Create a Job Control Record and link it into JCR chain
230  * Returns newly allocated JCR
231  * Note, since each daemon has a different JCR, he passes
232  *  us the size.
233  */
234 JCR *new_jcr(int size, JCR_free_HANDLER *daemon_free_jcr)
235 {
236    JCR *jcr;
237    MQUEUE_ITEM *item = NULL;
238    struct sigaction sigtimer;
239
240    Dmsg0(3400, "Enter new_jcr\n");
241    jcr = (JCR *)malloc(size);
242    memset(jcr, 0, size);
243    jcr->my_thread_id = pthread_self();
244    jcr->msg_queue = New(dlist(item, &item->link));
245    jcr->job_end_push.init(1, false);
246    jcr->sched_time = time(NULL);
247    jcr->daemon_free_jcr = daemon_free_jcr;    /* plug daemon free routine */
248    jcr->init_mutex();
249    jcr->inc_use_count();   
250    jcr->VolumeName = get_pool_memory(PM_FNAME);
251    jcr->VolumeName[0] = 0;
252    jcr->errmsg = get_pool_memory(PM_MESSAGE);
253    jcr->errmsg[0] = 0;
254    /* Setup some dummy values */
255    bstrncpy(jcr->Job, "*System*", sizeof(jcr->Job));
256    jcr->JobId = 0;
257    jcr->JobType = JT_SYSTEM;          /* internal job until defined */
258    jcr->JobLevel = L_NONE;
259    set_jcr_job_status(jcr, JS_Created);       /* ready to run */
260
261    sigtimer.sa_flags = 0;
262    sigtimer.sa_handler = timeout_handler;
263    sigfillset(&sigtimer.sa_mask);
264    sigaction(TIMEOUT_SIGNAL, &sigtimer, NULL);
265
266    /*
267     * Locking jobs is a global lock that is needed
268     * so that the Director can stop new jobs from being
269     * added to the jcr chain while it processes a new
270     * conf file and does the job_end_push().
271     */
272    lock_jobs();
273    lock_jcr_chain();
274    if (!jcrs) {
275       jcrs = New(dlist(jcr, &jcr->link));
276    }
277    jcrs->append(jcr);
278    unlock_jcr_chain();
279    unlock_jobs();
280
281    return jcr;
282 }
283
284
285 /*
286  * Remove a JCR from the chain
287  * NOTE! The chain must be locked prior to calling
288  *       this routine.
289  */
290 static void remove_jcr(JCR *jcr)
291 {
292    Dmsg0(3400, "Enter remove_jcr\n");
293    if (!jcr) {
294       Emsg0(M_ABORT, 0, _("NULL jcr.\n"));
295    }
296    jcrs->remove(jcr);
297    Dmsg0(3400, "Leave remove_jcr\n");
298 }
299
300 /*
301  * Free stuff common to all JCRs.  N.B. Be careful to include only
302  *  generic stuff in the common part of the jcr.
303  */
304 static void free_common_jcr(JCR *jcr)
305 {
306    struct s_last_job *je, last_job;
307
308    /* Keep some statistics */
309    switch (jcr->JobType) {
310    case JT_BACKUP:
311    case JT_VERIFY:
312    case JT_RESTORE:
313    case JT_MIGRATE:
314    case JT_COPY:
315    case JT_ADMIN:
316       num_jobs_run++;
317       last_job.Errors = jcr->Errors;
318       last_job.JobType = jcr->JobType;
319       last_job.JobId = jcr->JobId;
320       last_job.VolSessionId = jcr->VolSessionId;
321       last_job.VolSessionTime = jcr->VolSessionTime;
322       bstrncpy(last_job.Job, jcr->Job, sizeof(last_job.Job));
323       last_job.JobFiles = jcr->JobFiles;
324       last_job.JobBytes = jcr->JobBytes;
325       last_job.JobStatus = jcr->JobStatus;
326       last_job.JobLevel = jcr->JobLevel;
327       last_job.start_time = jcr->start_time;
328       last_job.end_time = time(NULL);
329       /* Keep list of last jobs, but not Console where JobId==0 */
330       if (last_job.JobId > 0) {
331          je = (struct s_last_job *)malloc(sizeof(struct s_last_job));
332          memcpy((char *)je, (char *)&last_job, sizeof(last_job));
333          if (!last_jobs) {
334             init_last_jobs_list();
335          }
336          last_jobs->append(je);
337          if (last_jobs->size() > max_last_jobs) {
338             je = (struct s_last_job *)last_jobs->first();
339             last_jobs->remove(je);
340             free(je);
341          }
342       }
343       break;
344    default:
345       break;
346    }
347    jcr->destroy_mutex();
348
349    if (jcr->msg_queue) {
350       delete jcr->msg_queue;
351       jcr->msg_queue = NULL;
352    }
353    close_msg(jcr);                    /* close messages for this job */
354
355    /* do this after closing messages */
356    if (jcr->client_name) {
357       free_pool_memory(jcr->client_name);
358       jcr->client_name = NULL;
359    }
360
361    if (jcr->attr) {
362       free_pool_memory(jcr->attr);
363       jcr->attr = NULL;
364    }
365
366    if (jcr->sd_auth_key) {
367       free(jcr->sd_auth_key);
368       jcr->sd_auth_key = NULL;
369    }
370    if (jcr->VolumeName) {
371       free_pool_memory(jcr->VolumeName);
372       jcr->VolumeName = NULL;
373    }
374
375    if (jcr->dir_bsock) {
376       bnet_close(jcr->dir_bsock);
377       jcr->dir_bsock = NULL;
378    }
379    if (jcr->errmsg) {
380       free_pool_memory(jcr->errmsg);
381       jcr->errmsg = NULL;
382    }
383    if (jcr->where) {
384       free(jcr->where);
385       jcr->where = NULL;
386    }
387    if (jcr->where_bregexp) {
388       free_bregexps(jcr->where_bregexp);
389       delete jcr->where_bregexp;
390       jcr->where_bregexp = NULL;
391    }
392    if (jcr->cached_path) {
393       free_pool_memory(jcr->cached_path);
394       jcr->cached_path = NULL;
395       jcr->cached_pnl = 0;
396    }
397    free_getuser_cache();
398    free_getgroup_cache();
399    free(jcr);
400 }
401
402 /*
403  * Global routine to free a jcr
404  */
405 #ifdef DEBUG
406 void b_free_jcr(const char *file, int line, JCR *jcr)
407 {
408    Dmsg3(3400, "Enter free_jcr 0x%x from %s:%d\n", jcr, file, line);
409
410 #else
411
412 void free_jcr(JCR *jcr)
413 {
414
415    Dmsg2(3400, "Enter free_jcr 0x%x job=%d\n", jcr, jcr->JobId);
416
417 #endif
418
419    dequeue_messages(jcr);
420    lock_jcr_chain();
421    jcr->dec_use_count();              /* decrement use count */
422    if (jcr->use_count() < 0) {
423       Emsg2(M_ERROR, 0, _("JCR use_count=%d JobId=%d\n"),
424          jcr->use_count(), jcr->JobId);
425    }
426    Dmsg3(3400, "Dec free_jcr 0x%x use_count=%d jobid=%d\n", jcr, jcr->use_count(), jcr->JobId);
427    if (jcr->use_count() > 0) {          /* if in use */
428       unlock_jcr_chain();
429       Dmsg3(3400, "free_jcr 0x%x job=%d use_count=%d\n", jcr, jcr->JobId, jcr->use_count());
430       return;
431    }
432
433    remove_jcr(jcr);                   /* remove Jcr from chain */
434    unlock_jcr_chain();
435
436    job_end_pop(jcr);                  /* pop and call hooked routines */
437
438    Dmsg1(3400, "End job=%d\n", jcr->JobId);
439    if (jcr->daemon_free_jcr) {
440       jcr->daemon_free_jcr(jcr);      /* call daemon free routine */
441    }
442    free_common_jcr(jcr);
443    close_msg(NULL);                   /* flush any daemon messages */
444    garbage_collect_memory_pool();
445    Dmsg0(3400, "Exit free_jcr\n");
446 }
447
448
449 /*
450  * Given a JobId, find the JCR
451  *   Returns: jcr on success
452  *            NULL on failure
453  */
454 JCR *get_jcr_by_id(uint32_t JobId)
455 {
456    JCR *jcr;
457
458    foreach_jcr(jcr) {
459       if (jcr->JobId == JobId) {
460          jcr->inc_use_count();
461          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
462          break;
463       }
464    }
465    endeach_jcr(jcr);
466    return jcr;
467 }
468
469 /*
470  * Given a SessionId and SessionTime, find the JCR
471  *   Returns: jcr on success
472  *            NULL on failure
473  */
474 JCR *get_jcr_by_session(uint32_t SessionId, uint32_t SessionTime)
475 {
476    JCR *jcr;
477
478    foreach_jcr(jcr) {
479       if (jcr->VolSessionId == SessionId &&
480           jcr->VolSessionTime == SessionTime) {
481          jcr->inc_use_count();
482          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
483          break;
484       }
485    }
486    endeach_jcr(jcr);
487    return jcr;
488 }
489
490
491 /*
492  * Given a Job, find the JCR
493  *  compares on the number of characters in Job
494  *  thus allowing partial matches.
495  *   Returns: jcr on success
496  *            NULL on failure
497  */
498 JCR *get_jcr_by_partial_name(char *Job)
499 {
500    JCR *jcr;
501    int len;
502
503    if (!Job) {
504       return NULL;
505    }
506    len = strlen(Job);
507    foreach_jcr(jcr) {
508       if (strncmp(Job, jcr->Job, len) == 0) {
509          jcr->inc_use_count();
510          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
511          break;
512       }
513    }
514    endeach_jcr(jcr);
515    return jcr;
516 }
517
518
519 /*
520  * Given a Job, find the JCR
521  *  requires an exact match of names.
522  *   Returns: jcr on success
523  *            NULL on failure
524  */
525 JCR *get_jcr_by_full_name(char *Job)
526 {
527    JCR *jcr;
528
529    if (!Job) {
530       return NULL;
531    }
532    foreach_jcr(jcr) {
533       if (strcmp(jcr->Job, Job) == 0) {
534          jcr->inc_use_count();
535          Dmsg2(3400, "Inc get_jcr 0x%x use_count=%d\n", jcr, jcr->use_count());
536          break;
537       }
538    }
539    endeach_jcr(jcr);
540    return jcr;
541 }
542
543 void set_jcr_job_status(JCR *jcr, int JobStatus)
544 {
545    /*
546     * For a set of errors, ... keep the current status
547     *   so it isn't lost. For all others, set it.
548     */
549    switch (jcr->JobStatus) {
550    case JS_ErrorTerminated:
551    case JS_Error:
552    case JS_FatalError:
553    case JS_Differences:
554    case JS_Canceled:
555       break;
556    default:
557       jcr->JobStatus = JobStatus;
558    }
559 }
560
561 #ifdef TRACE_JCR_CHAIN
562 static int lock_count = 0;
563 #endif
564
565 /*
566  * Lock the chain
567  */
568 #ifdef TRACE_JCR_CHAIN
569 static void b_lock_jcr_chain(const char *fname, int line)
570 #else
571 static void lock_jcr_chain()
572 #endif
573 {
574 #ifdef TRACE_JCR_CHAIN
575    Dmsg3(3400, "Lock jcr chain %d from %s:%d\n", ++lock_count,
576       fname, line);
577 #endif
578    P(jcr_lock);
579 }
580
581 /*
582  * Unlock the chain
583  */
584 #ifdef TRACE_JCR_CHAIN
585 static void b_unlock_jcr_chain(const char *fname, int line)
586 #else
587 static void unlock_jcr_chain()
588 #endif
589 {
590 #ifdef TRACE_JCR_CHAIN
591    Dmsg3(3400, "Unlock jcr chain %d from %s:%d\n", lock_count--,
592       fname, line);
593 #endif
594    V(jcr_lock);
595 }
596
597
598 /*
599  * Start walk of jcr chain
600  * The proper way to walk the jcr chain is:
601  *    JCR *jcr;
602  *    foreach_jcr(jcr) {
603  *      ...
604  *    }
605  *    endeach_jcr(jcr);
606  *
607  *  It is possible to leave out the endeach_jcr(jcr), but
608  *   in that case, the last jcr referenced must be explicitly
609  *   released with:
610  *
611  *    free_jcr(jcr);
612  *  
613  */
614 JCR *jcr_walk_start() 
615 {
616    JCR *jcr;
617    lock_jcr_chain();
618    jcr = (JCR *)jcrs->first();
619    if (jcr) {
620       jcr->inc_use_count();
621       Dmsg3(3400, "Inc jcr_walk_start 0x%x job=%d use_count=%d\n", jcr, 
622             jcr->JobId, jcr->use_count());
623    }
624    unlock_jcr_chain();
625    return jcr;
626 }
627
628 /*
629  * Get next jcr from chain, and release current one
630  */
631 JCR *jcr_walk_next(JCR *prev_jcr)
632 {
633    JCR *jcr;
634
635    lock_jcr_chain();
636    jcr = (JCR *)jcrs->next(prev_jcr);
637    if (jcr) {
638       jcr->inc_use_count();
639       Dmsg3(3400, "Inc jcr_walk_next 0x%x job=%d use_count=%d\n", jcr, 
640          jcr->JobId, jcr->use_count());
641    }
642    unlock_jcr_chain();
643    if (prev_jcr) {
644       free_jcr(prev_jcr);
645    }
646    return jcr;
647 }
648
649 /*
650  * Release last jcr referenced
651  */
652 void jcr_walk_end(JCR *jcr)
653 {
654    if (jcr) {
655       free_jcr(jcr);
656    }
657 }
658
659
660 /*
661  * Setup to call the timeout check routine every 30 seconds
662  *  This routine will check any timers that have been enabled.
663  */
664 bool init_jcr_subsystem(void)
665 {
666    watchdog_t *wd = new_watchdog();
667
668    wd->one_shot = false;
669    wd->interval = 30;   /* FIXME: should be configurable somewhere, even
670                          if only with a #define */
671    wd->callback = jcr_timeout_check;
672
673    register_watchdog(wd);
674
675    return true;
676 }
677
678 static void jcr_timeout_check(watchdog_t *self)
679 {
680    JCR *jcr;
681    BSOCK *fd;
682    time_t timer_start;
683
684    Dmsg0(3400, "Start JCR timeout checks\n");
685
686    /* Walk through all JCRs checking if any one is
687     * blocked for more than specified max time.
688     */
689    foreach_jcr(jcr) {
690       Dmsg2(3400, "jcr_timeout_check JobId=%u jcr=0x%x\n", jcr->JobId, jcr);
691       if (jcr->JobId == 0) {
692          continue;
693       }
694       fd = jcr->store_bsock;
695       if (fd) {
696          timer_start = fd->timer_start;
697          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
698             fd->timer_start = 0;      /* turn off timer */
699             fd->timed_out = true;
700             Jmsg(jcr, M_ERROR, 0, _(
701 "Watchdog sending kill after %d secs to thread stalled reading Storage daemon.\n"),
702                  watchdog_time - timer_start);
703             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
704          }
705       }
706       fd = jcr->file_bsock;
707       if (fd) {
708          timer_start = fd->timer_start;
709          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
710             fd->timer_start = 0;      /* turn off timer */
711             fd->timed_out = true;
712             Jmsg(jcr, M_ERROR, 0, _(
713 "Watchdog sending kill after %d secs to thread stalled reading File daemon.\n"),
714                  watchdog_time - timer_start);
715             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
716          }
717       }
718       fd = jcr->dir_bsock;
719       if (fd) {
720          timer_start = fd->timer_start;
721          if (timer_start && (watchdog_time - timer_start) > fd->timeout) {
722             fd->timer_start = 0;      /* turn off timer */
723             fd->timed_out = true;
724             Jmsg(jcr, M_ERROR, 0, _(
725 "Watchdog sending kill after %d secs to thread stalled reading Director.\n"),
726                  watchdog_time - timer_start);
727             pthread_kill(jcr->my_thread_id, TIMEOUT_SIGNAL);
728          }
729       }
730    }
731    endeach_jcr(jcr);
732
733    Dmsg0(3400, "Finished JCR timeout checks\n");
734 }
735
736 /*
737  * Timeout signal comes here
738  */
739 extern "C" void timeout_handler(int sig)
740 {
741    return;                            /* thus interrupting the function */
742 }