1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 *
21 * Bacula Director -- mac.c -- responsible for doing
22 * migration and copy jobs.
23 *
24 * Also handles Copy jobs (March MMVIII)
25 *
26 * Kern Sibbald, September MMIV
27 *
28 * Basic tasks done here:
29 * Open DB and create records for this job.
30 * Open Message Channel with Storage daemon to tell him a job will be starting.
31 * Open connection with Storage daemon and pass him commands
32 * to do the backup.
33 * When the Storage daemon finishes the job, update the DB.
34 *
35 */
36
37 #include "bacula.h"
38 #include "dird.h"
39 #include "ua.h"
40 #ifndef HAVE_REGEX_H
41 #include "lib/bregex.h"
42 #else
43 #include <regex.h>
44 #endif
45
46 struct uitem {
47 dlink link;
48 char *item;
49 };
50
51 /* Imported functions */
52 extern void start_mac_job(JCR*);
53
54 static const int dbglevel = 10;
55
56 /* Forware referenced functions */
57 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
58 const char *type);
59 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
60 const char *query2, const char *type);
61 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
62 static int unique_dbid_handler(void *ctx, int num_fields, char **row);
63 static int unique_name_handler(void *ctx, int num_fields, char **row);
64 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
65 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids);
66
67 /* Get Job names in Pool */
68 static const char *sql_job =
69 "SELECT DISTINCT Job.Name from Job,Pool"
70 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
71
72 /* Get JobIds from regex'ed Job names */
73 static const char *sql_jobids_from_job =
74 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
75 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
76 " ORDER by Job.StartTime";
77
78 /* Get Client names in Pool */
79 static const char *sql_client =
80 "SELECT DISTINCT Client.Name from Client,Pool,Job"
81 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
82 " Job.PoolId=Pool.PoolId";
83
84 /* Get JobIds from regex'ed Client names */
85 static const char *sql_jobids_from_client =
86 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
87 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
88 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
89 " AND Job.JobStatus IN ('T','W')"
90 " ORDER by Job.StartTime";
91
92 /* Get Volume names in Pool */
93 static const char *sql_vol =
94 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
95 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
96 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
97
98 /* Get JobIds from regex'ed Volume names */
99 static const char *sql_jobids_from_vol =
100 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
101 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
102 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
103 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
104 " ORDER by Job.StartTime";
105
106 static const char *sql_smallest_vol =
107 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
108 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
109 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
110 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
111 " ORDER BY VolBytes ASC LIMIT 1";
112
113 static const char *sql_oldest_vol =
114 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
115 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
116 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
117 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
118 " ORDER BY LastWritten ASC LIMIT 1";
119
120 /* Get JobIds when we have selected MediaId */
121 static const char *sql_jobids_from_mediaid =
122 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
123 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
124 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
125 " ORDER by Job.StartTime";
126
127 /* Get the number of bytes in the pool */
128 static const char *sql_pool_bytes =
129 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
130 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
131 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
132 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
133 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
134 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
135
136 /* Get the number of bytes in the Jobs */
137 static const char *sql_job_bytes =
138 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
139
140 /* Get Media Ids in Pool */
141 static const char *sql_mediaids =
142 "SELECT MediaId FROM Media,Pool WHERE"
143 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
144 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
145
146 /* Get JobIds in Pool longer than specified time */
147 static const char *sql_pool_time =
148 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
149 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
150 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
151 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
152 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
153 " AND Job.RealEndTime<='%s'";
154
155 /* Get JobIds from successfully completed backup jobs which have not been copied before */
156 static const char *sql_jobids_of_pool_uncopied_jobs =
157 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
158 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
159 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
160 " AND Job.jobBytes > 0"
161 " AND Job.JobId NOT IN"
162 " (SELECT PriorJobId FROM Job WHERE"
163 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
164 " AND PriorJobId != 0)"
165 " ORDER by Job.StartTime";
166
167 /*
168 *
169 * This is the central piece of code that finds a job or jobs
170 * actually JobIds to migrate. It first looks to see if one
171 * has been "manually" specified in jcr->MigrateJobId, and if
172 * so, it returns that JobId to be run. Otherwise, it
173 * examines the Selection Type to see what kind of migration
174 * we are doing (Volume, Job, Client, ...) and applies any
175 * Selection Pattern if appropriate to obtain a list of JobIds.
176 * Finally, it will loop over all the JobIds found, except the last
177 * one starting a new job with MigrationJobId set to that JobId, and
178 * finally, it returns the last JobId to the caller.
179 *
180 * Returns: -1 on error
181 * 0 if no jobs to migrate
182 * 1 if OK and jcr->previous_jr filled in
183 */
getJob_to_migrate(JCR * jcr)184 int getJob_to_migrate(JCR *jcr)
185 {
186 char ed1[30], ed2[30];
187 POOL_MEM query(PM_MESSAGE);
188 JobId_t JobId;
189 DBId_t DBId = 0;
190 int stat;
191 char *p;
192 idpkt ids, mid, jids;
193 db_int64_ctx ctx;
194 int64_t pool_bytes;
195 time_t ttime;
196 struct tm tm;
197 char dt[MAX_TIME_LENGTH];
198 int count = 0;
199 int limit = jcr->job->MaxSpawnedJobs; /* limit is max jobs to start */
200
201 ids.list = get_pool_memory(PM_MESSAGE);
202 ids.list[0] = 0;
203 ids.count = 0;
204 mid.list = get_pool_memory(PM_MESSAGE);
205 mid.list[0] = 0;
206 mid.count = 0;
207 jids.list = get_pool_memory(PM_MESSAGE);
208 jids.list[0] = 0;
209 jids.count = 0;
210
211 /*
212 * If MigrateJobId is set, then we migrate only that Job,
213 * otherwise, we go through the full selection of jobs to
214 * migrate.
215 */
216 if (jcr->MigrateJobId != 0) {
217 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
218 JobId = jcr->MigrateJobId;
219 } else {
220 switch (jcr->job->selection_type) {
221 case MT_JOB:
222 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
223 goto bail_out;
224 }
225 break;
226 case MT_CLIENT:
227 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
228 goto bail_out;
229 }
230 break;
231 case MT_VOLUME:
232 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
233 goto bail_out;
234 }
235 break;
236 case MT_SQLQUERY:
237 if (!jcr->job->selection_pattern) {
238 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
239 goto bail_out;
240 }
241 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
242 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
243 unique_dbid_handler, (void *)&ids)) {
244 Jmsg(jcr, M_FATAL, 0,
245 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
246 goto bail_out;
247 }
248 break;
249 case MT_SMALLEST_VOL:
250 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
251 goto bail_out;
252 }
253 break;
254 case MT_OLDEST_VOL:
255 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
256 goto bail_out;
257 }
258 break;
259 case MT_POOL_OCCUPANCY:
260 ctx.count = 0;
261 /* Find count of bytes in pool */
262 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
263 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
264 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
265 goto bail_out;
266 }
267 if (ctx.count == 0) {
268 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
269 goto ok_out;
270 }
271 pool_bytes = ctx.value;
272 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
273 pool_bytes);
274 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
275 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
276 goto ok_out;
277 }
278 Dmsg0(dbglevel, "We should do Occupation migration.\n");
279
280 ids.count = 0;
281 /* Find a list of MediaIds that could be migrated */
282 Mmsg(query, sql_mediaids, jcr->rpool->name());
283 Dmsg1(dbglevel, "query=%s\n", query.c_str());
284 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
285 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
286 goto bail_out;
287 }
288 if (ids.count == 0) {
289 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
290 goto ok_out;
291 }
292 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
293
294 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
295 goto bail_out;
296 }
297 /* ids == list of jobs */
298 p = ids.list;
299 for (int i=0; i < (int)ids.count; i++) {
300 stat = get_next_dbid_from_list(&p, &DBId);
301 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
302 if (stat < 0) {
303 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
304 goto bail_out;
305 } else if (stat == 0) {
306 break;
307 }
308
309 mid.count = 1;
310 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
311 if (jids.count > 0) {
312 pm_strcat(jids.list, ",");
313 }
314 pm_strcat(jids.list, mid.list);
315 jids.count += mid.count;
316
317 /* Find count of bytes from Jobs */
318 Mmsg(query, sql_job_bytes, mid.list);
319 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
320 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
321 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
322 goto bail_out;
323 }
324 pool_bytes -= ctx.value;
325 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
326 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
327 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
328 edit_int64_with_commas(pool_bytes, ed2));
329 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
330 Dmsg0(dbglevel, "We should be done.\n");
331 break;
332 }
333 }
334 /* Transfer jids to ids, where the jobs list is expected */
335 ids.count = jids.count;
336 pm_strcpy(ids.list, jids.list);
337 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
338 break;
339 case MT_POOL_TIME:
340 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
341 (void)localtime_r(&ttime, &tm);
342 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
343
344 ids.count = 0;
345 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
346 Dmsg1(dbglevel, "query=%s\n", query.c_str());
347 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
348 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
349 goto bail_out;
350 }
351 if (ids.count == 0) {
352 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
353 goto ok_out;
354 }
355 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
356 break;
357 case MT_POOL_UNCOPIED_JOBS:
358 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
359 goto bail_out;
360 }
361 break;
362 default:
363 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
364 goto bail_out;
365 }
366
367 /*
368 * Loop over all jobids except the last one, sending
369 * them to start_mac_job(), which will start a job
370 * for each of them. For the last JobId, we handle it below.
371 */
372 p = ids.list;
373 if (ids.count == 0) {
374 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
375 goto ok_out;
376 }
377
378 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
379 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
380 jcr->get_ActionName(1), ids.list);
381
382 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
383 /*
384 * Note: to not over load the system, limit the number
385 * of new jobs started to Maximum Spawned Jobs
386 */
387 for (int i=1; i < (int)ids.count; i++) {
388 JobId = 0;
389 stat = get_next_jobid_from_list(&p, &JobId);
390 Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
391 if (stat < 0) {
392 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
393 goto bail_out;
394 } else if (stat == 0) {
395 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
396 goto ok_out;
397 }
398 jcr->MigrateJobId = JobId;
399 /* Don't start any more when limit reaches zero */
400 limit--;
401 if (limit > 0) {
402 start_mac_job(jcr);
403 Dmsg0(dbglevel, "Back from start_mac_job\n");
404 }
405 }
406
407 /* Now get the last JobId and handle it in the current job */
408 JobId = 0;
409 stat = get_next_jobid_from_list(&p, &JobId);
410 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
411 if (stat < 0) {
412 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
413 goto bail_out;
414 } else if (stat == 0) {
415 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
416 goto ok_out;
417 }
418 }
419
420 jcr->previous_jr.JobId = JobId;
421 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
422
423 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
424 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
425 edit_int64(jcr->previous_jr.JobId, ed1),
426 jcr->get_ActionName(0),
427 db_strerror(jcr->db));
428 goto bail_out;
429 }
430
431 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
432 jcr->get_OperationName(),
433 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
434 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
435 jcr->get_OperationName(),
436 jcr->JobId,
437 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
438 count = 1;
439
440 ok_out:
441 goto out;
442
443 bail_out:
444 count = -1;
445
446 out:
447 free_pool_memory(ids.list);
448 free_pool_memory(mid.list);
449 free_pool_memory(jids.list);
450 return count;
451 }
452
453 /*
454 * This routine returns:
455 * false if an error occurred
456 * true otherwise
457 * ids.count number of jobids found (may be zero)
458 */
find_jobids_from_mediaid_list(JCR * jcr,idpkt * ids,const char * type)459 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
460 {
461 bool ok = false;
462 POOL_MEM query(PM_MESSAGE);
463
464 Mmsg(query, sql_jobids_from_mediaid, ids->list);
465 ids->count = 0;
466 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
467 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
468 goto bail_out;
469 }
470 if (ids->count == 0) {
471 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
472 }
473 ok = true;
474
475 bail_out:
476 return ok;
477 }
478
479 /*
480 * This routine returns:
481 * false if an error occurred
482 * true otherwise
483 * ids.count number of jobids found (may be zero)
484 */
find_jobids_of_pool_uncopied_jobs(JCR * jcr,idpkt * ids)485 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
486 {
487 bool ok = false;
488 POOL_MEM query(PM_MESSAGE);
489
490 /* Only a copy job is allowed */
491 if (jcr->getJobType() != JT_COPY) {
492 Jmsg(jcr, M_FATAL, 0,
493 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
494 goto bail_out;
495 }
496
497 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
498 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
499 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
500 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
501 Jmsg(jcr, M_FATAL, 0,
502 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
503 goto bail_out;
504 }
505 ok = true;
506
507 bail_out:
508 return ok;
509 }
510
regex_find_jobids(JCR * jcr,idpkt * ids,const char * query1,const char * query2,const char * type)511 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
512 const char *query2, const char *type)
513 {
514 dlist *item_chain;
515 uitem *item = NULL;
516 uitem *last_item = NULL;
517 regex_t preg;
518 char prbuf[500];
519 int rc;
520 bool ok = false;
521 POOL_MEM query(PM_MESSAGE);
522
523 item_chain = New(dlist(item, &item->link));
524 if (!jcr->job->selection_pattern) {
525 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
526 jcr->get_OperationName(), type);
527 goto bail_out;
528 }
529 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
530 /* Basic query for names */
531 Mmsg(query, query1, jcr->rpool->name());
532 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
533 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
534 (void *)item_chain)) {
535 Jmsg(jcr, M_FATAL, 0,
536 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
537 goto bail_out;
538 }
539 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
540 if (item_chain->size() == 0) {
541 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
542 jcr->rpool->name(), jcr->get_ActionName(0));
543 ok = true;
544 goto bail_out; /* skip regex match */
545 } else {
546 /* Compile regex expression */
547 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
548 if (rc != 0) {
549 regerror(rc, &preg, prbuf, sizeof(prbuf));
550 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
551 jcr->job->selection_pattern, prbuf);
552 goto bail_out;
553 }
554 /* Now apply the regex to the names and remove any item not matched */
555 foreach_dlist(item, item_chain) {
556 const int nmatch = 30;
557 regmatch_t pmatch[nmatch];
558 if (last_item) {
559 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
560 free(last_item->item);
561 item_chain->remove(last_item);
562 free(last_item);
563 }
564 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
565 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
566 if (rc == 0) {
567 last_item = NULL; /* keep this one */
568 } else {
569 last_item = item;
570 }
571 }
572 if (last_item) {
573 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
574 free(last_item->item);
575 item_chain->remove(last_item);
576 free(last_item);
577 }
578 regfree(&preg);
579 }
580 if (item_chain->size() == 0) {
581 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
582 ok = true;
583 goto bail_out; /* skip regex match */
584 }
585
586 /*
587 * At this point, we have a list of items in item_chain
588 * that have been matched by the regex, so now we need
589 * to look up their jobids.
590 */
591 ids->count = 0;
592 foreach_dlist(item, item_chain) {
593 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
594 Mmsg(query, query2, item->item, jcr->rpool->name());
595 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
596 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
597 Jmsg(jcr, M_FATAL, 0,
598 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
599 goto bail_out;
600 }
601 }
602 if (ids->count == 0) {
603 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
604 }
605 ok = true;
606
607 bail_out:
608 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
609 foreach_dlist(item, item_chain) {
610 free(item->item);
611 }
612 delete item_chain;
613 return ok;
614 }
615
find_mediaid_then_jobids(JCR * jcr,idpkt * ids,const char * query1,const char * type)616 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
617 const char *type)
618 {
619 bool ok = false;
620 POOL_MEM query(PM_MESSAGE);
621
622 ids->count = 0;
623 /* Basic query for MediaId */
624 Mmsg(query, query1, jcr->rpool->name());
625 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
626 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
627 goto bail_out;
628 }
629 if (ids->count == 0) {
630 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
631 ok = true; /* Not an error */
632 goto bail_out;
633 } else if (ids->count != 1) {
634 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
635 goto bail_out;
636 }
637 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
638
639 ok = find_jobids_from_mediaid_list(jcr, ids, type);
640
641 bail_out:
642 return ok;
643 }
644
645 /*
646 * const char *sql_ujobid =
647 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
648 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
649 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
650 */
651
652 /* Add an item to the list if it is unique */
add_unique_id(idpkt * ids,char * item)653 static void add_unique_id(idpkt *ids, char *item)
654 {
655 const int maxlen = 30;
656 char id[maxlen+1];
657 char *q = ids->list;
658
659 /* Walk through current list to see if each item is the same as item */
660 for ( ; *q; ) {
661 id[0] = 0;
662 for (int i=0; i<maxlen; i++) {
663 if (*q == 0) {
664 break;
665 } else if (*q == ',') {
666 q++;
667 break;
668 }
669 id[i] = *q++;
670 id[i+1] = 0;
671 }
672 if (strcmp(item, id) == 0) {
673 return;
674 }
675 }
676 /* Did not find item, so add it to list */
677 if (ids->count == 0) {
678 ids->list[0] = 0;
679 } else {
680 pm_strcat(ids->list, ",");
681 }
682 pm_strcat(ids->list, item);
683 ids->count++;
684 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
685 return;
686 }
687
688 /*
689 * Callback handler make list of DB Ids
690 */
unique_dbid_handler(void * ctx,int num_fields,char ** row)691 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
692 {
693 idpkt *ids = (idpkt *)ctx;
694
695 /* Sanity check */
696 if (!row || !row[0]) {
697 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
698 return 1; /* stop calling us */
699 }
700
701 add_unique_id(ids, row[0]);
702 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
703 return 0;
704 }
705
item_compare(void * item1,void * item2)706 static int item_compare(void *item1, void *item2)
707 {
708 uitem *i1 = (uitem *)item1;
709 uitem *i2 = (uitem *)item2;
710 return strcmp(i1->item, i2->item);
711 }
712
unique_name_handler(void * ctx,int num_fields,char ** row)713 static int unique_name_handler(void *ctx, int num_fields, char **row)
714 {
715 dlist *list = (dlist *)ctx;
716
717 uitem *new_item = (uitem *)malloc(sizeof(uitem));
718 uitem *item;
719
720 memset(new_item, 0, sizeof(uitem));
721 new_item->item = bstrdup(row[0]);
722 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
723 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
724 if (item != new_item) { /* already in list */
725 free(new_item->item);
726 free((char *)new_item);
727 return 0;
728 }
729 return 0;
730 }
731
732 /*
733 * Return next DBId from comma separated list
734 *
735 * Returns:
736 * 1 if next DBId returned
737 * 0 if no more DBIds are in list
738 * -1 there is an error
739 */
get_next_dbid_from_list(char ** p,DBId_t * DBId)740 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
741 {
742 const int maxlen = 30;
743 char id[maxlen+1];
744 char *q = *p;
745
746 id[0] = 0;
747 for (int i=0; i<maxlen; i++) {
748 if (*q == 0) {
749 break;
750 } else if (*q == ',') {
751 q++;
752 break;
753 }
754 id[i] = *q++;
755 id[i+1] = 0;
756 }
757 if (id[0] == 0) {
758 return 0;
759 } else if (!is_a_number(id)) {
760 return -1; /* error */
761 }
762 *p = q;
763 *DBId = str_to_int64(id);
764 return 1;
765 }
766