]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/vbackup.c
07ec06c9741821a6963384d2186d4fa660afdce1
[bacula/bacula] / bacula / src / dird / vbackup.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2016 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    /* Add the following when support for base jobs is added to virtual full */
288    //jcr->HasBase = jcr->job->base != NULL;
289    //jcr->jr.HasBase = jcr->HasBase;
290
291    /* Update job start record */
292    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
293       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
294       return false;
295    }
296
297    /* Declare the job started to start the MaxRunTime check */
298    jcr->setJobStarted();
299
300    /*
301     * Start the job prior to starting the message thread below
302     * to avoid two threads from using the BSOCK structure at
303     * the same time.
304     */
305    if (!sd->fsend("run")) {
306       return false;
307    }
308
309    /*
310     * Now start a Storage daemon message thread
311     */
312    if (!start_storage_daemon_message_thread(jcr)) {
313       return false;
314    }
315
316    jcr->setJobStatus(JS_Running);
317
318    /* Pickup Job termination data */
319    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
320    wait_for_storage_daemon_termination(jcr);
321    jcr->setJobStatus(jcr->SDJobStatus);
322    db_write_batch_file_records(jcr);    /* used by bulk batch file insert */
323    if (jcr->JobStatus != JS_Terminated) {
324       return false;
325    }
326
327    vbackup_cleanup(jcr, jcr->JobStatus);
328    return true;
329 }
330
331
332 /*
333  * Release resources allocated during backup.
334  */
335 void vbackup_cleanup(JCR *jcr, int TermCode)
336 {
337    char sdt[50], edt[50], schedt[50];
338    char ec1[30], ec3[30], ec4[30], compress[50];
339    char ec7[30], ec8[30], elapsed[50];
340    char term_code[100], sd_term_msg[100];
341    const char *term_msg;
342    int msg_type = M_INFO;
343    MEDIA_DBR mr;
344    CLIENT_DBR cr;
345    double kbps, compression;
346    utime_t RunTime;
347    POOL_MEM query(PM_MESSAGE);
348
349    Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
350    memset(&cr, 0, sizeof(cr));
351
352    jcr->setJobLevel(L_FULL);         /* we want this to appear as a Full backup */
353    jcr->jr.JobLevel = L_FULL;         /* we want this to appear as a Full backup */
354    jcr->JobFiles = jcr->SDJobFiles;
355    jcr->JobBytes = jcr->SDJobBytes;
356    update_job_end(jcr, TermCode);
357
358    /* Update final items to set them to the previous job's values */
359    Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
360                "JobTDate=%s WHERE JobId=%s",
361       jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
362       edit_uint64(jcr->previous_jr.JobTDate, ec1),
363       edit_uint64(jcr->JobId, ec3));
364    db_sql_query(jcr->db, query.c_str(), NULL, NULL);
365
366    /* Get the fully updated job record */
367    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
368       Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
369          db_strerror(jcr->db));
370       jcr->setJobStatus(JS_ErrorTerminated);
371    }
372
373    bstrncpy(cr.Name, jcr->client->name(), sizeof(cr.Name));
374    if (!db_get_client_record(jcr, jcr->db, &cr)) {
375       Jmsg(jcr, M_WARNING, 0, _("Error getting Client record for Job report: ERR=%s"),
376          db_strerror(jcr->db));
377    }
378
379    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
380    if (!db_get_media_record(jcr, jcr->db, &mr)) {
381       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
382          mr.VolumeName, db_strerror(jcr->db));
383       jcr->setJobStatus(JS_ErrorTerminated);
384    }
385
386    update_bootstrap_file(jcr);
387
388    switch (jcr->JobStatus) {
389       case JS_Terminated:
390          if (jcr->JobErrors || jcr->SDErrors) {
391             term_msg = _("Backup OK -- with warnings");
392          } else {
393             term_msg = _("Backup OK");
394          }
395          break;
396       case JS_FatalError:
397       case JS_ErrorTerminated:
398          term_msg = _("*** Backup Error ***");
399          msg_type = M_ERROR;          /* Generate error message */
400          if (jcr->store_bsock) {
401             jcr->store_bsock->signal(BNET_TERMINATE);
402             if (jcr->SD_msg_chan_started) {
403                pthread_cancel(jcr->SD_msg_chan);
404             }
405          }
406          break;
407       case JS_Canceled:
408          term_msg = _("Backup Canceled");
409          if (jcr->store_bsock) {
410             jcr->store_bsock->signal(BNET_TERMINATE);
411             if (jcr->SD_msg_chan_started) {
412                pthread_cancel(jcr->SD_msg_chan);
413             }
414          }
415          break;
416       default:
417          term_msg = term_code;
418          sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
419          break;
420    }
421    bstrftimes(schedt, sizeof(schedt), jcr->jr.SchedTime);
422    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
423    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
424    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
425    if (RunTime <= 0) {
426       kbps = 0;
427    } else {
428       kbps = ((double)jcr->jr.JobBytes) / (1000.0 * (double)RunTime);
429    }
430    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
431       /*
432        * Note, if the job has erred, most likely it did not write any
433        *  tape, so suppress this "error" message since in that case
434        *  it is normal.  Or look at it the other way, only for a
435        *  normal exit should we complain about this error.
436        */
437       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
438          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
439       }
440       jcr->VolumeName[0] = 0;         /* none */
441    }
442
443    if (jcr->ReadBytes == 0) {
444       bstrncpy(compress, "None", sizeof(compress));
445    } else {
446       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
447       if (compression < 0.5) {
448          bstrncpy(compress, "None", sizeof(compress));
449       } else {
450          bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
451       }
452    }
453    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
454
455    Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
456 "  Build OS:               %s %s %s\n"
457 "  JobId:                  %d\n"
458 "  Job:                    %s\n"
459 "  Backup Level:           Virtual Full\n"
460 "  Client:                 \"%s\" %s\n"
461 "  FileSet:                \"%s\" %s\n"
462 "  Pool:                   \"%s\" (From %s)\n"
463 "  Catalog:                \"%s\" (From %s)\n"
464 "  Storage:                \"%s\" (From %s)\n"
465 "  Scheduled time:         %s\n"
466 "  Start time:             %s\n"
467 "  End time:               %s\n"
468 "  Elapsed time:           %s\n"
469 "  Priority:               %d\n"
470 "  SD Files Written:       %s\n"
471 "  SD Bytes Written:       %s (%sB)\n"
472 "  Rate:                   %.1f KB/s\n"
473 "  Volume name(s):         %s\n"
474 "  Volume Session Id:      %d\n"
475 "  Volume Session Time:    %d\n"
476 "  Last Volume Bytes:      %s (%sB)\n"
477 "  SD Errors:              %d\n"
478 "  SD termination status:  %s\n"
479 "  Termination:            %s\n\n"),
480         BACULA, my_name, VERSION, LSMDATE,
481         HOST_OS, DISTNAME, DISTVER,
482         jcr->jr.JobId,
483         jcr->jr.Job,
484         jcr->client->name(), cr.Uname,
485         jcr->fileset->name(), jcr->FSCreateTime,
486         jcr->pool->name(), jcr->pool_source,
487         jcr->catalog->name(), jcr->catalog_source,
488         jcr->wstore->name(), jcr->wstore_source,
489         schedt,
490         sdt,
491         edt,
492         edit_utime(RunTime, elapsed, sizeof(elapsed)),
493         jcr->JobPriority,
494         edit_uint64_with_commas(jcr->jr.JobFiles, ec1),
495         edit_uint64_with_commas(jcr->jr.JobBytes, ec3),
496         edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
497         kbps,
498         jcr->VolumeName,
499         jcr->VolSessionId,
500         jcr->VolSessionTime,
501         edit_uint64_with_commas(mr.VolBytes, ec7),
502         edit_uint64_with_suffix(mr.VolBytes, ec8),
503         jcr->SDErrors,
504         sd_term_msg,
505         term_msg);
506
507    Dmsg0(100, "Leave vbackup_cleanup()\n");
508 }
509
510 /*
511  * This callback routine is responsible for inserting the
512  *  items it gets into the bootstrap structure. For each JobId selected
513  *  this routine is called once for each file. We do not allow
514  *  duplicate filenames, but instead keep the info from the most
515  *  recent file entered (i.e. the JobIds are assumed to be sorted)
516  *
517  *   See uar_sel_files in sql_cmds.c for query that calls us.
518  *      row[0]=Path, row[1]=Filename, row[2]=FileIndex
519  *      row[3]=JobId row[4]=LStat
520  */
521 int insert_bootstrap_handler(void *ctx, int num_fields, char **row)
522 {
523    JobId_t JobId;
524    int FileIndex;
525    RBSR *bsr = (RBSR *)ctx;
526
527    JobId = str_to_int64(row[3]);
528    FileIndex = str_to_int64(row[2]);
529    add_findex(bsr, JobId, FileIndex);
530    return 0;
531 }
532
533
534 static bool create_bootstrap_file(JCR *jcr, char *jobids)
535 {
536    RESTORE_CTX rx;
537    UAContext *ua;
538
539    memset(&rx, 0, sizeof(rx));
540    rx.bsr = new_bsr();
541    ua = new_ua_context(jcr);
542    rx.JobIds = jobids;
543
544 #define new_get_file_list
545 #ifdef new_get_file_list
546    if (!db_open_batch_connexion(jcr, jcr->db)) {
547       Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connexion");
548       return false;
549    }
550
551    if (!db_get_file_list(jcr, jcr->db_batch, jobids, false /* don't use md5 */,
552                          true /* use delta */,
553                          insert_bootstrap_handler, (void *)rx.bsr))
554    {
555       Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db_batch));
556    }
557 #else
558    char *p;
559    JobId_t JobId, last_JobId = 0;
560    rx.query = get_pool_memory(PM_MESSAGE);
561    for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
562       char ed1[50];
563
564       if (JobId == last_JobId) {
565          continue;                    /* eliminate duplicate JobIds */
566       }
567       last_JobId = JobId;
568       /*
569        * Find files for this JobId and insert them in the tree
570        */
571       Mmsg(rx.query, uar_sel_files, edit_int64(JobId, ed1));
572       Dmsg1(100, "uar_sel_files=%s\n", rx.query);
573       if (!db_sql_query(ua->db, rx.query, insert_bootstrap_handler, (void *)rx.bsr)) {
574          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(ua->db));
575       }
576       free_pool_memory(rx.query);
577       rx.query = NULL;
578    }
579 #endif
580
581    complete_bsr(ua, rx.bsr);
582    jcr->ExpectedFiles = write_bsr_file(ua, rx);
583    Jmsg(jcr, M_INFO, 0, _("Found %d files to consolidate into Virtual Full.\n"),
584         jcr->ExpectedFiles);
585    free_ua_context(ua);
586    free_bsr(rx.bsr);
587    return jcr->ExpectedFiles==0?false:true;
588 }