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);
193 * Target jcr is the new Job that corresponds to the original
194 * target job. It "runs" at the same time as the current
195 * migration job and becomes a new backup job that replaces
196 * the original backup job. Most operations on the current
197 * migration jcr are also done on the target jcr.
199 tjcr = jcr->target_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
200 memcpy(&tjcr->target_jr, &jcr->target_jr, sizeof(tjcr->target_jr));
202 /* Turn the tjcr into a "real" job */
203 set_jcr_defaults(tjcr, tjob);
204 if (!setup_job(tjcr)) {
207 /* Set output PoolId and FileSetId. */
208 tjcr->jr.PoolId = jcr->jr.PoolId;
209 tjcr->jr.FileSetId = jcr->jr.FileSetId;
212 * Get the PoolId used with the original job. Then
213 * find the pool name from the database record.
215 memset(&pr, 0, sizeof(pr));
216 pr.PoolId = tjcr->target_jr.PoolId;
217 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
219 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
220 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
223 /* Get the pool resource corresponding to the original job */
224 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
226 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
230 /* Check Migration time and High/Low water marks */
233 /* If pool storage specified, use it for restore */
234 copy_storage(tjcr, pool->storage);
236 /* If the original backup pool has a NextPool, make sure a
237 * record exists in the database.
239 if (pool->NextPool) {
240 memset(&pr, 0, sizeof(pr));
241 bstrncpy(pr.Name, pool->NextPool->hdr.name, sizeof(pr.Name));
243 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
244 /* Try to create the pool */
245 if (create_pool(jcr, jcr->db, pool->NextPool, POOL_OP_CREATE) < 0) {
246 Jmsg(jcr, M_FATAL, 0, _("Pool \"%s\" not in database. %s"), pr.Name,
247 db_strerror(jcr->db));
250 Jmsg(jcr, M_INFO, 0, _("Pool \"%s\" created in database.\n"), pr.Name);
254 * put the "NextPool" resource pointer in our jcr so that we
255 * can pull the Storage reference from it.
257 tjcr->pool = jcr->pool = pool->NextPool;
258 tjcr->jr.PoolId = jcr->jr.PoolId = pr.PoolId;
261 /* If pool storage specified, use it instead of job storage for backup */
262 copy_storage(jcr, jcr->pool->storage);
264 /* Print Job Start message */
265 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
266 Type, edit_uint64(jcr->JobId, ed1), jcr->Job);
268 set_jcr_job_status(jcr, JS_Running);
269 set_jcr_job_status(jcr, JS_Running);
270 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
271 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
272 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
276 if (!db_update_job_start_record(tjcr, tjcr->db, &tjcr->jr)) {
277 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(tjcr->db));
283 * Open a message channel connection with the Storage
284 * daemon. This is to let him know that our client
285 * will be contacting him for a backup session.
288 Dmsg0(110, "Open connection with storage daemon\n");
289 set_jcr_job_status(jcr, JS_WaitSD);
290 set_jcr_job_status(tjcr, JS_WaitSD);
292 * Start conversation with Storage daemon
294 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
297 sd = jcr->store_bsock;
299 * Now start a job with the Storage daemon
301 Dmsg2(000, "Read store=%s, write store=%s\n",
302 ((STORE *)tjcr->storage->first())->hdr.name,
303 ((STORE *)jcr->storage->first())->hdr.name);
304 if (!start_storage_daemon_job(jcr, tjcr->storage, jcr->storage)) {
307 Dmsg0(150, "Storage daemon connection OK\n");
309 if (!send_bootstrap_file(jcr, sd) ||
310 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
316 * Now start a Storage daemon message thread
318 if (!start_storage_daemon_message_thread(jcr)) {
322 if (!bnet_fsend(sd, "run")) {
326 set_jcr_job_status(jcr, JS_Running);
327 set_jcr_job_status(tjcr, JS_Running);
329 /* Pickup Job termination data */
330 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
331 wait_for_storage_daemon_termination(jcr);
333 jcr->JobStatus = jcr->SDJobStatus;
334 if (jcr->JobStatus == JS_Terminated) {
335 mac_cleanup(jcr, jcr->JobStatus);
343 * Release resources allocated during backup.
345 void mac_cleanup(JCR *jcr, int TermCode)
347 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
348 char ec1[30], ec2[30], ec3[30], ec4[30], elapsed[50];
349 char term_code[100], sd_term_msg[100];
350 const char *term_msg;
356 JCR *tjcr = jcr->target_jcr;
357 POOL_MEM query(PM_MESSAGE);
359 switch(jcr->JobType) {
374 /* Ensure target is defined to avoid a lot of testing */
378 tjcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
379 tjcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
380 tjcr->VolSessionId = jcr->VolSessionId;
381 tjcr->VolSessionTime = jcr->VolSessionTime;
383 Dmsg2(100, "Enter mac_cleanup %d %c\n", TermCode, TermCode);
384 dequeue_messages(jcr); /* display any queued messages */
385 memset(&mr, 0, sizeof(mr));
386 set_jcr_job_status(jcr, TermCode);
387 set_jcr_job_status(tjcr, TermCode);
390 update_job_end_record(jcr); /* update database */
391 update_job_end_record(tjcr);
393 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
394 "JobTDate=%s WHERE JobId=%s",
395 jcr->target_jr.cStartTime, jcr->target_jr.cEndTime,
396 edit_uint64(jcr->target_jr.JobTDate, ec1),
397 edit_uint64(tjcr->jr.JobId, ec2));
398 db_sql_query(tjcr->db, query.c_str(), NULL, NULL);
400 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
401 Jmsg(jcr, M_WARNING, 0, _("Error getting job record for stats: %s"),
402 db_strerror(jcr->db));
403 set_jcr_job_status(jcr, JS_ErrorTerminated);
406 bstrncpy(mr.VolumeName, jcr->VolumeName, sizeof(mr.VolumeName));
407 if (!db_get_media_record(jcr, jcr->db, &mr)) {
408 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
409 mr.VolumeName, db_strerror(jcr->db));
410 set_jcr_job_status(jcr, JS_ErrorTerminated);
413 update_bootstrap_file(tjcr);
415 msg_type = M_INFO; /* by default INFO message */
416 switch (jcr->JobStatus) {
418 if (jcr->Errors || jcr->SDErrors) {
419 term_msg = _("%s OK -- with warnings");
421 term_msg = _("%s OK");
425 case JS_ErrorTerminated:
426 term_msg = _("*** %s Error ***");
427 msg_type = M_ERROR; /* Generate error message */
428 if (jcr->store_bsock) {
429 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
430 if (jcr->SD_msg_chan) {
431 pthread_cancel(jcr->SD_msg_chan);
436 term_msg = _("%s Canceled");
437 if (jcr->store_bsock) {
438 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
439 if (jcr->SD_msg_chan) {
440 pthread_cancel(jcr->SD_msg_chan);
445 term_msg = _("Inappropriate %s term code");
448 bsnprintf(term_code, sizeof(term_code), term_msg, Type);
449 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
450 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
451 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
455 kbps = (double)jcr->jr.JobBytes / (1000 * RunTime);
457 if (!db_get_job_volume_names(tjcr, tjcr->db, tjcr->jr.JobId, &tjcr->VolumeName)) {
459 * Note, if the job has erred, most likely it did not write any
460 * tape, so suppress this "error" message since in that case
461 * it is normal. Or look at it the other way, only for a
462 * normal exit should we complain about this error.
464 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
465 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(tjcr->db));
467 tjcr->VolumeName[0] = 0; /* none */
470 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
472 // bmicrosleep(15, 0); /* for debugging SIGHUP */
474 Jmsg(jcr, msg_type, 0, _("Bacula %s (%s): %s\n"
475 " Old Backup JobId: %u\n"
476 " New Backup JobId: %u\n"
479 " Backup Level: %s%s\n"
481 " FileSet: \"%s\" %s\n"
485 " Elapsed time: %s\n"
487 " SD Files Written: %s\n"
488 " SD Bytes Written: %s (%sB)\n"
490 " Volume name(s): %s\n"
491 " Volume Session Id: %d\n"
492 " Volume Session Time: %d\n"
493 " Last Volume Bytes: %s\n"
495 " SD termination status: %s\n"
496 " Termination: %s\n\n"),
500 jcr->target_jr.JobId,
504 level_to_str(jcr->JobLevel), jcr->since,
505 jcr->client->hdr.name,
506 jcr->fileset->hdr.name, jcr->FSCreateTime,
510 edit_utime(RunTime, elapsed, sizeof(elapsed)),
512 edit_uint64_with_commas(jcr->SDJobFiles, ec2),
513 edit_uint64_with_commas(jcr->SDJobBytes, ec3),
514 edit_uint64_with_suffix(jcr->jr.JobBytes, ec4),
519 edit_uint64_with_commas(mr.VolBytes, ec1),
524 Dmsg1(100, "Leave mac_cleanup() target_jcr=0x%x\n", jcr->target_jcr);
525 if (jcr->target_jcr) {
526 free_jcr(jcr->target_jcr);