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