1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2001-2012 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2016 Planets Communications B.V.
6    Copyright (C) 2013-2016 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    Affero General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern Sibbald, October MMI
25  */
26 /**
27  * @file
28  * User Agent Prompt and Selection code
29  */
30 
31 #include "include/bareos.h"
32 #include "dird.h"
33 #include "dird/dird_globals.h"
34 #include "dird/jcr_private.h"
35 #include "dird/storage.h"
36 #include "dird/ua_input.h"
37 #include "dird/ua_select.h"
38 #include "lib/edit.h"
39 #include "lib/parse_conf.h"
40 #include "lib/util.h"
41 
42 #include <algorithm>
43 #include <string>
44 #include <vector>
45 
46 namespace directordaemon {
47 
48 /* Imported variables */
49 extern struct s_jt jobtypes[];
50 extern struct s_jl joblevels[];
51 
52 /**
53  * Confirm a retention period
54  */
ConfirmRetention(UaContext * ua,utime_t * ret,const char * msg)55 bool ConfirmRetention(UaContext* ua, utime_t* ret, const char* msg)
56 {
57   bool retval;
58   char ed1[100];
59   int yes_in_arg;
60 
61   yes_in_arg = FindArg(ua, NT_("yes"));
62   for (;;) {
63     ua->InfoMsg(_("The current %s retention period is: %s\n"), msg,
64                 edit_utime(*ret, ed1, sizeof(ed1)));
65     if (yes_in_arg != -1) { return true; }
66 
67     if (!GetCmd(ua, _("Continue? (yes/mod/no): "))) { return false; }
68 
69     if (Bstrcasecmp(ua->cmd, _("mod"))) {
70       if (!GetCmd(ua, _("Enter new retention period: "))) { return false; }
71       if (!DurationToUtime(ua->cmd, ret)) {
72         ua->ErrorMsg(_("Invalid period.\n"));
73         continue;
74       }
75       continue;
76     }
77 
78     if (IsYesno(ua->cmd, &retval)) { return retval; }
79   }
80 
81   return true;
82 }
83 
84 /**
85  * Given a list of keywords, find the first one that is in the argument list.
86  *
87  * Returns: -1 if not found
88  *          index into list (base 0) on success
89  */
FindArgKeyword(UaContext * ua,const char ** list)90 int FindArgKeyword(UaContext* ua, const char** list)
91 {
92   for (int i = 1; i < ua->argc; i++) {
93     for (int j = 0; list[j]; j++) {
94       if (Bstrcasecmp(list[j], ua->argk[i])) { return j; }
95     }
96   }
97 
98   return -1;
99 }
100 
101 /**
102  * Given one keyword, find the first one that is in the argument list.
103  *
104  * Returns: argk index (always gt 0)
105  *          -1 if not found
106  */
FindArg(UaContext * ua,const char * keyword)107 int FindArg(UaContext* ua, const char* keyword)
108 {
109   for (int i = 1; i < ua->argc; i++) {
110     if (Bstrcasecmp(keyword, ua->argk[i])) { return i; }
111   }
112 
113   return -1;
114 }
115 
116 /**
117  * Given a single keyword, find it in the argument list, but it must have a
118  * value
119  *
120  * Returns: -1 if not found or no value
121  *           list index (base 0) on success
122  */
FindArgWithValue(UaContext * ua,const char * keyword)123 int FindArgWithValue(UaContext* ua, const char* keyword)
124 {
125   for (int i = 1; i < ua->argc; i++) {
126     if (Bstrcasecmp(keyword, ua->argk[i])) {
127       if (ua->argv[i]) {
128         return i;
129       } else {
130         return -1;
131       }
132     }
133   }
134 
135   return -1;
136 }
137 
138 /**
139  * Given a list of keywords, prompt the user to choose one.
140  *
141  * Returns: -1 on failure
142  *          index into list (base 0) on success
143  */
DoKeywordPrompt(UaContext * ua,const char * msg,const char ** list)144 int DoKeywordPrompt(UaContext* ua, const char* msg, const char** list)
145 {
146   StartPrompt(ua, _("You have the following choices:\n"));
147   for (int i = 0; list[i]; i++) { AddPrompt(ua, list[i]); }
148 
149   return DoPrompt(ua, "", msg, NULL, 0);
150 }
151 
152 /**
153  * Select a Storage resource from prompt list
154  */
select_storage_resource(UaContext * ua,bool autochanger_only)155 StorageResource* select_storage_resource(UaContext* ua, bool autochanger_only)
156 {
157   StorageResource* store;
158   char name[MAX_NAME_LENGTH];
159   std::vector<std::string> storage_resource_names;
160 
161   if (autochanger_only) {
162     StartPrompt(ua, _("The defined Autochanger Storage resources are:\n"));
163   } else {
164     StartPrompt(ua, _("The defined Storage resources are:\n"));
165   }
166 
167   LockRes(my_config);
168   foreach_res (store, R_STORAGE) {
169     if (ua->AclAccessOk(Storage_ACL, store->resource_name_)) {
170       if (autochanger_only && !store->autochanger) {
171         continue;
172       } else {
173         storage_resource_names.emplace_back(store->resource_name_);
174       }
175     }
176   }
177   UnlockRes(my_config);
178 
179   SortCaseInsensitive(storage_resource_names);
180 
181   for (auto& resource_name : storage_resource_names) {
182     AddPrompt(ua, std::move(resource_name));
183   }
184 
185   if (DoPrompt(ua, _("Storage"), _("Select Storage resource"), name,
186                sizeof(name)) < 0) {
187     return NULL;
188   }
189   store = ua->GetStoreResWithName(name);
190 
191   return store;
192 }
193 
194 /**
195  * Select a FileSet resource from prompt list
196  */
select_fileset_resource(UaContext * ua)197 FilesetResource* select_fileset_resource(UaContext* ua)
198 {
199   FilesetResource* fs;
200   char name[MAX_NAME_LENGTH];
201   std::vector<std::string> fileset_resource_names;
202 
203   StartPrompt(ua, _("The defined FileSet resources are:\n"));
204 
205   LockRes(my_config);
206   foreach_res (fs, R_FILESET) {
207     if (ua->AclAccessOk(FileSet_ACL, fs->resource_name_)) {
208       fileset_resource_names.emplace_back(fs->resource_name_);
209     }
210   }
211   UnlockRes(my_config);
212 
213   SortCaseInsensitive(fileset_resource_names);
214 
215   for (auto& resource_name : fileset_resource_names) {
216     AddPrompt(ua, std::move(resource_name));
217   }
218 
219   if (DoPrompt(ua, _("FileSet"), _("Select FileSet resource"), name,
220                sizeof(name)) < 0) {
221     return NULL;
222   }
223 
224   fs = ua->GetFileSetResWithName(name);
225 
226   return fs;
227 }
228 
229 /**
230  * Get a catalog resource from prompt list
231  */
get_catalog_resource(UaContext * ua)232 CatalogResource* get_catalog_resource(UaContext* ua)
233 {
234   CatalogResource* catalog = NULL;
235   char name[MAX_NAME_LENGTH];
236 
237   for (int i = 1; i < ua->argc; i++) {
238     if (Bstrcasecmp(ua->argk[i], NT_("catalog")) && ua->argv[i]) {
239       catalog = ua->GetCatalogResWithName(ua->argv[i]);
240       if (catalog) { break; }
241     }
242   }
243 
244   if (ua->gui && !catalog) {
245     LockRes(my_config);
246     catalog = (CatalogResource*)my_config->GetNextRes(R_CATALOG, NULL);
247     UnlockRes(my_config);
248 
249     if (!catalog) {
250       ua->ErrorMsg(_("Could not find a Catalog resource\n"));
251       return NULL;
252     } else if (!ua->AclAccessOk(Catalog_ACL, catalog->resource_name_)) {
253       ua->ErrorMsg(
254           _("You must specify a \"use <catalog-name>\" command before "
255             "continuing.\n"));
256       return NULL;
257     }
258 
259     return catalog;
260   }
261 
262   if (!catalog) {
263     StartPrompt(ua, _("The defined Catalog resources are:\n"));
264 
265     LockRes(my_config);
266     foreach_res (catalog, R_CATALOG) {
267       if (ua->AclAccessOk(Catalog_ACL, catalog->resource_name_)) {
268         AddPrompt(ua, catalog->resource_name_);
269       }
270     }
271     UnlockRes(my_config);
272 
273     if (DoPrompt(ua, _("Catalog"), _("Select Catalog resource"), name,
274                  sizeof(name)) < 0) {
275       return NULL;
276     }
277 
278     catalog = ua->GetCatalogResWithName(name);
279   }
280 
281   return catalog;
282 }
283 
284 /**
285  * Select a job to enable or disable
286  */
select_enable_disable_job_resource(UaContext * ua,bool enable)287 JobResource* select_enable_disable_job_resource(UaContext* ua, bool enable)
288 {
289   JobResource* job;
290   char name[MAX_NAME_LENGTH];
291   std::vector<std::string> job_resource_names;
292 
293   StartPrompt(ua, _("The defined Job resources are:\n"));
294 
295   LockRes(my_config);
296   foreach_res (job, R_JOB) {
297     if (!ua->AclAccessOk(Job_ACL, job->resource_name_)) { continue; }
298     if (job->enabled == enable) { /* Already enabled/disabled? */
299       continue;                   /* yes, skip */
300     }
301     job_resource_names.emplace_back(job->resource_name_);
302   }
303   UnlockRes(my_config);
304 
305   SortCaseInsensitive(job_resource_names);
306 
307   for (auto& resource_name : job_resource_names) {
308     AddPrompt(ua, std::move(resource_name));
309   }
310 
311   if (DoPrompt(ua, _("Job"), _("Select Job resource"), name, sizeof(name)) <
312       0) {
313     return NULL;
314   }
315 
316   job = ua->GetJobResWithName(name);
317 
318   return job;
319 }
320 
321 /**
322  * Select a Job resource from prompt list
323  */
select_job_resource(UaContext * ua)324 JobResource* select_job_resource(UaContext* ua)
325 {
326   JobResource* job;
327   char name[MAX_NAME_LENGTH];
328   std::vector<std::string> job_resource_names;
329 
330   StartPrompt(ua, _("The defined Job resources are:\n"));
331 
332   LockRes(my_config);
333   foreach_res (job, R_JOB) {
334     if (ua->AclAccessOk(Job_ACL, job->resource_name_)) {
335       job_resource_names.emplace_back(job->resource_name_);
336     }
337   }
338   UnlockRes(my_config);
339 
340   SortCaseInsensitive(job_resource_names);
341 
342   for (auto& resource_name : job_resource_names) {
343     AddPrompt(ua, std::move(resource_name));
344   }
345 
346   if (DoPrompt(ua, _("Job"), _("Select Job resource"), name, sizeof(name)) <
347       0) {
348     return NULL;
349   }
350 
351   job = ua->GetJobResWithName(name);
352 
353   return job;
354 }
355 
356 /**
357  * Select a Restore Job resource from argument or prompt
358  */
get_restore_job(UaContext * ua)359 JobResource* get_restore_job(UaContext* ua)
360 {
361   int i;
362   JobResource* job;
363 
364   i = FindArgWithValue(ua, NT_("restorejob"));
365   if (i >= 0) {
366     job = ua->GetJobResWithName(ua->argv[i]);
367     if (job && job->JobType == JT_RESTORE) { return job; }
368     ua->ErrorMsg(_("Error: Restore Job resource \"%s\" does not exist.\n"),
369                  ua->argv[i]);
370   }
371 
372   return select_restore_job_resource(ua);
373 }
374 
375 /**
376  * Select a Restore Job resource from prompt list
377  */
select_restore_job_resource(UaContext * ua)378 JobResource* select_restore_job_resource(UaContext* ua)
379 {
380   JobResource* job;
381   char name[MAX_NAME_LENGTH];
382   std::vector<std::string> restore_job_names;
383 
384   StartPrompt(ua, _("The defined Restore Job resources are:\n"));
385 
386   LockRes(my_config);
387   foreach_res (job, R_JOB) {
388     if (job->JobType == JT_RESTORE &&
389         ua->AclAccessOk(Job_ACL, job->resource_name_)) {
390       restore_job_names.emplace_back(job->resource_name_);
391     }
392   }
393   UnlockRes(my_config);
394 
395   SortCaseInsensitive(restore_job_names);
396 
397   for (auto& resource_name : restore_job_names) {
398     AddPrompt(ua, std::move(resource_name));
399   }
400 
401   if (DoPrompt(ua, _("Job"), _("Select Restore Job"), name, sizeof(name)) < 0) {
402     return NULL;
403   }
404 
405   job = ua->GetJobResWithName(name);
406 
407   return job;
408 }
409 
410 /**
411  * Select a client resource from prompt list
412  */
select_client_resource(UaContext * ua)413 ClientResource* select_client_resource(UaContext* ua)
414 {
415   ClientResource* client;
416   char name[MAX_NAME_LENGTH];
417   std::vector<std::string> client_resource_names;
418 
419   StartPrompt(ua, _("The defined Client resources are:\n"));
420 
421   LockRes(my_config);
422   foreach_res (client, R_CLIENT) {
423     if (ua->AclAccessOk(Client_ACL, client->resource_name_)) {
424       client_resource_names.emplace_back(client->resource_name_);
425     }
426   }
427   UnlockRes(my_config);
428 
429   SortCaseInsensitive(client_resource_names);
430 
431   for (auto& resource_name : client_resource_names) {
432     AddPrompt(ua, std::move(resource_name));
433   }
434 
435   if (DoPrompt(ua, _("Client"), _("Select Client (File daemon) resource"), name,
436                sizeof(name)) < 0) {
437     return NULL;
438   }
439 
440   client = ua->GetClientResWithName(name);
441 
442   return client;
443 }
444 
445 /**
446  * Select a client to enable or disable
447  */
select_enable_disable_client_resource(UaContext * ua,bool enable)448 ClientResource* select_enable_disable_client_resource(UaContext* ua,
449                                                       bool enable)
450 {
451   ClientResource* client;
452   char name[MAX_NAME_LENGTH];
453   std::vector<std::string> client_resource_names;
454 
455   StartPrompt(ua, _("The defined Client resources are:\n"));
456 
457   LockRes(my_config);
458   foreach_res (client, R_CLIENT) {
459     if (!ua->AclAccessOk(Client_ACL, client->resource_name_)) { continue; }
460     if (client->enabled == enable) { /* Already enabled/disabled? */
461       continue;                      /* yes, skip */
462     }
463     client_resource_names.emplace_back(client->resource_name_);
464   }
465   UnlockRes(my_config);
466 
467   SortCaseInsensitive(client_resource_names);
468 
469   for (auto& resource_name : client_resource_names) {
470     AddPrompt(ua, std::move(resource_name));
471   }
472 
473   if (DoPrompt(ua, _("Client"), _("Select Client resource"), name,
474                sizeof(name)) < 0) {
475     return NULL;
476   }
477 
478   client = ua->GetClientResWithName(name);
479 
480   return client;
481 }
482 
483 /**
484  *  Get client resource, start by looking for
485  *   client=<client-name>
486  *  if we don't find the keyword, we prompt the user.
487  */
get_client_resource(UaContext * ua)488 ClientResource* get_client_resource(UaContext* ua)
489 {
490   ClientResource* client = NULL;
491 
492   for (int i = 1; i < ua->argc; i++) {
493     if ((Bstrcasecmp(ua->argk[i], NT_("client")) ||
494          Bstrcasecmp(ua->argk[i], NT_("fd"))) &&
495         ua->argv[i]) {
496       client = ua->GetClientResWithName(ua->argv[i]);
497       if (client) { return client; }
498 
499       ua->ErrorMsg(_("Error: Client resource %s does not exist.\n"),
500                    ua->argv[i]);
501 
502       break;
503     }
504   }
505 
506   return select_client_resource(ua);
507 }
508 
509 /**
510  * Select a schedule to enable or disable
511  */
select_enable_disable_schedule_resource(UaContext * ua,bool enable)512 ScheduleResource* select_enable_disable_schedule_resource(UaContext* ua,
513                                                           bool enable)
514 {
515   ScheduleResource* sched;
516   char name[MAX_NAME_LENGTH];
517   std::vector<std::string> schedule_resource_names;
518 
519   StartPrompt(ua, _("The defined Schedule resources are:\n"));
520 
521   LockRes(my_config);
522   foreach_res (sched, R_SCHEDULE) {
523     if (!ua->AclAccessOk(Schedule_ACL, sched->resource_name_)) { continue; }
524     if (sched->enabled == enable) { /* Already enabled/disabled? */
525       continue;                     /* yes, skip */
526     }
527     schedule_resource_names.emplace_back(sched->resource_name_);
528   }
529   UnlockRes(my_config);
530 
531   SortCaseInsensitive(schedule_resource_names);
532 
533   for (auto& resource_name : schedule_resource_names) {
534     AddPrompt(ua, std::move(resource_name));
535   }
536 
537   if (DoPrompt(ua, _("Schedule"), _("Select Schedule resource"), name,
538                sizeof(name)) < 0) {
539     return NULL;
540   }
541 
542   sched = ua->GetScheduleResWithName(name);
543 
544   return sched;
545 }
546 
547 /**
548  * Scan what the user has entered looking for:
549  *
550  * client=<client-name>
551  *
552  * If error or not found, put up a list of client DBRs to choose from.
553  *
554  * returns: false on error
555  *          true on success and fills in ClientDbRecord
556  */
GetClientDbr(UaContext * ua,ClientDbRecord * cr)557 bool GetClientDbr(UaContext* ua, ClientDbRecord* cr)
558 {
559   if (cr->Name[0]) { /* If name already supplied */
560     if (ua->db->GetClientRecord(ua->jcr, cr)) { return true; }
561     ua->ErrorMsg(_("Could not find Client %s: ERR=%s"), cr->Name,
562                  ua->db->strerror());
563   }
564 
565   for (int i = 1; i < ua->argc; i++) {
566     if ((Bstrcasecmp(ua->argk[i], NT_("client")) ||
567          Bstrcasecmp(ua->argk[i], NT_("fd"))) &&
568         ua->argv[i]) {
569       if (!ua->AclAccessOk(Client_ACL, ua->argv[i])) { break; }
570       bstrncpy(cr->Name, ua->argv[i], sizeof(cr->Name));
571       if (!ua->db->GetClientRecord(ua->jcr, cr)) {
572         ua->ErrorMsg(_("Could not find Client \"%s\": ERR=%s"), ua->argv[i],
573                      ua->db->strerror());
574         cr->ClientId = 0;
575         break;
576       }
577       return true;
578     }
579   }
580 
581   if (!SelectClientDbr(ua, cr)) { /* try once more by proposing a list */
582     return false;
583   }
584 
585   return true;
586 }
587 
588 /**
589  * Select a Client record from the catalog
590  *
591  * Returns true on success
592  *         false on failure
593  */
SelectClientDbr(UaContext * ua,ClientDbRecord * cr)594 bool SelectClientDbr(UaContext* ua, ClientDbRecord* cr)
595 {
596   DBId_t* ids = nullptr;
597   ClientDbRecord ocr;
598   int num_clients;
599   char name[MAX_NAME_LENGTH];
600 
601   cr->ClientId = 0;
602   if (!ua->db->GetClientIds(ua->jcr, &num_clients, &ids)) {
603     ua->ErrorMsg(_("Error obtaining client ids. ERR=%s\n"), ua->db->strerror());
604     if (ids) { free(ids); }
605     return false;
606   }
607 
608   if (num_clients <= 0) {
609     ua->ErrorMsg(_(
610         "No clients defined. You must run a job before using this command.\n"));
611     if (ids) { free(ids); }
612     return false;
613   }
614 
615   StartPrompt(ua, _("Defined Clients:\n"));
616   for (int i = 0; i < num_clients; i++) {
617     ocr.ClientId = ids[i];
618     if (!ua->db->GetClientRecord(ua->jcr, &ocr) ||
619         !ua->AclAccessOk(Client_ACL, ocr.Name)) {
620       continue;
621     }
622     AddPrompt(ua, ocr.Name);
623   }
624   if (ids) { free(ids); }
625 
626   if (DoPrompt(ua, _("Client"), _("Select the Client"), name, sizeof(name)) <
627       0) {
628     return false;
629   }
630 
631   new (&ocr) ClientDbRecord();
632   bstrncpy(ocr.Name, name, sizeof(ocr.Name));
633 
634   if (!ua->db->GetClientRecord(ua->jcr, &ocr)) {
635     ua->ErrorMsg(_("Could not find Client \"%s\": ERR=%s"), name,
636                  ua->db->strerror());
637     return false;
638   }
639 
640   memcpy(cr, &ocr, sizeof(ocr));
641 
642   return true;
643 }
644 
645 /**
646  * Scan what the user has entered looking for:
647  *
648  * argk=<storage-name>
649  *
650  * where argk can be : storage
651  *
652  * If error or not found, put up a list of storage DBRs to choose from.
653  *
654  * returns: false on error
655  *          true  on success and fills in StorageDbRecord
656  */
GetStorageDbr(UaContext * ua,StorageDbRecord * sr,const char * argk)657 bool GetStorageDbr(UaContext* ua, StorageDbRecord* sr, const char* argk)
658 {
659   if (sr->Name[0]) { /* If name already supplied */
660     if (ua->db->GetStorageRecord(ua->jcr, sr) &&
661         ua->AclAccessOk(Pool_ACL, sr->Name)) {
662       return true;
663     }
664     ua->ErrorMsg(_("Could not find Storage \"%s\": ERR=%s"), sr->Name,
665                  ua->db->strerror());
666   }
667 
668   if (!SelectStorageDbr(ua, sr, argk)) { /* try once more */
669     return false;
670   }
671 
672   return true;
673 }
674 
675 /**
676  * Scan what the user has entered looking for:
677  *
678  * argk=<pool-name>
679  *
680  * where argk can be : pool, recyclepool, scratchpool, nextpool etc..
681  *
682  * If error or not found, put up a list of pool DBRs to choose from.
683  *
684  * returns: false on error
685  *          true  on success and fills in PoolDbRecord
686  */
GetPoolDbr(UaContext * ua,PoolDbRecord * pr,const char * argk)687 bool GetPoolDbr(UaContext* ua, PoolDbRecord* pr, const char* argk)
688 {
689   if (pr->Name[0]) { /* If name already supplied */
690     if (ua->db->GetPoolRecord(ua->jcr, pr) &&
691         ua->AclAccessOk(Pool_ACL, pr->Name)) {
692       return true;
693     }
694     ua->ErrorMsg(_("Could not find Pool \"%s\": ERR=%s"), pr->Name,
695                  ua->db->strerror());
696   }
697 
698   if (!SelectPoolDbr(ua, pr, argk)) { /* try once more */
699     return false;
700   }
701 
702   return true;
703 }
704 
705 /**
706  * Select a Pool record from catalog
707  * argk can be pool, recyclepool, scratchpool etc..
708  */
SelectPoolDbr(UaContext * ua,PoolDbRecord * pr,const char * argk)709 bool SelectPoolDbr(UaContext* ua, PoolDbRecord* pr, const char* argk)
710 {
711   PoolDbRecord opr;
712   DBId_t* ids = nullptr;
713   int num_pools;
714   char name[MAX_NAME_LENGTH];
715 
716   for (int i = 1; i < ua->argc; i++) {
717     if (Bstrcasecmp(ua->argk[i], argk) && ua->argv[i] &&
718         ua->AclAccessOk(Pool_ACL, ua->argv[i])) {
719       bstrncpy(pr->Name, ua->argv[i], sizeof(pr->Name));
720       if (!ua->db->GetPoolRecord(ua->jcr, pr)) {
721         ua->ErrorMsg(_("Could not find Pool \"%s\": ERR=%s"), ua->argv[i],
722                      ua->db->strerror());
723         pr->PoolId = 0;
724         break;
725       }
726       return true;
727     }
728   }
729 
730   pr->PoolId = 0;
731   if (!ua->db->GetPoolIds(ua->jcr, &num_pools, &ids)) {
732     ua->ErrorMsg(_("Error obtaining pool ids. ERR=%s\n"), ua->db->strerror());
733     if (ids) { free(ids); }
734     return 0;
735   }
736 
737   if (num_pools <= 0) {
738     ua->ErrorMsg(
739         _("No pools defined. Use the \"create\" command to create one.\n"));
740     if (ids) { free(ids); }
741     return false;
742   }
743 
744   StartPrompt(ua, _("Defined Pools:\n"));
745   if (bstrcmp(argk, NT_("recyclepool"))) { AddPrompt(ua, _("*None*")); }
746 
747   for (int i = 0; i < num_pools; i++) {
748     opr.PoolId = ids[i];
749     if (!ua->db->GetPoolRecord(ua->jcr, &opr) ||
750         !ua->AclAccessOk(Pool_ACL, opr.Name)) {
751       continue;
752     }
753     AddPrompt(ua, opr.Name);
754   }
755   if (ids) { free(ids); }
756 
757   if (DoPrompt(ua, _("Pool"), _("Select the Pool"), name, sizeof(name)) < 0) {
758     return false;
759   }
760 
761   new (&opr) PoolDbRecord();  // placement new instead of memset
762 
763   /*
764    * *None* is only returned when selecting a recyclepool, and in that case
765    * the calling code is only interested in opr.Name, so then we can leave
766    * pr as all zero.
767    */
768   if (!bstrcmp(name, _("*None*"))) {
769     bstrncpy(opr.Name, name, sizeof(opr.Name));
770 
771     if (!ua->db->GetPoolRecord(ua->jcr, &opr)) {
772       ua->ErrorMsg(_("Could not find Pool \"%s\": ERR=%s"), name,
773                    ua->db->strerror());
774       return false;
775     }
776   }
777 
778   memcpy(pr, &opr, sizeof(opr));
779 
780   return true;
781 }
782 
783 /**
784  * Select a Pool and a Media (Volume) record from the database
785  */
SelectPoolAndMediaDbr(UaContext * ua,PoolDbRecord * pr,MediaDbRecord * mr)786 bool SelectPoolAndMediaDbr(UaContext* ua, PoolDbRecord* pr, MediaDbRecord* mr)
787 {
788   if (!SelectMediaDbr(ua, mr)) { return false; }
789 
790   new (pr) PoolDbRecord();  // placement new instead of memset
791 
792   pr->PoolId = mr->PoolId;
793   if (!ua->db->GetPoolRecord(ua->jcr, pr)) {
794     ua->ErrorMsg("%s", ua->db->strerror());
795     return false;
796   }
797 
798   if (!ua->AclAccessOk(Pool_ACL, pr->Name, true)) {
799     ua->ErrorMsg(_("No access to Pool \"%s\"\n"), pr->Name);
800     return false;
801   }
802 
803   return true;
804 }
805 
806 /**
807  * Select a Storage record from catalog
808  * argk can be storage
809  */
SelectStorageDbr(UaContext * ua,StorageDbRecord * sr,const char * argk)810 bool SelectStorageDbr(UaContext* ua, StorageDbRecord* sr, const char* argk)
811 {
812   StorageDbRecord osr;
813   DBId_t* ids = nullptr;
814   int num_storages;
815   char name[MAX_NAME_LENGTH];
816 
817   for (int i = 1; i < ua->argc; i++) {
818     if (Bstrcasecmp(ua->argk[i], argk) && ua->argv[i] &&
819         ua->AclAccessOk(Storage_ACL, ua->argv[i])) {
820       bstrncpy(sr->Name, ua->argv[i], sizeof(sr->Name));
821       if (!ua->db->GetStorageRecord(ua->jcr, sr)) {
822         ua->ErrorMsg(_("Could not find Storage \"%s\": ERR=%s"), ua->argv[i],
823                      ua->db->strerror());
824         sr->StorageId = 0;
825         break;
826       }
827       return true;
828     }
829   }
830 
831   sr->StorageId = 0;
832   if (!ua->db->GetStorageIds(ua->jcr, &num_storages, &ids)) {
833     ua->ErrorMsg(_("Error obtaining storage ids. ERR=%s\n"),
834                  ua->db->strerror());
835     if (ids) { free(ids); }
836     return 0;
837   }
838 
839   if (num_storages <= 0) {
840     ua->ErrorMsg(_("No storages defined.\n"));
841     if (ids) { free(ids); }
842     return false;
843   }
844 
845   StartPrompt(ua, _("Defined Storages:\n"));
846   if (bstrcmp(argk, NT_("recyclestorage"))) { AddPrompt(ua, _("*None*")); }
847 
848   for (int i = 0; i < num_storages; i++) {
849     osr.StorageId = ids[i];
850     if (!ua->db->GetStorageRecord(ua->jcr, &osr) ||
851         !ua->AclAccessOk(Storage_ACL, osr.Name)) {
852       continue;
853     }
854     AddPrompt(ua, osr.Name);
855   }
856   if (ids) { free(ids); }
857 
858   if (DoPrompt(ua, _("Storage"), _("Select the Storage"), name, sizeof(name)) <
859       0) {
860     return false;
861   }
862 
863   new (&osr) StorageDbRecord();  // placement new instead of memset
864 
865   /*
866    * *None* is only returned when selecting a recyclestorage, and in that case
867    * the calling code is only interested in osr.Name, so then we can leave
868    * sr as all zero.
869    */
870   if (!bstrcmp(name, _("*None*"))) {
871     bstrncpy(osr.Name, name, sizeof(osr.Name));
872 
873     if (!ua->db->GetStorageRecord(ua->jcr, &osr)) {
874       ua->ErrorMsg(_("Could not find Storage \"%s\": ERR=%s"), name,
875                    ua->db->strerror());
876       return false;
877     }
878   }
879 
880   memcpy(sr, &osr, sizeof(osr));
881 
882   return true;
883 }
884 
885 /**
886  * Select a Media (Volume) record from the database
887  */
SelectMediaDbr(UaContext * ua,MediaDbRecord * mr)888 bool SelectMediaDbr(UaContext* ua, MediaDbRecord* mr)
889 {
890   int i;
891   int retval = false;
892   POOLMEM* err = GetPoolMemory(PM_FNAME);
893 
894   *err = 0;
895   new (mr) MediaDbRecord();  // placement new instead of memset
896   i = FindArgWithValue(ua, NT_("volume"));
897   if (i >= 0) {
898     if (IsNameValid(ua->argv[i], err)) {
899       bstrncpy(mr->VolumeName, ua->argv[i], sizeof(mr->VolumeName));
900     } else {
901       goto bail_out;
902     }
903   }
904 
905   if (mr->VolumeName[0] == 0) {
906     PoolDbRecord pr;
907     /*
908      * Get the pool from pool=<pool-name>
909      */
910     if (!GetPoolDbr(ua, &pr)) { goto bail_out; }
911 
912     mr->PoolId = pr.PoolId;
913     ua->db->ListMediaRecords(ua->jcr, mr, NULL, false, ua->send, HORZ_LIST);
914 
915     ua->SendMsg(_("Enter the volume name or MediaId of the volume prefixed with an asterisk (*).\n"));
916     if (!GetCmd(ua, _("E.g. \"full-0001\" or \"*42\": "))) { goto bail_out; }
917 
918     if (ua->cmd[0] == '*' && Is_a_number(ua->cmd + 1)) {
919       mr->MediaId = str_to_int64(ua->cmd + 1);
920     } else if (IsNameValid(ua->cmd, err)) {
921       bstrncpy(mr->VolumeName, ua->cmd, sizeof(mr->VolumeName));
922     } else {
923       goto bail_out;
924     }
925   }
926 
927   if (!ua->db->GetMediaRecord(ua->jcr, mr)) {
928     PmStrcpy(err, ua->db->strerror());
929     goto bail_out;
930   }
931   retval = true;
932 
933 bail_out:
934   if (!retval && *err) { ua->ErrorMsg("%s", err); }
935   FreePoolMemory(err);
936 
937   return retval;
938 }
939 
940 /**
941  * Select a pool resource from prompt list
942  */
select_pool_resource(UaContext * ua)943 PoolResource* select_pool_resource(UaContext* ua)
944 {
945   PoolResource* pool;
946   char name[MAX_NAME_LENGTH];
947   std::vector<std::string> pool_resource_names;
948 
949   StartPrompt(ua, _("The defined Pool resources are:\n"));
950   LockRes(my_config);
951   foreach_res (pool, R_POOL) {
952     if (ua->AclAccessOk(Pool_ACL, pool->resource_name_)) {
953       pool_resource_names.emplace_back(pool->resource_name_);
954     }
955   }
956   UnlockRes(my_config);
957 
958   SortCaseInsensitive(pool_resource_names);
959 
960   for (auto& resource_name : pool_resource_names) {
961     AddPrompt(ua, std::move(resource_name));
962   }
963 
964   if (DoPrompt(ua, _("Pool"), _("Select Pool resource"), name, sizeof(name)) <
965       0) {
966     return NULL;
967   }
968 
969   pool = ua->GetPoolResWithName(name);
970 
971   return pool;
972 }
973 
974 /**
975  *  If you are thinking about using it, you
976  *  probably want to use SelectPoolDbr()
977  *  or GetPoolDbr() above.
978  */
get_pool_resource(UaContext * ua)979 PoolResource* get_pool_resource(UaContext* ua)
980 {
981   int i;
982   PoolResource* pool = NULL;
983 
984   i = FindArgWithValue(ua, NT_("pool"));
985   if (i >= 0 && ua->AclAccessOk(Pool_ACL, ua->argv[i])) {
986     pool = ua->GetPoolResWithName(ua->argv[i]);
987     if (pool) { return pool; }
988     ua->ErrorMsg(_("Error: Pool resource \"%s\" does not exist.\n"),
989                  ua->argv[i]);
990   }
991 
992   return select_pool_resource(ua);
993 }
994 
995 /**
996  * List all jobs and ask user to select one
997  */
SelectJobDbr(UaContext * ua,JobDbRecord * jr)998 int SelectJobDbr(UaContext* ua, JobDbRecord* jr)
999 {
1000   ua->db->ListJobRecords(ua->jcr, jr, "", NULL, 0, 0, NULL, NULL, 0, 0, 0,
1001                          ua->send, HORZ_LIST);
1002   if (!GetPint(ua, _("Enter the JobId to select: "))) { return 0; }
1003 
1004   jr->JobId = ua->int64_val;
1005   if (!ua->db->GetJobRecord(ua->jcr, jr)) {
1006     ua->ErrorMsg("%s", ua->db->strerror());
1007     return 0;
1008   }
1009 
1010   return jr->JobId;
1011 }
1012 
1013 /**
1014  * Scan what the user has entered looking for:
1015  *
1016  * jobid=nn
1017  *
1018  * if error or not found, put up a list of Jobs
1019  * to choose from.
1020  *
1021  * returns: 0 on error
1022  *          JobId on success and fills in JobDbRecord
1023  */
GetJobDbr(UaContext * ua,JobDbRecord * jr)1024 int GetJobDbr(UaContext* ua, JobDbRecord* jr)
1025 {
1026   int i;
1027 
1028   for (i = 1; i < ua->argc; i++) {
1029     if (Bstrcasecmp(ua->argk[i], NT_("ujobid")) && ua->argv[i]) {
1030       jr->JobId = 0;
1031       bstrncpy(jr->Job, ua->argv[i], sizeof(jr->Job));
1032     } else if (Bstrcasecmp(ua->argk[i], NT_("jobid")) && ua->argv[i]) {
1033       jr->JobId = str_to_int64(ua->argv[i]);
1034       jr->Job[0] = 0;
1035     } else {
1036       continue;
1037     }
1038     if (!ua->db->GetJobRecord(ua->jcr, jr)) {
1039       ua->ErrorMsg(_("Could not find Job \"%s\": ERR=%s"), ua->argv[i],
1040                    ua->db->strerror());
1041       jr->JobId = 0;
1042       break;
1043     }
1044     return jr->JobId;
1045   }
1046 
1047   jr->JobId = 0;
1048   jr->Job[0] = 0;
1049 
1050   for (i = 1; i < ua->argc; i++) {
1051     if ((Bstrcasecmp(ua->argk[i], NT_("jobname")) ||
1052          Bstrcasecmp(ua->argk[i], NT_("job"))) &&
1053         ua->argv[i]) {
1054       jr->JobId = 0;
1055       bstrncpy(jr->Name, ua->argv[i], sizeof(jr->Name));
1056       break;
1057     }
1058   }
1059 
1060   if (!SelectJobDbr(ua, jr)) { /* try once more */
1061     return 0;
1062   }
1063 
1064   return jr->JobId;
1065 }
1066 
1067 /**
1068  * Implement unique set of prompts
1069  */
StartPrompt(UaContext * ua,const char * msg)1070 void StartPrompt(UaContext* ua, const char* msg)
1071 {
1072   if (ua->max_prompts == 0) {
1073     ua->max_prompts = 10;
1074     ua->prompt = (char**)malloc(sizeof(char*) * ua->max_prompts);
1075   }
1076   ua->num_prompts = 1;
1077   ua->prompt[0] = strdup(msg);
1078 }
1079 
1080 /**
1081  * Add to prompts -- keeping them unique
1082  */
AddPrompt(UaContext * ua,const char * prompt)1083 void AddPrompt(UaContext* ua, const char* prompt)
1084 {
1085   if (ua->num_prompts == ua->max_prompts) {
1086     ua->max_prompts *= 2;
1087     ua->prompt = (char**)realloc(ua->prompt, sizeof(char*) * ua->max_prompts);
1088   }
1089 
1090   for (int i = 1; i < ua->num_prompts; i++) {
1091     if (bstrcmp(ua->prompt[i], prompt)) { return; }
1092   }
1093 
1094   ua->prompt[ua->num_prompts++] = strdup(prompt);
1095 }
1096 
AddPrompt(UaContext * ua,std::string && prompt)1097 void AddPrompt(UaContext* ua, std::string&& prompt)
1098 {
1099   std::string p{prompt};
1100   AddPrompt(ua, p.c_str());
1101 }
1102 
1103 /**
1104  * Display prompts and get user's choice
1105  *
1106  * Returns: -1 on error
1107  *           index base 0 on success, and choice is copied to prompt if not NULL
1108  *           prompt is set to the chosen prompt item string
1109  */
DoPrompt(UaContext * ua,const char * automsg,const char * msg,char * prompt,int max_prompt)1110 int DoPrompt(UaContext* ua,
1111              const char* automsg,
1112              const char* msg,
1113              char* prompt,
1114              int max_prompt)
1115 {
1116   int item;
1117   PoolMem pmsg(PM_MESSAGE);
1118   BareosSocket* user = ua->UA_sock;
1119 
1120   if (prompt) { *prompt = 0; }
1121   if (ua->num_prompts == 2) {
1122     item = 1;
1123     if (prompt) { bstrncpy(prompt, ua->prompt[1], max_prompt); }
1124     if (!ua->api && !ua->runscript) {
1125       ua->SendMsg(_("Automatically selected %s: %s\n"), automsg, ua->prompt[1]);
1126     }
1127     goto done;
1128   }
1129 
1130   /*
1131    * If running non-interactive, bail out
1132    */
1133   if (ua->batch) {
1134     /*
1135      * First print the choices he wanted to make
1136      */
1137     ua->SendMsg(ua->prompt[0]);
1138     for (int i = 1; i < ua->num_prompts; i++) {
1139       ua->SendMsg("%6d: %s\n", i, ua->prompt[i]);
1140     }
1141 
1142     /*
1143      * Now print error message
1144      */
1145     ua->SendMsg(_("Your request has multiple choices for \"%s\". Selection is "
1146                   "not possible in batch mode.\n"),
1147                 automsg);
1148     item = -1;
1149     goto done;
1150   }
1151 
1152   if (ua->api) { user->signal(BNET_START_SELECT); }
1153 
1154   ua->SendMsg(ua->prompt[0]);
1155   for (int i = 1; i < ua->num_prompts; i++) {
1156     if (ua->api) {
1157       ua->SendMsg("%s", ua->prompt[i]);
1158     } else {
1159       ua->SendMsg("%6d: %s\n", i, ua->prompt[i]);
1160     }
1161   }
1162 
1163   if (ua->api) { user->signal(BNET_END_SELECT); }
1164 
1165   while (1) {
1166     /*
1167      * First item is the prompt string, not the items
1168      */
1169     if (ua->num_prompts == 1) {
1170       ua->ErrorMsg(_("Selection list for \"%s\" is empty!\n"), automsg);
1171       item = -1; /* list is empty ! */
1172       break;
1173     }
1174     if (ua->num_prompts == 2) {
1175       item = 1;
1176       ua->SendMsg(_("Automatically selected: %s\n"), ua->prompt[1]);
1177       if (prompt) { bstrncpy(prompt, ua->prompt[1], max_prompt); }
1178       break;
1179     } else {
1180       Mmsg(pmsg, "%s (1-%d): ", msg, ua->num_prompts - 1);
1181     }
1182 
1183     /*
1184      * Either a . or an @ will get you out of the loop
1185      */
1186     if (ua->api) { user->signal(BNET_SELECT_INPUT); }
1187 
1188     if (!GetPint(ua, pmsg.c_str())) {
1189       item = -1; /* error */
1190       ua->InfoMsg(_("Selection aborted, nothing done.\n"));
1191       break;
1192     }
1193     item = ua->pint32_val;
1194     if (item < 1 || item >= ua->num_prompts) {
1195       ua->WarningMsg(_("Please enter a number between 1 and %d\n"),
1196                      ua->num_prompts - 1);
1197       continue;
1198     }
1199     if (prompt) { bstrncpy(prompt, ua->prompt[item], max_prompt); }
1200     break;
1201   }
1202 
1203 done:
1204   for (int i = 0; i < ua->num_prompts; i++) { free(ua->prompt[i]); }
1205   ua->num_prompts = 0;
1206 
1207   return (item > 0) ? (item - 1) : item;
1208 }
1209 
1210 /**
1211  * We scan what the user has entered looking for :
1212  * - storage=<storage-resource>
1213  * - job=<job_name>
1214  * - jobid=<jobid>
1215  * - ?              (prompt him with storage list)
1216  * - <some-error>   (prompt him with storage list)
1217  *
1218  * If use_default is set, we assume that any keyword without a value
1219  * is the name of the Storage resource wanted.
1220  *
1221  * If autochangers_only is given, we limit the output to autochangers only.
1222  */
get_storage_resource(UaContext * ua,bool use_default,bool autochangers_only)1223 StorageResource* get_storage_resource(UaContext* ua,
1224                                       bool use_default,
1225                                       bool autochangers_only)
1226 {
1227   int i;
1228   JobControlRecord* jcr;
1229   int jobid;
1230   char ed1[50];
1231   char* StoreName = NULL;
1232   StorageResource* store = NULL;
1233 
1234   Dmsg1(100, "get_storage_resource: autochangers_only is %d\n",
1235         autochangers_only);
1236 
1237   for (i = 1; i < ua->argc; i++) {
1238     /*
1239      * Ignore any zapped keyword.
1240      */
1241     if (*ua->argk[i] == 0) { continue; }
1242     if (use_default && !ua->argv[i]) {
1243       /*
1244        * Ignore barcode, barcodes, encrypt, scan and slots keywords.
1245        */
1246       if (Bstrcasecmp("barcode", ua->argk[i]) ||
1247           Bstrcasecmp("barcodes", ua->argk[i]) ||
1248           Bstrcasecmp("encrypt", ua->argk[i]) ||
1249           Bstrcasecmp("scan", ua->argk[i]) ||
1250           Bstrcasecmp("slots", ua->argk[i])) {
1251         continue;
1252       }
1253       /*
1254        * Default argument is storage
1255        */
1256       if (StoreName) {
1257         ua->ErrorMsg(_("Storage name given twice.\n"));
1258         return NULL;
1259       }
1260       StoreName = ua->argk[i];
1261       if (*StoreName == '?') {
1262         *StoreName = 0;
1263         break;
1264       }
1265     } else {
1266       if (Bstrcasecmp(ua->argk[i], NT_("storage")) ||
1267           Bstrcasecmp(ua->argk[i], NT_("sd"))) {
1268         StoreName = ua->argv[i];
1269         break;
1270       } else if (Bstrcasecmp(ua->argk[i], NT_("jobid"))) {
1271         jobid = str_to_int64(ua->argv[i]);
1272         if (jobid <= 0) {
1273           ua->ErrorMsg(_("Expecting jobid=nn command, got: %s\n"), ua->argk[i]);
1274           return NULL;
1275         }
1276         if (!(jcr = get_jcr_by_id(jobid))) {
1277           ua->ErrorMsg(_("JobId %s is not running.\n"), edit_int64(jobid, ed1));
1278           return NULL;
1279         }
1280         store = jcr->impl->res.write_storage;
1281         FreeJcr(jcr);
1282         break;
1283       } else if (Bstrcasecmp(ua->argk[i], NT_("job")) ||
1284                  Bstrcasecmp(ua->argk[i], NT_("jobname"))) {
1285         if (!ua->argv[i]) {
1286           ua->ErrorMsg(_("Expecting job=xxx, got: %s.\n"), ua->argk[i]);
1287           return NULL;
1288         }
1289         if (!(jcr = get_jcr_by_partial_name(ua->argv[i]))) {
1290           ua->ErrorMsg(_("Job \"%s\" is not running.\n"), ua->argv[i]);
1291           return NULL;
1292         }
1293         store = jcr->impl->res.write_storage;
1294         FreeJcr(jcr);
1295         break;
1296       } else if (Bstrcasecmp(ua->argk[i], NT_("ujobid"))) {
1297         if (!ua->argv[i]) {
1298           ua->ErrorMsg(_("Expecting ujobid=xxx, got: %s.\n"), ua->argk[i]);
1299           return NULL;
1300         }
1301         if (!(jcr = get_jcr_by_full_name(ua->argv[i]))) {
1302           ua->ErrorMsg(_("Job \"%s\" is not running.\n"), ua->argv[i]);
1303           return NULL;
1304         }
1305         store = jcr->impl->res.write_storage;
1306         FreeJcr(jcr);
1307         break;
1308       }
1309     }
1310   }
1311 
1312   if (store && !ua->AclAccessOk(Storage_ACL, store->resource_name_)) {
1313     store = NULL;
1314   }
1315 
1316   if (!store && StoreName && StoreName[0] != 0) {
1317     store = ua->GetStoreResWithName(StoreName);
1318 
1319     if (!store) {
1320       ua->ErrorMsg(_("Storage resource \"%s\": not found\n"), StoreName);
1321     }
1322   }
1323 
1324   if (store && !ua->AclAccessOk(Storage_ACL, store->resource_name_)) {
1325     store = NULL;
1326   }
1327 
1328   /*
1329    * No keywords found, so present a selection list
1330    */
1331   if (!store) { store = select_storage_resource(ua, autochangers_only); }
1332 
1333   return store;
1334 }
1335 
1336 /**
1337  * Get drive that we are working with for this storage
1338  */
GetStorageDrive(UaContext * ua,StorageResource * store)1339 drive_number_t GetStorageDrive(UaContext* ua, StorageResource* store)
1340 {
1341   int i;
1342   char drivename[10];
1343   drive_number_t drive = kInvalidDriveNumber;
1344 
1345   /*
1346    * Get drive for autochanger if possible
1347    */
1348   i = FindArgWithValue(ua, NT_("drive"));
1349   if (i >= 0) {
1350     drive = static_cast<drive_number_t>(atoi(ua->argv[i]));
1351   } else if (store && store->autochanger) {
1352     drive_number_t drives;
1353 
1354     drives = GetNumDrives(ua, store);
1355 
1356     /*
1357      * If only one drive, default = 0
1358      */
1359     if (drives == 1) {
1360       drive = 0;
1361     } else {
1362       /*
1363        * Ask user to enter drive number
1364        */
1365       StartPrompt(ua, _("Select Drive:\n"));
1366       for (drive_number_t cnt = 0; cnt < drives; cnt++) {
1367         Bsnprintf(drivename, sizeof(drivename), "Drive %hd", cnt);
1368         AddPrompt(ua, drivename);
1369       }
1370       if (DoPrompt(ua, _("Drive"), _("Select drive"), drivename,
1371                    sizeof(drivename)) < 0) {
1372         drive = kInvalidDriveNumber; /* None */
1373       } else {
1374         sscanf(drivename, "Drive %hd", &drive);
1375       }
1376     }
1377   } else {
1378     /*
1379      * If only one drive, default = 0
1380      */
1381     drive = 0;
1382   }
1383 
1384   return drive;
1385 }
1386 
1387 /**
1388  * Get slot that we are working with for this storage
1389  */
GetStorageSlot(UaContext * ua,StorageResource * store)1390 slot_number_t GetStorageSlot(UaContext* ua, StorageResource* store)
1391 {
1392   int i;
1393   slot_number_t slot = -1;
1394 
1395   /*
1396    * Get slot for autochanger if possible
1397    */
1398   i = FindArgWithValue(ua, NT_("slot"));
1399   if (i >= 0) {
1400     slot = atoi(ua->argv[i]);
1401   } else if (store && store->autochanger) {
1402     /*
1403      * Ask user to enter slot number
1404      */
1405     ua->cmd[0] = 0;
1406     if (!GetCmd(ua, _("Enter autochanger slot: "))) {
1407       slot = -1; /* None */
1408     } else {
1409       slot = atoi(ua->cmd);
1410     }
1411   }
1412 
1413   return slot;
1414 }
1415 
1416 /**
1417  * Scan looking for mediatype=
1418  *
1419  *  if not found or error, put up selection list
1420  *
1421  *  Returns: 0 on error
1422  *           1 on success, MediaType is set
1423  */
GetMediaType(UaContext * ua,char * MediaType,int max_media)1424 int GetMediaType(UaContext* ua, char* MediaType, int max_media)
1425 {
1426   StorageResource* store;
1427   int i;
1428 
1429   i = FindArgWithValue(ua, NT_("mediatype"));
1430   if (i >= 0) {
1431     bstrncpy(MediaType, ua->argv[i], max_media);
1432     return 1;
1433   }
1434 
1435   StartPrompt(ua, _("Media Types defined in conf file:\n"));
1436 
1437   LockRes(my_config);
1438   foreach_res (store, R_STORAGE) {
1439     if (ua->AclAccessOk(Storage_ACL, store->resource_name_)) {
1440       AddPrompt(ua, store->media_type);
1441     }
1442   }
1443   UnlockRes(my_config);
1444 
1445   return (DoPrompt(ua, _("Media Type"), _("Select the Media Type"), MediaType,
1446                    max_media) < 0)
1447              ? 0
1448              : 1;
1449 }
1450 
GetLevelFromName(JobControlRecord * jcr,const char * level_name)1451 bool GetLevelFromName(JobControlRecord* jcr, const char* level_name)
1452 {
1453   bool found = false;
1454 
1455   /*
1456    * Look up level name and pull code
1457    */
1458   for (int i = 0; joblevels[i].level_name; i++) {
1459     if (Bstrcasecmp(level_name, joblevels[i].level_name)) {
1460       jcr->setJobLevel(joblevels[i].level);
1461       found = true;
1462       break;
1463     }
1464   }
1465 
1466   return found;
1467 }
1468 
1469 /**
1470  * Insert an JobId into the list of selected JobIds when its a unique new id.
1471  */
InsertSelectedJobid(alist * selected_jobids,JobId_t JobId)1472 static inline bool InsertSelectedJobid(alist* selected_jobids, JobId_t JobId)
1473 {
1474   bool found;
1475   JobId_t* selected_jobid = nullptr;
1476 
1477   found = false;
1478   foreach_alist (selected_jobid, selected_jobids) {
1479     if (*selected_jobid == JobId) {
1480       found = true;
1481       break;
1482     }
1483   }
1484 
1485   if (!found) {
1486     selected_jobid = (JobId_t*)malloc(sizeof(JobId_t));
1487     *selected_jobid = JobId;
1488     selected_jobids->append(selected_jobid);
1489     return true;
1490   }
1491 
1492   return false;
1493 }
1494 
1495 /**
1496  * Get a job selection, "reason" is used in user messages and can be: cancel,
1497  * limit, ...
1498  *
1499  * Returns: NULL on error
1500  *          alist on success with the selected jobids.
1501  */
select_jobs(UaContext * ua,const char * reason)1502 alist* select_jobs(UaContext* ua, const char* reason)
1503 {
1504   int i;
1505   int cnt = 0;
1506   int njobs = 0;
1507   JobControlRecord* jcr = NULL;
1508   bool select_all = false;
1509   bool select_by_state = false;
1510   alist* selected_jobids;
1511   const char* lst[] = {"job", "jobid", "ujobid", NULL};
1512   enum
1513   {
1514     none,
1515     all_jobs,
1516     created_jobs,
1517     blocked_jobs,
1518     waiting_jobs,
1519     running_jobs
1520   } selection_criterium;
1521 
1522   /*
1523    * Allocate a list for holding the selected JobIds.
1524    */
1525   selected_jobids = new alist(10, owned_by_alist);
1526 
1527   /*
1528    * See if "all" is given.
1529    */
1530   if (FindArg(ua, NT_("all")) > 0) { select_all = true; }
1531 
1532   /*
1533    * See if "state=" is given.
1534    */
1535   if (FindArgWithValue(ua, NT_("state")) > 0) { select_by_state = true; }
1536 
1537   /*
1538    * See if there are any jobid, job or ujobid keywords.
1539    */
1540   if (FindArgKeyword(ua, lst) > 0) {
1541     for (i = 1; i < ua->argc; i++) {
1542       if (Bstrcasecmp(ua->argk[i], NT_("jobid"))) {
1543         JobId_t JobId = str_to_int64(ua->argv[i]);
1544         if (!JobId) { continue; }
1545         if (!(jcr = get_jcr_by_id(JobId))) {
1546           ua->ErrorMsg(
1547               _("JobId %s is not running. Use Job name to %s inactive jobs.\n"),
1548               ua->argv[i], _(reason));
1549           continue;
1550         }
1551       } else if (Bstrcasecmp(ua->argk[i], NT_("job"))) {
1552         if (!ua->argv[i]) { continue; }
1553         if (!(jcr = get_jcr_by_partial_name(ua->argv[i]))) {
1554           ua->WarningMsg(
1555               _("Warning Job %s is not running. Continuing anyway ...\n"),
1556               ua->argv[i]);
1557           continue;
1558         }
1559       } else if (Bstrcasecmp(ua->argk[i], NT_("ujobid"))) {
1560         if (!ua->argv[i]) { continue; }
1561         if (!(jcr = get_jcr_by_full_name(ua->argv[i]))) {
1562           ua->WarningMsg(
1563               _("Warning Job %s is not running. Continuing anyway ...\n"),
1564               ua->argv[i]);
1565           continue;
1566         }
1567       }
1568 
1569       if (jcr) {
1570         if (jcr->impl->res.job &&
1571             !ua->AclAccessOk(Job_ACL, jcr->impl->res.job->resource_name_,
1572                              true)) {
1573           ua->ErrorMsg(_("Unauthorized command from this console.\n"));
1574           goto bail_out;
1575         }
1576 
1577         if (InsertSelectedJobid(selected_jobids, jcr->JobId)) { cnt++; }
1578 
1579         FreeJcr(jcr);
1580         jcr = NULL;
1581       }
1582     }
1583   }
1584 
1585   /*
1586    * If we didn't select any Jobs using jobid, job or ujobid keywords try other
1587    * selections.
1588    */
1589   if (cnt == 0) {
1590     char buf[1000];
1591     int tjobs = 0; /* Total # number jobs */
1592 
1593     /*
1594      * Count Jobs running
1595      */
1596     foreach_jcr (jcr) {
1597       if (jcr->JobId == 0) { /* This is us */
1598         continue;
1599       }
1600       tjobs++; /* Count of all jobs */
1601       if (!ua->AclAccessOk(Job_ACL, jcr->impl->res.job->resource_name_)) {
1602         continue; /* Skip not authorized */
1603       }
1604       njobs++; /* Count of authorized jobs */
1605     }
1606     endeach_jcr(jcr);
1607 
1608     if (njobs == 0) { /* No authorized */
1609       if (tjobs == 0) {
1610         ua->SendMsg(_("No Jobs running.\n"));
1611       } else {
1612         ua->SendMsg(_("None of your jobs are running.\n"));
1613       }
1614       goto bail_out;
1615     }
1616 
1617     if (select_all || select_by_state) {
1618       /*
1619        * Set selection criterium.
1620        */
1621       selection_criterium = none;
1622       if (select_all) {
1623         selection_criterium = all_jobs;
1624       } else {
1625         i = FindArgWithValue(ua, NT_("state"));
1626         if (i > 0) {
1627           if (Bstrcasecmp(ua->argv[i], NT_("created"))) {
1628             selection_criterium = created_jobs;
1629           }
1630 
1631           if (Bstrcasecmp(ua->argv[i], NT_("blocked"))) {
1632             selection_criterium = blocked_jobs;
1633           }
1634 
1635           if (Bstrcasecmp(ua->argv[i], NT_("waiting"))) {
1636             selection_criterium = waiting_jobs;
1637           }
1638 
1639           if (Bstrcasecmp(ua->argv[i], NT_("running"))) {
1640             selection_criterium = running_jobs;
1641           }
1642 
1643           if (selection_criterium == none) {
1644             ua->ErrorMsg(_(
1645                 "Illegal state either created, blocked, waiting or running\n"));
1646             goto bail_out;
1647           }
1648         }
1649       }
1650 
1651       /*
1652        * Select from all available Jobs the Jobs matching the selection
1653        * criterium.
1654        */
1655       foreach_jcr (jcr) {
1656         if (jcr->JobId == 0) { /* This is us */
1657           continue;
1658         }
1659 
1660         if (!ua->AclAccessOk(Job_ACL, jcr->impl->res.job->resource_name_)) {
1661           continue; /* Skip not authorized */
1662         }
1663 
1664         /*
1665          * See if we need to select this JobId.
1666          */
1667         switch (selection_criterium) {
1668           case all_jobs:
1669             break;
1670           case created_jobs:
1671             if (jcr->JobStatus != JS_Created) { continue; }
1672             break;
1673           case blocked_jobs:
1674             if (!jcr->job_started || jcr->JobStatus != JS_Blocked) { continue; }
1675             break;
1676           case waiting_jobs:
1677             if (!JobWaiting(jcr)) { continue; }
1678             break;
1679           case running_jobs:
1680             if (!jcr->job_started || jcr->JobStatus != JS_Running) { continue; }
1681             break;
1682           default:
1683             break;
1684         }
1685 
1686         InsertSelectedJobid(selected_jobids, jcr->JobId);
1687         ua->SendMsg(_("Selected Job %d for cancelling\n"), jcr->JobId);
1688       }
1689 
1690       if (selected_jobids->empty()) {
1691         ua->SendMsg(_("No Jobs selected.\n"));
1692         goto bail_out;
1693       }
1694 
1695       /*
1696        * Only ask for confirmation when not in batch mode and there is no yes on
1697        * the cmdline.
1698        */
1699       if (!ua->batch && FindArg(ua, NT_("yes")) == -1) {
1700         if (!GetYesno(ua, _("Confirm cancel (yes/no): ")) || !ua->pint32_val) {
1701           goto bail_out;
1702         }
1703       }
1704     } else {
1705       char temp[256];
1706       char JobName[MAX_NAME_LENGTH];
1707 
1708       /*
1709        * Interactivly select a Job.
1710        */
1711       StartPrompt(ua, _("Select Job:\n"));
1712       foreach_jcr (jcr) {
1713         char ed1[50];
1714         if (jcr->JobId == 0) { /* This is us */
1715           continue;
1716         }
1717         if (!ua->AclAccessOk(Job_ACL, jcr->impl->res.job->resource_name_)) {
1718           continue; /* Skip not authorized */
1719         }
1720         Bsnprintf(buf, sizeof(buf), _("JobId=%s Job=%s"),
1721                   edit_int64(jcr->JobId, ed1), jcr->Job);
1722         AddPrompt(ua, buf);
1723       }
1724       endeach_jcr(jcr);
1725 
1726       Bsnprintf(temp, sizeof(temp), _("Choose Job to %s"), _(reason));
1727       if (DoPrompt(ua, _("Job"), temp, buf, sizeof(buf)) < 0) { goto bail_out; }
1728 
1729       if (bstrcmp(reason, "cancel")) {
1730         if (ua->api && njobs == 1) {
1731           char nbuf[1000];
1732 
1733           Bsnprintf(nbuf, sizeof(nbuf), _("Cancel: %s\n\n%s"), buf,
1734                     _("Confirm cancel?"));
1735           if (!GetYesno(ua, nbuf) || !ua->pint32_val) { goto bail_out; }
1736         } else {
1737           if (njobs == 1) {
1738             if (!GetYesno(ua, _("Confirm cancel (yes/no): ")) ||
1739                 !ua->pint32_val) {
1740               goto bail_out;
1741             }
1742           }
1743         }
1744       }
1745 
1746       sscanf(buf, "JobId=%d Job=%127s", &njobs, JobName);
1747       jcr = get_jcr_by_full_name(JobName);
1748       if (!jcr) {
1749         ua->WarningMsg(_("Job \"%s\" not found.\n"), JobName);
1750         goto bail_out;
1751       }
1752 
1753       InsertSelectedJobid(selected_jobids, jcr->JobId);
1754       FreeJcr(jcr);
1755     }
1756   }
1757 
1758   return selected_jobids;
1759 
1760 bail_out:
1761   delete selected_jobids;
1762 
1763   return NULL;
1764 }
1765 
1766 /**
1767  * Get a slot selection.
1768  *
1769  * Returns: false on error
1770  *          true on success with the selected slots set in the slot_list.
1771  */
GetUserSlotList(UaContext * ua,char * slot_list,const char * argument,int num_slots)1772 bool GetUserSlotList(UaContext* ua,
1773                      char* slot_list,
1774                      const char* argument,
1775                      int num_slots)
1776 {
1777   int i, len, beg, end;
1778   const char* msg;
1779   char search_argument[20];
1780   char *p, *e, *h;
1781 
1782   /*
1783    * See if the argument given is found on the cmdline.
1784    */
1785   bstrncpy(search_argument, argument, sizeof(search_argument));
1786   i = FindArgWithValue(ua, search_argument);
1787   if (i == -1) { /* not found */
1788     /*
1789      * See if the last letter of search_argument is a 's'
1790      * When it is strip it and try if that argument is given.
1791      */
1792     len = strlen(search_argument);
1793     if (len > 0 && search_argument[len - 1] == 's') {
1794       search_argument[len - 1] = '\0';
1795       i = FindArgWithValue(ua, search_argument);
1796     }
1797   }
1798 
1799   if (i > 0) {
1800     /*
1801      * Scan slot list in ua->argv[i]
1802      */
1803     StripTrailingJunk(ua->argv[i]);
1804     for (p = ua->argv[i]; p && *p; p = e) {
1805       /*
1806        * Check for list
1807        */
1808       e = strchr(p, ',');
1809       if (e) { *e++ = 0; }
1810       /*
1811        * Check for range
1812        */
1813       h = strchr(p, '-'); /* range? */
1814       if (h == p) {
1815         msg = _("Negative numbers not permitted\n");
1816         goto bail_out;
1817       }
1818       if (h) {
1819         *h++ = 0;
1820         if (!IsAnInteger(h)) {
1821           msg = _("Range end is not integer.\n");
1822           goto bail_out;
1823         }
1824         SkipSpaces(&p);
1825         if (!IsAnInteger(p)) {
1826           msg = _("Range start is not an integer.\n");
1827           goto bail_out;
1828         }
1829         beg = atoi(p);
1830         end = atoi(h);
1831         if (end < beg) {
1832           msg = _("Range end not bigger than start.\n");
1833           goto bail_out;
1834         }
1835       } else {
1836         SkipSpaces(&p);
1837         if (!IsAnInteger(p)) {
1838           msg = _("Input value is not an integer.\n");
1839           goto bail_out;
1840         }
1841         beg = end = atoi(p);
1842       }
1843       if (beg <= 0 || end <= 0) {
1844         msg = _("Values must be be greater than zero.\n");
1845         goto bail_out;
1846       }
1847       if (end > num_slots) {
1848         msg = _("Slot too large.\n");
1849         goto bail_out;
1850       }
1851 
1852       /*
1853        * Turn on specified range
1854        */
1855       for (i = beg; i <= end; i++) { SetBit(i - 1, slot_list); }
1856     }
1857   } else {
1858     /*
1859      * Turn everything on
1860      */
1861     for (i = 1; i <= num_slots; i++) { SetBit(i - 1, slot_list); }
1862   }
1863 
1864   if (debug_level >= 100) {
1865     Dmsg0(100, "Slots turned on:\n");
1866     for (i = 1; i <= num_slots; i++) {
1867       if (BitIsSet(i - 1, slot_list)) { Dmsg1(100, "%d\n", i); }
1868     }
1869   }
1870 
1871   return true;
1872 
1873 bail_out:
1874   Dmsg1(100, "Problem with user selection ERR=%s\n", msg);
1875 
1876   return false;
1877 }
1878 
GetUserJobTypeSelection(UaContext * ua,int * jobtype)1879 bool GetUserJobTypeSelection(UaContext* ua, int* jobtype)
1880 {
1881   int i;
1882   char job_type[MAX_NAME_LENGTH];
1883 
1884   /* set returning jobtype to invalid */
1885   *jobtype = -1;
1886 
1887   if ((i = FindArgWithValue(ua, NT_("jobtype"))) >= 0) {
1888     bstrncpy(job_type, ua->argv[i], sizeof(job_type));
1889   } else {
1890     StartPrompt(ua, _("Jobtype to prune:\n"));
1891     for (i = 0; jobtypes[i].type_name; i++) {
1892       AddPrompt(ua, jobtypes[i].type_name);
1893     }
1894 
1895     if (DoPrompt(ua, _("JobType"), _("Select Job Type"), job_type,
1896                  sizeof(job_type)) < 0) {
1897       return false;
1898     }
1899   }
1900 
1901   for (i = 0; jobtypes[i].type_name; i++) {
1902     if (Bstrcasecmp(jobtypes[i].type_name, job_type)) { break; }
1903   }
1904 
1905   if (!jobtypes[i].type_name) {
1906     ua->WarningMsg(_("Illegal jobtype %s.\n"), job_type);
1907     return false;
1908   }
1909 
1910   *jobtype = jobtypes[i].job_type;
1911 
1912   return true;
1913 }
1914 
GetUserJobStatusSelection(UaContext * ua,int * jobstatus)1915 bool GetUserJobStatusSelection(UaContext* ua, int* jobstatus)
1916 {
1917   int i;
1918 
1919   if ((i = FindArgWithValue(ua, NT_("jobstatus"))) >= 0) {
1920     if (strlen(ua->argv[i]) == 1 && ua->argv[i][0] >= 'A' &&
1921         ua->argv[i][0] <= 'z') {
1922       *jobstatus = ua->argv[i][0];
1923     } else if (Bstrcasecmp(ua->argv[i], "terminated")) {
1924       *jobstatus = JS_Terminated;
1925     } else if (Bstrcasecmp(ua->argv[i], "warnings")) {
1926       *jobstatus = JS_Warnings;
1927     } else if (Bstrcasecmp(ua->argv[i], "canceled")) {
1928       *jobstatus = JS_Canceled;
1929     } else if (Bstrcasecmp(ua->argv[i], "running")) {
1930       *jobstatus = JS_Running;
1931     } else if (Bstrcasecmp(ua->argv[i], "error")) {
1932       *jobstatus = JS_ErrorTerminated;
1933     } else if (Bstrcasecmp(ua->argv[i], "fatal")) {
1934       *jobstatus = JS_FatalError;
1935     } else {
1936       /* invalid jobstatus */
1937       return false;
1938     }
1939   }
1940   return true;
1941 }
1942 
GetUserJobLevelSelection(UaContext * ua,int * joblevel)1943 bool GetUserJobLevelSelection(UaContext* ua, int* joblevel)
1944 {
1945   int i;
1946 
1947   if ((i = FindArgWithValue(ua, NT_("joblevel"))) >= 0) {
1948     if (strlen(ua->argv[i]) == 1 && ua->argv[i][0] >= 'A' &&
1949         ua->argv[i][0] <= 'z') {
1950       *joblevel = ua->argv[i][0];
1951     } else {
1952       /* invalid joblevel */
1953       return false;
1954     }
1955   }
1956   return true;
1957 }
1958 } /* namespace directordaemon */
1959