]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/vbackup.c
Tweaks to htable code to improved debugging and make names
[bacula/bacula] / bacula / src / dird / vbackup.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2008-2008 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 and included
11    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 Kern Sibbald.
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  *
30  *   Bacula Director -- vbackup.c -- responsible for doing virtual
31  *     backup jobs or in other words, consolidation or synthetic
32  *     backups.
33  *
34  *     Kern Sibbald, July MMVIII
35  *
36  *  Basic tasks done here:
37  *     Open DB and create records for this job.
38  *     Figure out what Jobs to copy.
39  *     Open Message Channel with Storage daemon to tell him a job will be starting.
40  *     Open connection with File daemon and pass him commands
41  *       to do the backup.
42  *     When the File daemon finishes the job, update the DB.
43  *
44  *   Version $Id: $
45  */
46
47 #include "bacula.h"
48 #include "dird.h"
49 #include "ua.h"
50
51 static const int dbglevel = 10;
52
53 static char OKbootstrap[] = "3000 OK bootstrap\n";
54
55 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids);
56 void vbackup_cleanup(JCR *jcr, int TermCode);
57
58 /* 
59  * Called here before the job is run to do the job
60  *   specific setup.
61  */
62 bool do_vbackup_init(JCR *jcr)
63 {
64    char *p;
65
66    if (!get_or_create_fileset_record(jcr)) {
67       Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
68       return false;
69    }
70
71    apply_pool_overrides(jcr);
72
73    if (!allow_duplicate_job(jcr)) {
74       return false;
75    }
76
77    jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
78    if (jcr->jr.PoolId == 0) {
79       Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
80       Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
81       return false;
82    }
83    /*
84     * Note, at this point, pool is the pool for this job.  We
85     *  transfer it to rpool (read pool), and a bit later,
86     *  pool will be changed to point to the write pool, 
87     *  which comes from pool->NextPool.
88     */
89    jcr->rpool = jcr->pool;            /* save read pool */
90    pm_strcpy(jcr->rpool_source, jcr->pool_source);
91
92    /* If pool storage specified, use it for restore */
93    copy_rstorage(jcr, jcr->pool->storage, _("Pool resource"));
94
95    Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
96
97    jcr->start_time = time(NULL);
98    jcr->jr.StartTime = jcr->start_time;
99    jcr->jr.JobLevel = L_FULL;      /* we want this to appear as a Full backup */
100    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
101       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
102    }
103
104    POOLMEM *jobids = get_pool_memory(PM_FNAME);
105    jcr->jr.JobLevel = L_VIRTUAL_FULL;
106    db_accurate_get_jobids(jcr, jcr->db, &jcr->jr, jobids);
107    jcr->jr.JobLevel = L_FULL;
108    Dmsg1(10, "Accurate jobids=%s\n", jobids);
109    if (*jobids == 0) {
110       free_pool_memory(jobids);
111       Jmsg(jcr, M_FATAL, 0, _("Cannot find previous JobIds.\n"));
112       return false;
113    }
114
115    /*
116     * Now we find the last job that ran and store it's info in
117     *  the previous_jr record.  We will set our times to the
118     *  values from that job so that anything changed after that
119     *  time will be picked up on the next backup.
120     */
121    p = strrchr(jobids, ',');              /* find last jobid */
122    if (p != NULL) {
123       p++;
124    } else {
125       p = jobids;
126    }
127    memset(&jcr->previous_jr, 0, sizeof(jcr->previous_jr));
128    jcr->previous_jr.JobId = str_to_int64(p);
129    if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
130       Jmsg(jcr, M_FATAL, 0, _("Error getting Job record for previous Job: ERR=%s"),
131                db_strerror(jcr->db));
132       return false;
133    }
134
135    if (!create_bootstrap_file(jcr, jobids)) {
136       Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
137       free_pool_memory(jobids);
138       return false;
139    }
140    free_pool_memory(jobids);
141
142    /*
143     * If the original backup pool has a NextPool, make sure a 
144     *  record exists in the database. Note, in this case, we
145     *  will be backing up from pool to pool->NextPool.
146     */
147    if (jcr->pool->NextPool) {
148       jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->NextPool->name());
149       if (jcr->jr.PoolId == 0) {
150          return false;
151       }
152    }
153    if (!set_migration_wstorage(jcr, jcr->pool)) {
154       return false;
155    }
156    jcr->pool = jcr->pool->NextPool;
157    pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
158
159    Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
160
161 // create_clones(jcr);
162
163    return true;
164 }
165
166 /*
167  * Do a virtual backup, which consolidates all previous backups into
168  *  a sort of synthetic Full.
169  *
170  *  Returns:  false on failure
171  *            true  on success
172  */
173 bool do_vbackup(JCR *jcr)
174 {
175    char ed1[100];
176    BSOCK *sd;
177
178    Dmsg2(100, "rstorage=%p wstorage=%p\n", jcr->rstorage, jcr->wstorage);
179    Dmsg2(100, "Read store=%s, write store=%s\n", 
180       ((STORE *)jcr->rstorage->first())->name(),
181       ((STORE *)jcr->wstorage->first())->name());
182    /* ***FIXME***  we really should simply verify that the pools are different */
183 #ifdef xxx
184    if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
185       Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
186            ((STORE *)jcr->rstorage->first())->name());
187       return false;
188    }
189 #endif
190
191    /* Print Job Start message */
192    Jmsg(jcr, M_INFO, 0, _("Start Virtual Backup JobId %s, Job=%s\n"),
193         edit_uint64(jcr->JobId, ed1), jcr->Job);
194    if (!jcr->accurate) {
195       Jmsg(jcr, M_WARNING, 0, 
196 _("This Job is not an Accurate backup so is not equivalent to a Full backup.\n"));
197    }
198
199    /*
200     * Open a message channel connection with the Storage
201     * daemon. This is to let him know that our client
202     * will be contacting him for a backup  session.
203     *
204     */
205    Dmsg0(110, "Open connection with storage daemon\n");
206    set_jcr_job_status(jcr, JS_WaitSD);
207    /*
208     * Start conversation with Storage daemon
209     */
210    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
211       return false;
212    }
213    sd = jcr->store_bsock;
214
215    /*
216     * Now start a job with the Storage daemon
217     */
218    if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
219       return false;
220    }
221    Dmsg0(100, "Storage daemon connection OK\n");
222
223    if (!send_bootstrap_file(jcr, sd) ||
224        !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
225       return false;
226    }
227
228    /*    
229     * We re-update the job start record so that the start
230     *  time is set after the run before job.  This avoids 
231     *  that any files created by the run before job will
232     *  be saved twice.  They will be backed up in the current
233     *  job, but not in the next one unless they are changed.
234     *  Without this, they will be backed up in this job and
235     *  in the next job run because in that case, their date 
236     *   is after the start of this run.
237     */
238    jcr->start_time = time(NULL);
239    jcr->jr.StartTime = jcr->start_time;
240    jcr->jr.JobTDate = jcr->start_time;
241    set_jcr_job_status(jcr, JS_Running);
242
243    /* Update job start record */
244    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
245       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
246       return false;
247    }
248
249    /*
250     * Start the job prior to starting the message thread below
251     * to avoid two threads from using the BSOCK structure at
252     * the same time.
253     */
254    if (!sd->fsend("run")) {
255       return false;
256    }
257
258    /*
259     * Now start a Storage daemon message thread
260     */
261    if (!start_storage_daemon_message_thread(jcr)) {
262       return false;
263    }
264
265    set_jcr_job_status(jcr, JS_Running);
266
267    /* Pickup Job termination data */
268    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
269    wait_for_storage_daemon_termination(jcr);
270    set_jcr_job_status(jcr, jcr->SDJobStatus);
271    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
272    if (jcr->JobStatus != JS_Terminated) {
273       return false;
274    }
275
276    vbackup_cleanup(jcr, jcr->JobStatus);
277    return true;
278 }
279
280
281 /*
282  * Release resources allocated during backup.
283  */
284 void vbackup_cleanup(JCR *jcr, int TermCode)
285 {
286    char sdt[50], edt[50], schedt[50];
287    char ec1[30], ec3[30], ec4[30], compress[50];
288    char ec7[30], ec8[30], elapsed[50];
289    char term_code[100], sd_term_msg[100];
290    const char *term_msg;
291    int msg_type = M_INFO;
292    MEDIA_DBR mr;
293    CLIENT_DBR cr;
294    double kbps, compression;
295    utime_t RunTime;
296    POOL_MEM query(PM_MESSAGE);
297
298    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
299    memset(&mr, 0, sizeof(mr));
300    memset(&cr, 0, sizeof(cr));
301
302    jcr->set_JobLevel(L_FULL);         /* we want this to appear as a Full backup */
303    jcr->jr.JobLevel = L_FULL;         /* we want this to appear as a Full backup */
304    jcr->JobFiles = jcr->SDJobFiles;
305    jcr->JobBytes = jcr->SDJobBytes;
306    update_job_end(jcr, TermCode);
307
308    /* Update final items to set them to the previous job's values */
309    Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
310                "JobTDate=%s WHERE JobId=%s", 
311       jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime, 
312       edit_uint64(jcr->previous_jr.JobTDate, ec1),
313       edit_uint64(jcr->JobId, ec3));
314    db_sql_query(jcr->db, query.c_str(), NULL, NULL);
315
316    /* Get the fully updated job record */
317    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
318       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
319          db_strerror(jcr->db));
320       set_jcr_job_status(jcr, JS_ErrorTerminated);
321    }
322
323    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
324    if (!db_get_client_record(jcr, jcr->db, &cr)) {
325       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
326          db_strerror(jcr->db));
327    }
328
329    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
330    if (!db_get_media_record(jcr, jcr->db, &mr)) {
331       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
332          mr.VolumeName, db_strerror(jcr->db));
333       set_jcr_job_status(jcr, JS_ErrorTerminated);
334    }
335
336    update_bootstrap_file(jcr);
337
338    switch (jcr->JobStatus) {
339       case JS_Terminated:
340          if (jcr->Errors || jcr->SDErrors) {
341             term_msg = _("Backup OK -- with warnings");
342          } else {
343             term_msg = _("Backup OK");
344          }
345          break;
346       case JS_FatalError:
347       case JS_ErrorTerminated:
348          term_msg = _("*** Backup Error ***");
349          msg_type = M_ERROR;          /* Generate error message */
350          if (jcr->store_bsock) {
351             jcr->store_bsock->signal(BNET_TERMINATE);
352             if (jcr->SD_msg_chan) {
353                pthread_cancel(jcr->SD_msg_chan);
354             }
355          }
356          break;
357       case JS_Canceled:
358          term_msg = _("Backup Canceled");
359          if (jcr->store_bsock) {
360             jcr->store_bsock->signal(BNET_TERMINATE);
361             if (jcr->SD_msg_chan) {
362                pthread_cancel(jcr->SD_msg_chan);
363             }
364          }
365          break;
366       default:
367          term_msg = term_code;
368          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
369          break;
370    }
371    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
372    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
373    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
374    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
375    if (RunTime <= 0) {
376       kbps = 0;
377    } else {
378       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
379    }
380    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
381       /*
382        * Note, if the job has erred, most likely it did not write any
383        *  tape, so suppress this "error" message since in that case
384        *  it is normal.  Or look at it the other way, only for a
385        *  normal exit should we complain about this error.
386        */
387       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
388          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
389       }
390       jcr->VolumeName[0] = 0;         /* none */
391    }
392
393    if (jcr->ReadBytes == 0) {
394       bstrncpy(compress, "None", sizeof(compress));
395    } else {
396       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
397       if (compression < 0.5) {
398          bstrncpy(compress, "None", sizeof(compress));
399       } else {
400          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
401       }
402    }
403    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
404
405    Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
406 "  Build OS:               %s %s %s\n"
407 "  JobId:                  %d\n"
408 "  Job:                    %s\n"
409 "  Backup Level:           Virtual Full\n"
410 "  Client:                 \"%s\" %s\n"
411 "  FileSet:                \"%s\" %s\n"
412 "  Pool:                   \"%s\" (From %s)\n"
413 "  Catalog:                \"%s\" (From %s)\n"
414 "  Storage:                \"%s\" (From %s)\n"
415 "  Scheduled time:         %s\n"
416 "  Start time:             %s\n"
417 "  End time:               %s\n"
418 "  Elapsed time:           %s\n"
419 "  Priority:               %d\n"
420 "  SD Files Written:       %s\n"
421 "  SD Bytes Written:       %s (%sB)\n"
422 "  Rate:                   %.1f KB/s\n"
423 "  Volume name(s):         %s\n"
424 "  Volume Session Id:      %d\n"
425 "  Volume Session Time:    %d\n"
426 "  Last Volume Bytes:      %s (%sB)\n"
427 "  SD Errors:              %d\n"
428 "  SD termination status:  %s\n"
429 "  Termination:            %s\n\n"),
430         my_name, VERSION, LSMDATE, edt,
431         HOST_OS, DISTNAME, DISTVER,
432         jcr->jr.JobId,
433         jcr->jr.Job,
434         jcr->client->name(), cr.Uname,
435         jcr->fileset->name(), jcr->FSCreateTime,
436         jcr->pool->name(), jcr->pool_source,
437         jcr->catalog->name(), jcr->catalog_source,
438         jcr->wstore->name(), jcr->wstore_source,
439         schedt,
440         sdt,
441         edt,
442         edit_utime(RunTime, elapsed, sizeof(elapsed)),
443         jcr->JobPriority,
444         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
445         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
446         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
447         kbps,
448         jcr->VolumeName,
449         jcr->VolSessionId,
450         jcr->VolSessionTime,
451         edit_uint64_with_commas(mr.VolBytes, ec7),
452         edit_uint64_with_suffix(mr.VolBytes, ec8),
453         jcr->SDErrors,
454         sd_term_msg,
455         term_msg);
456
457    Dmsg0(100, "Leave vbackup_cleanup()\n");
458 }
459
460 /*
461  * This callback routine is responsible for inserting the
462  *  items it gets into the bootstrap structure. For each JobId selected
463  *  this routine is called once for each file. We do not allow
464  *  duplicate filenames, but instead keep the info from the most
465  *  recent file entered (i.e. the JobIds are assumed to be sorted)
466  *
467  *   See uar_sel_files in sql_cmds.c for query that calls us.
468  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
469  *      row[3]=JobId row[4]=LStat
470  */
471 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
472 {
473    JobId_t JobId;
474    int FileIndex;
475    RBSR *bsr = (RBSR *)ctx;
476
477    JobId = str_to_int64(row[3]);
478    FileIndex = str_to_int64(row[2]);
479    add_findex(bsr, JobId, FileIndex);
480    return 0;
481 }
482
483
484 static bool create_bootstrap_file(JCR *jcr, POOLMEM *jobids)
485 {
486    RESTORE_CTX rx;
487    UAContext *ua;
488
489    memset(&rx, 0, sizeof(rx));
490    rx.bsr = new_bsr();
491    ua = new_ua_context(jcr);
492    rx.JobIds = jobids;
493
494 #define new_get_file_list
495 #ifdef new_get_file_list
496    if (!db_get_file_list(jcr, ua->db, jobids, insert_bootstrap_handler, (void *)rx.bsr)) {
497       Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
498    }
499 #else
500    char *p;
501    JobId_t JobId, last_JobId = 0;
502    rx.query = get_pool_memory(PM_MESSAGE);
503    for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
504       char ed1[50];
505
506       if (JobId == last_JobId) {
507          continue;                    /* eliminate duplicate JobIds */
508       }
509       last_JobId = JobId;
510       /*
511        * Find files for this JobId and insert them in the tree
512        */
513       Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
514       Dmsg1(100, "uar_sel_files=%s\n", rx.query);
515       if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
516          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
517       }
518       free_pool_memory(rx.query);
519       rx.query = NULL;
520    }
521 #endif
522
523    complete_bsr(ua, rx.bsr);
524    jcr->ExpectedFiles = write_bsr_file(ua, rx);
525    if (debug_level >= 10) {
526       Dmsg1(000,  "Found %d files to consolidate.\n", jcr->ExpectedFiles);
527    }
528    if (jcr->ExpectedFiles == 0) {
529       free_ua_context(ua);
530       free_bsr(rx.bsr);
531       return false;
532    }
533    free_ua_context(ua);
534    free_bsr(rx.bsr);
535    return true;
536 }