]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/mac.c
- Continue implementing migration.
[bacula/bacula] / bacula / src / dird / mac.c
1 /*
2  *
3  *   Bacula Director -- mac.c -- responsible for doing
4  *     migration, archive, and copy jobs.
5  *
6  *     Kern Sibbald, September MMIV
7  *
8  *  Basic tasks done here:
9  *     Open DB and create records for this job.
10  *     Open Message Channel with Storage daemon to tell him a job will be starting.
11  *     Open connection with Storage daemon and pass him commands
12  *       to do the backup.
13  *     When the Storage daemon finishes the job, update the DB.
14  *
15  *   Version $Id$
16  */
17 /*
18    Copyright (C) 2004-2006 Kern Sibbald
19
20    This program is free software; you can redistribute it and/or
21    modify it under the terms of the GNU General Public License
22    version 2 as amended with additional clauses defined in the
23    file LICENSE in the main source directory.
24
25    This program is distributed in the hope that it will be useful,
26    but WITHOUT ANY WARRANTY; without even the implied warranty of
27    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
28    the file LICENSE for additional details.
29
30  */
31
32 #include "bacula.h"
33 #include "dird.h"
34 #include "ua.h"
35
36 static char OKbootstrap[] = "3000 OK bootstrap\n";
37
38 /* 
39  * Called here before the job is run to do the job
40  *   specific setup.
41  */
42 bool do_mac_init(JCR *jcr)
43 {
44    POOL_DBR pr;
45    JOB_DBR jr;
46    JobId_t input_jobid;
47    char *Name;
48    RESTORE_CTX rx;
49    UAContext *ua;
50    const char *Type;
51
52    switch(jcr->JobType) {
53    case JT_MIGRATE:
54       Type = "Migration";
55       break;
56    case JT_ARCHIVE:
57       Type = "Archive";
58       break;
59    case JT_COPY:
60       Type = "Copy";
61       break;
62    default:
63       Type = "Unknown";
64       break;
65    }
66
67    if (!get_or_create_fileset_record(jcr)) {
68       return false;
69    }
70
71    /*
72     * Find JobId of last job that ran.
73     */
74    memcpy(&jr, &jcr->jr, sizeof(jr));
75    Name = jcr->job->migration_job->hdr.name;
76    Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
77    if (!db_find_last_jobid(jcr, jcr->db, Name, &jr)) {
78       Jmsg(jcr, M_FATAL, 0, _(
79            _("Unable to find JobId of previous Job for this client.\n")));
80       return false;
81    }
82    input_jobid = jr.JobId;
83    Dmsg1(100, "Last jobid=%d\n", input_jobid);
84
85    jcr->target_jr.JobId = input_jobid;
86    if (!db_get_job_record(jcr, jcr->db, &jcr->target_jr)) {
87       Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
88            db_strerror(jcr->db));
89       return false;
90    }
91    if (jcr->target_jr.JobStatus != 'T') {
92       Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
93          input_jobid, jcr->target_jr.JobStatus);
94       return false;
95    }
96    Jmsg(jcr, M_INFO, 0, _("%s using JobId=%d Job=%s\n"),
97       Type, jcr->target_jr.JobId, jcr->target_jr.Job);
98
99
100    /*
101     * Get the Pool record -- first apply any level defined pools
102     */
103    switch (jcr->target_jr.JobLevel) {
104    case L_FULL:
105       if (jcr->full_pool) {
106          jcr->pool = jcr->full_pool;
107       }
108       break;
109    case L_INCREMENTAL:
110       if (jcr->inc_pool) {
111          jcr->pool = jcr->inc_pool;
112       }
113       break;
114    case L_DIFFERENTIAL:
115       if (jcr->dif_pool) {
116          jcr->pool = jcr->dif_pool;
117       }
118       break;
119    }
120    memset(&pr, 0, sizeof(pr));
121    bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
122
123    while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
124       /* Try to create the pool */
125       if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
126          Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
127             db_strerror(jcr->db));
128          return false;
129       } else {
130          Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
131       }
132    }
133    jcr->PoolId = pr.PoolId;               /****FIXME**** this can go away */
134    jcr->jr.PoolId = pr.PoolId;
135
136    memset(&rx, 0, sizeof(rx));
137    rx.bsr = new_bsr();
138    rx.JobIds = "";                       
139    rx.bsr->JobId = jcr->target_jr.JobId;
140    ua = new_ua_context(jcr);
141    complete_bsr(ua, rx.bsr);
142    rx.bsr->fi = new_findex();
143    rx.bsr->fi->findex = 1;
144    rx.bsr->fi->findex2 = jcr->target_jr.JobFiles;
145    jcr->ExpectedFiles = write_bsr_file(ua, rx);
146    if (jcr->ExpectedFiles == 0) {
147       free_ua_context(ua);
148       free_bsr(rx.bsr);
149       return false;
150    }
151    if (jcr->RestoreBootstrap) {
152       free(jcr->RestoreBootstrap);
153    }
154    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
155    make_unique_restore_filename(ua, &fname);
156    jcr->RestoreBootstrap = bstrdup(fname);
157    free_ua_context(ua);
158    free_bsr(rx.bsr);
159    free_pool_memory(fname);
160
161    jcr->needs_sd = true;
162    return true;
163 }
164
165 /*
166  * Do a Migration, Archive, or Copy of a previous job
167  *
168  *  Returns:  false on failure
169  *            true  on success
170  */
171 bool do_mac(JCR *jcr)
172 {
173    int stat;
174    const char *Type;
175    char ed1[100];
176    BSOCK *sd;
177
178    switch(jcr->JobType) {
179    case JT_MIGRATE:
180       Type = "Migration";
181       break;
182    case JT_ARCHIVE:
183       Type = "Archive";
184       break;
185    case JT_COPY:
186       Type = "Copy";
187       break;
188    default:
189       Type = "Unknown";
190       break;
191    }
192
193
194    /* Print Job Start message */
195    Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
196         Type, edit_uint64(jcr->JobId, ed1), jcr->Job);
197
198    set_jcr_job_status(jcr, JS_Running);
199    Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
200    if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
201       Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
202       return false;
203    }
204
205    /*
206     * Open a message channel connection with the Storage
207     * daemon. This is to let him know that our client
208     * will be contacting him for a backup  session.
209     *
210     */
211    Dmsg0(110, "Open connection with storage daemon\n");
212    set_jcr_job_status(jcr, JS_WaitSD);
213    /*
214     * Start conversation with Storage daemon
215     */
216    if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
217       return false;
218    }
219    sd = jcr->store_bsock;
220    /*
221     * Now start a job with the Storage daemon
222     */
223    if (!start_storage_daemon_job(jcr, jcr->storage, NULL)) {
224       return false;
225    }
226    Dmsg0(150, "Storage daemon connection OK\n");
227
228    if (!send_bootstrap_file(jcr, sd) ||
229        !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
230       return false;
231    }
232
233
234    /*
235     * Now start a Storage daemon message thread
236     */
237    if (!start_storage_daemon_message_thread(jcr)) {
238       return false;
239    }
240
241    if (!bnet_fsend(sd, "run")) {
242       return false;
243    }
244
245    /* Pickup Job termination data */
246    set_jcr_job_status(jcr, JS_Running);
247
248    /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
249    wait_for_storage_daemon_termination(jcr);
250
251    if (jcr->JobStatus != JS_Terminated) {
252       stat = jcr->JobStatus;
253    } else {
254       stat = jcr->SDJobStatus;
255    }
256    if (stat == JS_Terminated) {
257       mac_cleanup(jcr, stat);
258       return true;
259    }
260    return false;
261 }
262
263
264 /*
265  * Release resources allocated during backup.
266  */
267 void mac_cleanup(JCR *jcr, int TermCode)
268 {
269    char sdt[50], edt[50];
270    char ec3[30], ec4[30], ec5[30], compress[50];
271    char term_code[100], sd_term_msg[100];
272    const char *term_msg;
273    int msg_type;
274    MEDIA_DBR mr;
275    double kbps, compression;
276    utime_t RunTime;
277    const char *Type;
278
279    switch(jcr->JobType) {
280    case JT_MIGRATE:
281       Type = "Migration";
282       break;
283    case JT_ARCHIVE:
284       Type = "Archive";
285       break;
286    case JT_COPY:
287       Type = "Copy";
288       break;
289    default:
290       Type = "Unknown";
291       break;
292    }
293
294    Dmsg2(100, "Enter mac_cleanup %d %c\n", TermCode, TermCode);
295    dequeue_messages(jcr);             /* display any queued messages */
296    memset(&mr, 0, sizeof(mr));
297    set_jcr_job_status(jcr, TermCode);
298
299    update_job_end_record(jcr);        /* update database */
300
301    if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
302       Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
303          db_strerror(jcr->db));
304       set_jcr_job_status(jcr, JS_ErrorTerminated);
305    }
306
307    bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
308    if (!db_get_media_record(jcr, jcr->db, &mr)) {
309       Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
310          mr.VolumeName, db_strerror(jcr->db));
311       set_jcr_job_status(jcr, JS_ErrorTerminated);
312    }
313
314    /* Now update the bootstrap file if any */
315    if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes &&
316        jcr->job->WriteBootstrap) {
317       FILE *fd;
318       BPIPE *bpipe = NULL;
319       int got_pipe = 0;
320       char *fname = jcr->job->WriteBootstrap;
321       VOL_PARAMS *VolParams = NULL;
322       int VolCount;
323
324       if (*fname == '|') {
325          fname++;
326          got_pipe = 1;
327          bpipe = open_bpipe(fname, 0, "w");
328          fd = bpipe ? bpipe->wfd : NULL;
329       } else {
330          /* ***FIXME*** handle BASE */
331          fd = fopen(fname, jcr->JobLevel==L_FULL?"w+":"a+");
332       }
333       if (fd) {
334          VolCount = db_get_job_volume_parameters(jcr, jcr->db, jcr->JobId,
335                     &VolParams);
336          if (VolCount == 0) {
337             Jmsg(jcr, M_ERROR, 0, _("Could not get Job Volume Parameters to "
338                  "update Bootstrap file. ERR=%s\n"), db_strerror(jcr->db));
339              if (jcr->SDJobFiles != 0) {
340                 set_jcr_job_status(jcr, JS_ErrorTerminated);
341              }
342
343          }
344          for (int i=0; i < VolCount; i++) {
345             /* Write the record */
346             fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
347             fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
348             fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
349             fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
350             fprintf(fd, "VolFile=%u-%u\n", VolParams[i].StartFile,
351                          VolParams[i].EndFile);
352             fprintf(fd, "VolBlock=%u-%u\n", VolParams[i].StartBlock,
353                          VolParams[i].EndBlock);
354             fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
355                          VolParams[i].LastIndex);
356          }
357          if (VolParams) {
358             free(VolParams);
359          }
360          if (got_pipe) {
361             close_bpipe(bpipe);
362          } else {
363             fclose(fd);
364          }
365       } else {
366          berrno be;
367          Jmsg(jcr, M_ERROR, 0, _("Could not open WriteBootstrap file:\n"
368               "%s: ERR=%s\n"), fname, be.strerror());
369          set_jcr_job_status(jcr, JS_ErrorTerminated);
370       }
371    }
372
373    msg_type = M_INFO;                 /* by default INFO message */
374    switch (jcr->JobStatus) {
375       case JS_Terminated:
376          if (jcr->Errors || jcr->SDErrors) {
377             term_msg = _("%s OK -- with warnings");
378          } else {
379             term_msg = _("%s OK");
380          }
381          break;
382       case JS_FatalError:
383       case JS_ErrorTerminated:
384          term_msg = _("*** %s Error ***");
385          msg_type = M_ERROR;          /* Generate error message */
386          if (jcr->store_bsock) {
387             bnet_sig(jcr->store_bsock, BNET_TERMINATE);
388             if (jcr->SD_msg_chan) {
389                pthread_cancel(jcr->SD_msg_chan);
390             }
391          }
392          break;
393       case JS_Canceled:
394          term_msg = _("%s Canceled");
395          if (jcr->store_bsock) {
396             bnet_sig(jcr->store_bsock, BNET_TERMINATE);
397             if (jcr->SD_msg_chan) {
398                pthread_cancel(jcr->SD_msg_chan);
399             }
400          }
401          break;
402       default:
403          term_msg = _("Inappropriate %s term code");
404          break;
405    }
406    bsnprintf(term_code, sizeof(term_code), term_msg, Type);
407    bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
408    bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
409    RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
410    if (RunTime <= 0) {
411       kbps = 0;
412    } else {
413       kbps = (double)jcr->jr.JobBytes / (1000 * RunTime);
414    }
415    if (!db_get_job_volume_names(jcr, jcr->db, jcr->jr.JobId, &jcr->VolumeName)) {
416       /*
417        * Note, if the job has erred, most likely it did not write any
418        *  tape, so suppress this "error" message since in that case
419        *  it is normal.  Or look at it the other way, only for a
420        *  normal exit should we complain about this error.
421        */
422       if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
423          Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(jcr->db));
424       }
425       jcr->VolumeName[0] = 0;         /* none */
426    }
427
428    if (jcr->ReadBytes == 0) {
429       bstrncpy(compress, "None", sizeof(compress));
430    } else {
431       compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
432       if (compression < 0.5) {
433          bstrncpy(compress, "None", sizeof(compress));
434       } else {
435          bsnprintf(compress, sizeof(compress), "%.1f %%", (float)compression);
436       }
437    }
438    jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
439
440 // bmicrosleep(15, 0);                /* for debugging SIGHUP */
441
442    Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
443 "  JobId:                  %d\n"
444 "  Job:                    %s\n"
445 "  Backup Level:           %s%s\n"
446 "  Client:                 %s\n"
447 "  FileSet:                \"%s\" %s\n"
448 "  Pool:                   \"%s\"\n"
449 "  Start time:             %s\n"
450 "  End time:               %s\n"
451 "  SD Files Written:       %s\n"
452 "  SD Bytes Written:       %s\n"
453 "  Rate:                   %.1f KB/s\n"
454 "  Software Compression:   %s\n"
455 "  Volume name(s):         %s\n"
456 "  Volume Session Id:      %d\n"
457 "  Volume Session Time:    %d\n"
458 "  Last Volume Bytes:      %s\n"
459 "  SD Errors:              %d\n"
460 "  SD termination status:  %s\n"
461 "  Termination:            %s\n\n"),
462    VERSION,
463    LSMDATE,
464         edt,
465         jcr->jr.JobId,
466         jcr->jr.Job,
467         level_to_str(jcr->JobLevel), jcr->since,
468         jcr->client->hdr.name,
469         jcr->fileset->hdr.name, jcr->FSCreateTime,
470         jcr->pool->hdr.name,
471         sdt,
472         edt,
473         edit_uint64_with_commas(jcr->SDJobFiles, ec4),
474         edit_uint64_with_commas(jcr->SDJobBytes, ec5),
475         (float)kbps,
476         compress,
477         jcr->VolumeName,
478         jcr->VolSessionId,
479         jcr->VolSessionTime,
480         edit_uint64_with_commas(mr.VolBytes, ec3),
481         jcr->SDErrors,
482         sd_term_msg,
483         term_code);
484
485    Dmsg0(100, "Leave mac_cleanup()\n");
486 }