2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2015 Kern Sibbald
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.
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.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
21 * Bacula Director -- mac.c -- responsible for doing
22 * migration and copy jobs.
24 * Also handles Copy jobs (March MMVIII)
26 * Kern Sibbald, September MMIV
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
33 * When the Storage daemon finishes the job, update the DB.
41 #include "lib/bregex.h"
51 /* Imported functions */
52 extern void start_mac_job(JCR*);
54 static const int dbglevel = 10;
56 /* Forware referenced functions */
57 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
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);
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";
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";
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";
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";
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'";
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";
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";
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";
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";
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)";
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)";
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";
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'";
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";
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.
180 * Returns: -1 on error
181 * 0 if no jobs to migrate
182 * 1 if OK and jcr->previous_jr filled in
184 int getJob_to_migrate(JCR *jcr)
186 char ed1[30], ed2[30];
187 POOL_MEM query(PM_MESSAGE);
192 idpkt ids, mid, jids;
197 char dt[MAX_TIME_LENGTH];
199 int limit = jcr->job->MaxSpawnedJobs; /* limit is max jobs to start */
201 ids.list = get_pool_memory(PM_MESSAGE);
204 mid.list = get_pool_memory(PM_MESSAGE);
207 jids.list = get_pool_memory(PM_MESSAGE);
212 * If MigrateJobId is set, then we migrate only that Job,
213 * otherwise, we go through the full selection of jobs to
216 if (jcr->MigrateJobId != 0) {
217 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
218 JobId = jcr->MigrateJobId;
220 switch (jcr->job->selection_type) {
222 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
227 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
232 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
237 if (!jcr->job->selection_pattern) {
238 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
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));
249 case MT_SMALLEST_VOL:
250 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
255 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
259 case MT_POOL_OCCUPANCY:
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));
267 if (ctx.count == 0) {
268 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
271 pool_bytes = ctx.value;
272 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
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));
278 Dmsg0(dbglevel, "We should do Occupation migration.\n");
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));
288 if (ids.count == 0) {
289 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
292 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
294 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
297 /* ids == list of jobs */
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);
303 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
305 } else if (stat == 0) {
310 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
311 if (jids.count > 0) {
312 pm_strcat(jids.list, ",");
314 pm_strcat(jids.list, mid.list);
315 jids.count += mid.count;
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));
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");
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);
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);
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));
351 if (ids.count == 0) {
352 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
355 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
357 case MT_POOL_UNCOPIED_JOBS:
358 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
363 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
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.
373 if (ids.count == 0) {
374 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
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);
382 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
384 * Note: to not over load the system, limit the number
385 * of new jobs started to 100 (see limit above)
387 for (int i=1; i < (int)ids.count; i++) {
389 stat = get_next_jobid_from_list(&p, &JobId);
390 Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
392 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
394 } else if (stat == 0) {
395 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
398 jcr->MigrateJobId = JobId;
399 /* Don't start any more when limit reaches zero */
403 Dmsg0(dbglevel, "Back from start_mac_job\n");
407 /* Now get the last JobId and handle it in the current job */
409 stat = get_next_jobid_from_list(&p, &JobId);
410 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
412 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
414 } else if (stat == 0) {
415 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
420 jcr->previous_jr.JobId = JobId;
421 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
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));
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(),
437 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
447 free_pool_memory(ids.list);
448 free_pool_memory(mid.list);
449 free_pool_memory(jids.list);
454 * This routine returns:
455 * false if an error occurred
457 * ids.count number of jobids found (may be zero)
459 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
462 POOL_MEM query(PM_MESSAGE);
464 Mmsg(query, sql_jobids_from_mediaid, ids->list);
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));
470 if (ids->count == 0) {
471 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
480 * This routine returns:
481 * false if an error occurred
483 * ids.count number of jobids found (may be zero)
485 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
488 POOL_MEM query(PM_MESSAGE);
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"));
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));
511 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
512 const char *query2, const char *type)
516 uitem *last_item = NULL;
521 POOL_MEM query(PM_MESSAGE);
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);
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));
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));
544 goto bail_out; /* skip regex match */
546 /* Compile regex expression */
547 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
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);
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];
559 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
560 free(last_item->item);
561 item_chain->remove(last_item);
563 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
564 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
566 last_item = NULL; /* keep this one */
572 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
573 free(last_item->item);
574 item_chain->remove(last_item);
578 if (item_chain->size() == 0) {
579 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
581 goto bail_out; /* skip regex match */
585 * At this point, we have a list of items in item_chain
586 * that have been matched by the regex, so now we need
587 * to look up their jobids.
590 foreach_dlist(item, item_chain) {
591 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
592 Mmsg(query, query2, item->item, jcr->rpool->name());
593 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
594 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
595 Jmsg(jcr, M_FATAL, 0,
596 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
600 if (ids->count == 0) {
601 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
606 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
607 foreach_dlist(item, item_chain) {
614 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
618 POOL_MEM query(PM_MESSAGE);
621 /* Basic query for MediaId */
622 Mmsg(query, query1, jcr->rpool->name());
623 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
624 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
627 if (ids->count == 0) {
628 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
629 ok = true; /* Not an error */
631 } else if (ids->count != 1) {
632 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
635 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
637 ok = find_jobids_from_mediaid_list(jcr, ids, type);
644 * const char *sql_ujobid =
645 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
646 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
647 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
650 /* Add an item to the list if it is unique */
651 static void add_unique_id(idpkt *ids, char *item)
653 const int maxlen = 30;
657 /* Walk through current list to see if each item is the same as item */
660 for (int i=0; i<maxlen; i++) {
663 } else if (*q == ',') {
670 if (strcmp(item, id) == 0) {
674 /* Did not find item, so add it to list */
675 if (ids->count == 0) {
678 pm_strcat(ids->list, ",");
680 pm_strcat(ids->list, item);
682 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
687 * Callback handler make list of DB Ids
689 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
691 idpkt *ids = (idpkt *)ctx;
694 if (!row || !row[0]) {
695 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
696 return 1; /* stop calling us */
699 add_unique_id(ids, row[0]);
700 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
704 static int item_compare(void *item1, void *item2)
706 uitem *i1 = (uitem *)item1;
707 uitem *i2 = (uitem *)item2;
708 return strcmp(i1->item, i2->item);
711 static int unique_name_handler(void *ctx, int num_fields, char **row)
713 dlist *list = (dlist *)ctx;
715 uitem *new_item = (uitem *)malloc(sizeof(uitem));
718 memset(new_item, 0, sizeof(uitem));
719 new_item->item = bstrdup(row[0]);
720 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
721 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
722 if (item != new_item) { /* already in list */
723 free(new_item->item);
724 free((char *)new_item);
731 * Return next DBId from comma separated list
734 * 1 if next DBId returned
735 * 0 if no more DBIds are in list
736 * -1 there is an error
738 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
740 const int maxlen = 30;
745 for (int i=0; i<maxlen; i++) {
748 } else if (*q == ',') {
757 } else if (!is_a_number(id)) {
758 return -1; /* error */
761 *DBId = str_to_int64(id);