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