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