]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/mac_sql.c
More debug info when aborting
[bacula/bacula] / bacula / src / dird / mac_sql.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 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  */
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 100 (see limit above)
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  */
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  */
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
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          }
563          Dmsg1(dbglevel, "get name Item=%s\n", item->item);
564          rc = regexec(&preg, item->item, nmatch, pmatch,  0);
565          if (rc == 0) {
566             last_item = NULL;   /* keep this one */
567          } else {
568             last_item = item;
569          }
570       }
571       if (last_item) {
572          Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
573          free(last_item->item);
574          item_chain->remove(last_item);
575       }
576       regfree(&preg);
577    }
578    if (item_chain->size() == 0) {
579       Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
580       ok = true;
581       goto bail_out;               /* skip regex match */
582    }
583
584    /*
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.
588     */
589    ids->count = 0;
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));
597          goto bail_out;
598       }
599    }
600    if (ids->count == 0) {
601       Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
602    }
603    ok = true;
604
605 bail_out:
606    Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
607    foreach_dlist(item, item_chain) {
608       free(item->item);
609    }
610    delete item_chain;
611    return ok;
612 }
613
614 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
615                  const char *type)
616 {
617    bool ok = false;
618    POOL_MEM query(PM_MESSAGE);
619
620    ids->count = 0;
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));
625       goto bail_out;
626    }
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 */
630       goto bail_out;
631    } else if (ids->count != 1) {
632       Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
633       goto bail_out;
634    }
635    Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
636
637    ok = find_jobids_from_mediaid_list(jcr, ids, type);
638
639 bail_out:
640    return ok;
641 }
642
643 /*
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";
648 */
649
650 /* Add an item to the list if it is unique */
651 static void add_unique_id(idpkt *ids, char *item)
652 {
653    const int maxlen = 30;
654    char id[maxlen+1];
655    char *q = ids->list;
656
657    /* Walk through current list to see if each item is the same as item */
658    for ( ; *q; ) {
659        id[0] = 0;
660        for (int i=0; i<maxlen; i++) {
661           if (*q == 0) {
662              break;
663           } else if (*q == ',') {
664              q++;
665              break;
666           }
667           id[i] = *q++;
668           id[i+1] = 0;
669        }
670        if (strcmp(item, id) == 0) {
671           return;
672        }
673    }
674    /* Did not find item, so add it to list */
675    if (ids->count == 0) {
676       ids->list[0] = 0;
677    } else {
678       pm_strcat(ids->list, ",");
679    }
680    pm_strcat(ids->list, item);
681    ids->count++;
682 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
683    return;
684 }
685
686 /*
687  * Callback handler make list of DB Ids
688  */
689 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
690 {
691    idpkt *ids = (idpkt *)ctx;
692
693    /* Sanity check */
694    if (!row || !row[0]) {
695       Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
696       return 1;              /* stop calling us */
697    }
698
699    add_unique_id(ids, row[0]);
700    Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
701    return 0;
702 }
703
704 static int item_compare(void *item1, void *item2)
705 {
706    uitem *i1 = (uitem *)item1;
707    uitem *i2 = (uitem *)item2;
708    return strcmp(i1->item, i2->item);
709 }
710
711 static int unique_name_handler(void *ctx, int num_fields, char **row)
712 {
713    dlist *list = (dlist *)ctx;
714
715    uitem *new_item = (uitem *)malloc(sizeof(uitem));
716    uitem *item;
717
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);
725       return 0;
726    }
727    return 0;
728 }
729
730 /*
731  * Return next DBId from comma separated list
732  *
733  * Returns:
734  *   1 if next DBId returned
735  *   0 if no more DBIds are in list
736  *  -1 there is an error
737  */
738 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
739 {
740    const int maxlen = 30;
741    char id[maxlen+1];
742    char *q = *p;
743
744    id[0] = 0;
745    for (int i=0; i<maxlen; i++) {
746       if (*q == 0) {
747          break;
748       } else if (*q == ',') {
749          q++;
750          break;
751       }
752       id[i] = *q++;
753       id[i+1] = 0;
754    }
755    if (id[0] == 0) {
756       return 0;
757    } else if (!is_a_number(id)) {
758       return -1;                      /* error */
759    }
760    *p = q;
761    *DBId = str_to_int64(id);
762    return 1;
763 }