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