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