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