3 * Bacula Director -- mac.c -- responsible for doing
4 * migration, archive, and copy jobs.
6 * Kern Sibbald, September MMIV
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
13 * When the Storage daemon finishes the job, update the DB.
18 Copyright (C) 2004-2006 Kern Sibbald
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.
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.
36 static char OKbootstrap[] = "3000 OK bootstrap\n";
39 * Called here before the job is run to do the job
42 bool do_mac_init(JCR *jcr)
48 switch(jcr->JobType) {
63 if (!get_or_create_fileset_record(jcr)) {
68 * Find JobId of last job that ran.
70 Name = jcr->job->migration_job->hdr.name;
71 Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
72 jcr->target_jr.JobType = JT_BACKUP;
73 if (!db_find_last_jobid(jcr, jcr->db, Name, &jcr->target_jr)) {
75 _("Previous job \"%s\" not found. ERR=%s\n"), Name,
76 db_strerror(jcr->db));
79 Dmsg1(100, "Last jobid=%d\n", jcr->target_jr.JobId);
81 if (!db_get_job_record(jcr, jcr->db, &jcr->target_jr)) {
82 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for previous Job. ERR=%s"),
83 db_strerror(jcr->db));
86 if (jcr->target_jr.JobStatus != 'T') {
87 Jmsg(jcr, M_FATAL, 0, _("Last Job %d did not terminate normally. JobStatus=%c\n"),
88 jcr->target_jr.JobId, jcr->target_jr.JobStatus);
91 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%d Job=%s\n"),
92 Type, jcr->target_jr.JobId, jcr->target_jr.Job);
96 * Get the Pool record -- first apply any level defined pools
98 switch (jcr->target_jr.JobLevel) {
100 if (jcr->full_pool) {
101 jcr->pool = jcr->full_pool;
106 jcr->pool = jcr->inc_pool;
111 jcr->pool = jcr->dif_pool;
115 memset(&pr, 0, sizeof(pr));
116 bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
118 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
119 /* Try to create the pool */
120 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
121 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
122 db_strerror(jcr->db));
125 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
128 jcr->jr.PoolId = pr.PoolId;
130 /* If pool storage specified, use it instead of job storage */
131 copy_storage(jcr, jcr->pool->storage);
134 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
138 if (!create_restore_bootstrap_file(jcr)) {
145 * Do a Migration, Archive, or Copy of a previous job
147 * Returns: false on failure
150 bool do_mac(JCR *jcr)
160 switch(jcr->JobType) {
176 Dmsg4(100, "Target: Name=%s JobId=%d Type=%c Level=%c\n",
177 jcr->target_jr.Name, jcr->target_jr.JobId,
178 jcr->target_jr.JobType, jcr->target_jr.JobLevel);
180 Dmsg4(100, "Current: Name=%s JobId=%d Type=%c Level=%c\n",
181 jcr->jr.Name, jcr->jr.JobId,
182 jcr->jr.JobType, jcr->jr.JobLevel);
185 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
186 tjob = (JOB *)GetResWithName(R_JOB, jcr->target_jr.Name);
192 tjcr = jcr->target_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
193 memcpy(&tjcr->target_jr, &jcr->target_jr, sizeof(tjcr->target_jr));
194 set_jcr_defaults(tjcr, tjob);
196 if (!setup_job(tjcr)) {
199 /* Set output PoolId and FileSetId. */
200 tjcr->jr.PoolId = jcr->jr.PoolId;
201 tjcr->jr.FileSetId = jcr->jr.FileSetId;
204 * Get the PoolId used with the original job. Then
205 * find the pool name from the database record.
207 memset(&pr, 0, sizeof(pr));
208 pr.PoolId = tjcr->target_jr.PoolId;
209 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
211 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
212 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
215 /* Get the pool resource corresponding to the original job */
216 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
218 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
222 /* If pool storage specified, use it for restore */
223 copy_storage(tjcr, pool->storage);
225 /* If the original backup pool has a NextPool, make sure a
226 * record exists in the database.
228 if (pool->NextPool) {
229 memset(&pr, 0, sizeof(pr));
230 bstrncpy(pr.Name, pool->NextPool->hdr.name, sizeof(pr.Name));
232 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
233 /* Try to create the pool */
234 if (create_pool(jcr, jcr->db, pool->NextPool, POOL_OP_CREATE) < 0) {
235 Jmsg(jcr, M_FATAL, 0, _("Pool \"%s\" not in database. %s"), pr.Name,
236 db_strerror(jcr->db));
239 Jmsg(jcr, M_INFO, 0, _("Pool \"%s\" created in database.\n"), pr.Name);
243 * put the "NextPool" resource pointer in our jcr so that we
244 * can pull the Storage reference from it.
246 tjcr->pool = jcr->pool = pool->NextPool;
247 tjcr->jr.PoolId = jcr->jr.PoolId = pr.PoolId;
250 /* If pool storage specified, use it instead of job storage for backup */
251 copy_storage(jcr, jcr->pool->storage);
253 /* Print Job Start message */
254 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
255 Type, edit_uint64(jcr->JobId, ed1), jcr->Job);
257 set_jcr_job_status(jcr, JS_Running);
258 set_jcr_job_status(jcr, JS_Running);
259 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
260 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
261 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
265 if (!db_update_job_start_record(tjcr, tjcr->db, &tjcr->jr)) {
266 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(tjcr->db));
272 * Open a message channel connection with the Storage
273 * daemon. This is to let him know that our client
274 * will be contacting him for a backup session.
277 Dmsg0(110, "Open connection with storage daemon\n");
278 set_jcr_job_status(jcr, JS_WaitSD);
279 set_jcr_job_status(tjcr, JS_WaitSD);
281 * Start conversation with Storage daemon
283 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
286 sd = jcr->store_bsock;
288 * Now start a job with the Storage daemon
290 Dmsg2(000, "Read store=%s, write store=%s\n",
291 ((STORE *)tjcr->storage->first())->hdr.name,
292 ((STORE *)jcr->storage->first())->hdr.name);
293 if (!start_storage_daemon_job(jcr, tjcr->storage, jcr->storage)) {
296 Dmsg0(150, "Storage daemon connection OK\n");
298 if (!send_bootstrap_file(jcr, sd) ||
299 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
305 * Now start a Storage daemon message thread
307 if (!start_storage_daemon_message_thread(jcr)) {
311 if (!bnet_fsend(sd, "run")) {
315 set_jcr_job_status(jcr, JS_Running);
316 set_jcr_job_status(tjcr, JS_Running);
318 /* Pickup Job termination data */
319 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
320 wait_for_storage_daemon_termination(jcr);
322 jcr->JobStatus = jcr->SDJobStatus;
323 if (jcr->JobStatus == JS_Terminated) {
324 mac_cleanup(jcr, jcr->JobStatus);
332 * Release resources allocated during backup.
334 void mac_cleanup(JCR *jcr, int TermCode)
336 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
337 char ec1[30], ec2[30], ec3[30], ec4[30], elapsed[50];
338 char term_code[100], sd_term_msg[100];
339 const char *term_msg;
345 JCR *tjcr = jcr->target_jcr;
346 POOL_MEM query(PM_MESSAGE);
348 switch(jcr->JobType) {
363 /* Ensure target is defined to avoid a lot of testing */
367 tjcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
368 tjcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
369 tjcr->VolSessionId = jcr->VolSessionId;
370 tjcr->VolSessionTime = jcr->VolSessionTime;
372 Dmsg2(100, "Enter mac_cleanup %d %c\n", TermCode, TermCode);
373 dequeue_messages(jcr); /* display any queued messages */
374 memset(&mr, 0, sizeof(mr));
375 set_jcr_job_status(jcr, TermCode);
376 set_jcr_job_status(tjcr, TermCode);
379 update_job_end_record(jcr); /* update database */
380 update_job_end_record(tjcr);
382 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
383 "JobTDate=%s WHERE JobId=%s",
384 jcr->target_jr.cStartTime, jcr->target_jr.cEndTime,
385 edit_uint64(jcr->target_jr.JobTDate, ec1),
386 edit_uint64(tjcr->jr.JobId, ec2));
387 db_sql_query(tjcr->db, query.c_str(), NULL, NULL);
389 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
390 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
391 db_strerror(jcr->db));
392 set_jcr_job_status(jcr, JS_ErrorTerminated);
395 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
396 if (!db_get_media_record(jcr, jcr->db, &mr)) {
397 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
398 mr.VolumeName, db_strerror(jcr->db));
399 set_jcr_job_status(jcr, JS_ErrorTerminated);
402 update_bootstrap_file(tjcr);
404 msg_type = M_INFO; /* by default INFO message */
405 switch (jcr->JobStatus) {
407 if (jcr->Errors || jcr->SDErrors) {
408 term_msg = _("%s OK -- with warnings");
410 term_msg = _("%s OK");
414 case JS_ErrorTerminated:
415 term_msg = _("*** %s Error ***");
416 msg_type = M_ERROR; /* Generate error message */
417 if (jcr->store_bsock) {
418 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
419 if (jcr->SD_msg_chan) {
420 pthread_cancel(jcr->SD_msg_chan);
425 term_msg = _("%s Canceled");
426 if (jcr->store_bsock) {
427 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
428 if (jcr->SD_msg_chan) {
429 pthread_cancel(jcr->SD_msg_chan);
434 term_msg = _("Inappropriate %s term code");
437 bsnprintf(term_code, sizeof(term_code), term_msg, Type);
438 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
439 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
440 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
444 kbps = (double)jcr->jr.JobBytes / (1000 * RunTime);
446 if (!db_get_job_volume_names(tjcr, tjcr->db, tjcr->jr.JobId, &tjcr->VolumeName)) {
448 * Note, if the job has erred, most likely it did not write any
449 * tape, so suppress this "error" message since in that case
450 * it is normal. Or look at it the other way, only for a
451 * normal exit should we complain about this error.
453 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
454 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(tjcr->db));
456 tjcr->VolumeName[0] = 0; /* none */
459 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
461 // bmicrosleep(15, 0); /* for debugging SIGHUP */
463 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
464 " Old Backup JobId: %u\n"
465 " New Backup JobId: %u\n"
468 " Backup Level: %s%s\n"
470 " FileSet: \"%s\" %s\n"
474 " Elapsed time: %s\n"
476 " SD Files Written: %s\n"
477 " SD Bytes Written: %s (%sB)\n"
479 " Volume name(s): %s\n"
480 " Volume Session Id: %d\n"
481 " Volume Session Time: %d\n"
482 " Last Volume Bytes: %s\n"
484 " SD termination status: %s\n"
485 " Termination: %s\n\n"),
489 jcr->target_jr.JobId,
493 level_to_str(jcr->JobLevel), jcr->since,
494 jcr->client->hdr.name,
495 jcr->fileset->hdr.name, jcr->FSCreateTime,
499 edit_utime(RunTime, elapsed, sizeof(elapsed)),
501 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
502 edit_uint64_with_commas(jcr->SDJobBytes, ec3),
503 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
508 edit_uint64_with_commas(mr.VolBytes, ec1),
513 Dmsg1(100, "Leave mac_cleanup() target_jcr=0x%x\n", jcr->target_jcr);
514 if (jcr->target_jcr) {
515 free_jcr(jcr->target_jcr);