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