3 * Bacula Director -- User Agent Prompt and Selection code
5 * Kern Sibbald, October MMI
10 Copyright (C) 2001-2006 Kern Sibbald
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 version 2 as amended with additional clauses defined in the
15 file LICENSE in the main source directory.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 the file LICENSE for additional details.
27 /* Imported variables */
28 extern struct s_jl joblevels[];
32 * Confirm a retention period
34 int confirm_retention(UAContext *ua, utime_t *ret, const char *msg)
39 int yes_in_arg = find_arg(ua, NT_("yes"));
42 bsendmsg(ua, _("The current %s retention period is: %s\n"),
43 msg, edit_utime(*ret, ed1, sizeof(ed1)));
44 if (yes_in_arg != -1) {
47 if (!get_cmd(ua, _("Continue? (yes/mod/no): "))) {
50 if (strcasecmp(ua->cmd, _("mod")) == 0) {
51 if (!get_cmd(ua, _("Enter new retention period: "))) {
54 if (!duration_to_utime(ua->cmd, ret)) {
55 bsendmsg(ua, _("Invalid period.\n"));
60 if (is_yesno(ua->cmd, &val)) {
61 return val; /* is 1 for yes, 0 for no */
68 * Given a list of keywords, find the first one
69 * that is in the argument list.
70 * Returns: -1 if not found
71 * index into list (base 0) on success
73 int find_arg_keyword(UAContext *ua, const char **list)
75 for (int i=1; i<ua->argc; i++) {
76 for(int j=0; list[j]; j++) {
77 if (strcasecmp(list[j], ua->argk[i]) == 0) {
86 * Given one keyword, find the first one that
87 * is in the argument list.
88 * Returns: argk index (always gt 0)
91 int find_arg(UAContext *ua, const char *keyword)
93 for (int i=1; i<ua->argc; i++) {
94 if (strcasecmp(keyword, ua->argk[i]) == 0) {
102 * Given a single keyword, find it in the argument list, but
103 * it must have a value
104 * Returns: -1 if not found or no value
105 * list index (base 0) on success
107 int find_arg_with_value(UAContext *ua, const char *keyword)
109 for (int i=1; i<ua->argc; i++) {
110 if (strcasecmp(keyword, ua->argk[i]) == 0) {
122 * Given a list of keywords, prompt the user
125 * Returns: -1 on failure
126 * index into list (base 0) on success
128 int do_keyword_prompt(UAContext *ua, const char *msg, const char **list)
131 start_prompt(ua, _("You have the following choices:\n"));
132 for (i=0; list[i]; i++) {
133 add_prompt(ua, list[i]);
135 return do_prompt(ua, "", msg, NULL, 0);
140 * Select a Storage resource from prompt list
142 STORE *select_storage_resource(UAContext *ua)
144 char name[MAX_NAME_LENGTH];
147 start_prompt(ua, _("The defined Storage resources are:\n"));
149 foreach_res(store, R_STORAGE) {
150 if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
151 add_prompt(ua, store->hdr.name);
155 if (do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name)) < 0) {
158 store = (STORE *)GetResWithName(R_STORAGE, name);
163 * Select a FileSet resource from prompt list
165 FILESET *select_fileset_resource(UAContext *ua)
167 char name[MAX_NAME_LENGTH];
170 start_prompt(ua, _("The defined FileSet resources are:\n"));
172 foreach_res(fs, R_FILESET) {
173 if (acl_access_ok(ua, FileSet_ACL, fs->hdr.name)) {
174 add_prompt(ua, fs->hdr.name);
178 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), name, sizeof(name)) < 0) {
181 fs = (FILESET *)GetResWithName(R_FILESET, name);
187 * Get a catalog resource from prompt list
189 CAT *get_catalog_resource(UAContext *ua)
191 char name[MAX_NAME_LENGTH];
195 for (i=1; i<ua->argc; i++) {
196 if (strcasecmp(ua->argk[i], NT_("catalog")) == 0 && ua->argv[i]) {
197 if (acl_access_ok(ua, Catalog_ACL, ua->argv[i])) {
198 catalog = (CAT *)GetResWithName(R_CATALOG, ua->argv[i]);
204 start_prompt(ua, _("The defined Catalog resources are:\n"));
206 foreach_res(catalog, R_CATALOG) {
207 if (acl_access_ok(ua, Catalog_ACL, catalog->hdr.name)) {
208 add_prompt(ua, catalog->hdr.name);
212 if (do_prompt(ua, _("Catalog"), _("Select Catalog resource"), name, sizeof(name)) < 0) {
215 catalog = (CAT *)GetResWithName(R_CATALOG, name);
222 * Select a Job resource from prompt list
224 JOB *select_job_resource(UAContext *ua)
226 char name[MAX_NAME_LENGTH];
229 start_prompt(ua, _("The defined Job resources are:\n"));
231 foreach_res(job, R_JOB) {
232 if (acl_access_ok(ua, Job_ACL, job->hdr.name)) {
233 add_prompt(ua, job->hdr.name);
237 if (do_prompt(ua, _("Job"), _("Select Job resource"), name, sizeof(name)) < 0) {
240 job = (JOB *)GetResWithName(R_JOB, name);
245 * Select a Restore Job resource from prompt list
247 JOB *select_restore_job_resource(UAContext *ua)
249 char name[MAX_NAME_LENGTH];
252 start_prompt(ua, _("The defined Restore Job resources are:\n"));
254 foreach_res(job, R_JOB) {
255 if (job->JobType == JT_RESTORE && acl_access_ok(ua, Job_ACL, job->hdr.name)) {
256 add_prompt(ua, job->hdr.name);
260 if (do_prompt(ua, _("Job"), _("Select Restore Job"), name, sizeof(name)) < 0) {
263 job = (JOB *)GetResWithName(R_JOB, name);
270 * Select a client resource from prompt list
272 CLIENT *select_client_resource(UAContext *ua)
274 char name[MAX_NAME_LENGTH];
277 start_prompt(ua, _("The defined Client resources are:\n"));
279 foreach_res(client, R_CLIENT) {
280 if (acl_access_ok(ua, Client_ACL, client->hdr.name)) {
281 add_prompt(ua, client->hdr.name);
285 if (do_prompt(ua, _("Client"), _("Select Client (File daemon) resource"), name, sizeof(name)) < 0) {
288 client = (CLIENT *)GetResWithName(R_CLIENT, name);
293 * Get client resource, start by looking for
294 * client=<client-name>
295 * if we don't find the keyword, we prompt the user.
297 CLIENT *get_client_resource(UAContext *ua)
299 CLIENT *client = NULL;
302 for (i=1; i<ua->argc; i++) {
303 if ((strcasecmp(ua->argk[i], NT_("client")) == 0 ||
304 strcasecmp(ua->argk[i], NT_("fd")) == 0) && ua->argv[i]) {
305 if (!acl_access_ok(ua, Client_ACL, ua->argv[i])) {
308 client = (CLIENT *)GetResWithName(R_CLIENT, ua->argv[i]);
312 bsendmsg(ua, _("Error: Client resource %s does not exist.\n"), ua->argv[i]);
316 return select_client_resource(ua);
319 /* Scan what the user has entered looking for:
321 * client=<client-name>
323 * if error or not found, put up a list of client DBRs
326 * returns: 0 on error
327 * 1 on success and fills in CLIENT_DBR
329 int get_client_dbr(UAContext *ua, CLIENT_DBR *cr)
333 if (cr->Name[0]) { /* If name already supplied */
334 if (db_get_client_record(ua->jcr, ua->db, cr)) {
337 bsendmsg(ua, _("Could not find Client %s: ERR=%s"), cr->Name, db_strerror(ua->db));
339 for (i=1; i<ua->argc; i++) {
340 if ((strcasecmp(ua->argk[i], NT_("client")) == 0 ||
341 strcasecmp(ua->argk[i], NT_("fd")) == 0) && ua->argv[i]) {
342 if (!acl_access_ok(ua, Client_ACL, ua->argv[i])) {
345 bstrncpy(cr->Name, ua->argv[i], sizeof(cr->Name));
346 if (!db_get_client_record(ua->jcr, ua->db, cr)) {
347 bsendmsg(ua, _("Could not find Client \"%s\": ERR=%s"), ua->argv[i],
348 db_strerror(ua->db));
355 if (!select_client_dbr(ua, cr)) { /* try once more by proposing a list */
362 * Select a Client record from the catalog
363 * Returns 1 on success
366 int select_client_dbr(UAContext *ua, CLIENT_DBR *cr)
369 char name[MAX_NAME_LENGTH];
375 if (!db_get_client_ids(ua->jcr, ua->db, &num_clients, &ids)) {
376 bsendmsg(ua, _("Error obtaining client ids. ERR=%s\n"), db_strerror(ua->db));
379 if (num_clients <= 0) {
380 bsendmsg(ua, _("No clients defined. You must run a job before using this command.\n"));
384 start_prompt(ua, _("Defined Clients:\n"));
385 for (i=0; i < num_clients; i++) {
386 ocr.ClientId = ids[i];
387 if (!db_get_client_record(ua->jcr, ua->db, &ocr) ||
388 !acl_access_ok(ua, Client_ACL, ocr.Name)) {
391 add_prompt(ua, ocr.Name);
394 if (do_prompt(ua, _("Client"), _("Select the Client"), name, sizeof(name)) < 0) {
397 memset(&ocr, 0, sizeof(ocr));
398 bstrncpy(ocr.Name, name, sizeof(ocr.Name));
400 if (!db_get_client_record(ua->jcr, ua->db, &ocr)) {
401 bsendmsg(ua, _("Could not find Client \"%s\": ERR=%s"), name, db_strerror(ua->db));
404 memcpy(cr, &ocr, sizeof(ocr));
410 /* Scan what the user has entered looking for:
414 * if error or not found, put up a list of pool DBRs
417 * returns: false on error
418 * true on success and fills in POOL_DBR
420 bool get_pool_dbr(UAContext *ua, POOL_DBR *pr)
422 if (pr->Name[0]) { /* If name already supplied */
423 if (db_get_pool_record(ua->jcr, ua->db, pr) &&
424 acl_access_ok(ua, Pool_ACL, pr->Name)) {
427 bsendmsg(ua, _("Could not find Pool \"%s\": ERR=%s"), pr->Name, db_strerror(ua->db));
429 if (!select_pool_dbr(ua, pr)) { /* try once more */
436 * Select a Pool record from the catalog
438 bool select_pool_dbr(UAContext *ua, POOL_DBR *pr)
441 char name[MAX_NAME_LENGTH];
445 for (i=1; i<ua->argc; i++) {
446 if (strcasecmp(ua->argk[i], NT_("pool")) == 0 && ua->argv[i] &&
447 acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
448 bstrncpy(pr->Name, ua->argv[i], sizeof(pr->Name));
449 if (!db_get_pool_record(ua->jcr, ua->db, pr)) {
450 bsendmsg(ua, _("Could not find Pool \"%s\": ERR=%s"), ua->argv[i],
451 db_strerror(ua->db));
460 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
461 bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"), db_strerror(ua->db));
464 if (num_pools <= 0) {
465 bsendmsg(ua, _("No pools defined. Use the \"create\" command to create one.\n"));
469 start_prompt(ua, _("Defined Pools:\n"));
470 for (i=0; i < num_pools; i++) {
472 if (!db_get_pool_record(ua->jcr, ua->db, &opr) ||
473 !acl_access_ok(ua, Pool_ACL, opr.Name)) {
476 add_prompt(ua, opr.Name);
479 if (do_prompt(ua, _("Pool"), _("Select the Pool"), name, sizeof(name)) < 0) {
482 memset(&opr, 0, sizeof(opr));
483 bstrncpy(opr.Name, name, sizeof(opr.Name));
485 if (!db_get_pool_record(ua->jcr, ua->db, &opr)) {
486 bsendmsg(ua, _("Could not find Pool \"%s\": ERR=%s"), name, db_strerror(ua->db));
489 memcpy(pr, &opr, sizeof(opr));
494 * Select a Pool and a Media (Volume) record from the database
496 int select_pool_and_media_dbr(UAContext *ua, POOL_DBR *pr, MEDIA_DBR *mr)
499 if (!select_media_dbr(ua, mr)) {
502 memset(pr, 0, sizeof(POOL_DBR));
503 pr->PoolId = mr->PoolId;
504 if (!db_get_pool_record(ua->jcr, ua->db, pr)) {
505 bsendmsg(ua, "%s", db_strerror(ua->db));
508 if (!acl_access_ok(ua, Pool_ACL, pr->Name)) {
509 bsendmsg(ua, _("No access to Pool \"%s\"\n"), pr->Name);
515 /* Select a Media (Volume) record from the database */
516 int select_media_dbr(UAContext *ua, MEDIA_DBR *mr)
520 memset(mr, 0, sizeof(MEDIA_DBR));
522 i = find_arg_with_value(ua, "volume");
524 bstrncpy(mr->VolumeName, ua->argv[i], sizeof(mr->VolumeName));
526 if (mr->VolumeName[0] == 0) {
528 memset(&pr, 0, sizeof(pr));
529 /* Get the pool from pool=<pool-name> */
530 if (!get_pool_dbr(ua, &pr)) {
533 mr->PoolId = pr.PoolId;
534 db_list_media_records(ua->jcr, ua->db, mr, prtit, ua, HORZ_LIST);
535 if (!get_cmd(ua, _("Enter MediaId or Volume name: "))) {
538 if (is_a_number(ua->cmd)) {
539 mr->MediaId = str_to_int64(ua->cmd);
541 bstrncpy(mr->VolumeName, ua->cmd, sizeof(mr->VolumeName));
545 if (!db_get_media_record(ua->jcr, ua->db, mr)) {
546 bsendmsg(ua, "%s", db_strerror(ua->db));
554 * Select a pool resource from prompt list
556 POOL *select_pool_resource(UAContext *ua)
558 char name[MAX_NAME_LENGTH];
561 start_prompt(ua, _("The defined Pool resources are:\n"));
563 foreach_res(pool, R_POOL) {
564 if (acl_access_ok(ua, Pool_ACL, pool->hdr.name)) {
565 add_prompt(ua, pool->hdr.name);
569 if (do_prompt(ua, _("Pool"), _("Select Pool resource"), name, sizeof(name)) < 0) {
572 pool = (POOL *)GetResWithName(R_POOL, name);
578 * If you are thinking about using it, you
579 * probably want to use select_pool_dbr()
580 * or get_pool_dbr() above.
582 POOL *get_pool_resource(UAContext *ua)
587 i = find_arg_with_value(ua, "pool");
588 if (i >= 0 && acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
589 pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
593 bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
595 return select_pool_resource(ua);
599 * List all jobs and ask user to select one
601 int select_job_dbr(UAContext *ua, JOB_DBR *jr)
603 db_list_job_records(ua->jcr, ua->db, jr, prtit, ua, HORZ_LIST);
604 if (!get_pint(ua, _("Enter the JobId to select: "))) {
607 jr->JobId = ua->int64_val;
608 if (!db_get_job_record(ua->jcr, ua->db, jr)) {
609 bsendmsg(ua, "%s", db_strerror(ua->db));
617 /* Scan what the user has entered looking for:
621 * if error or not found, put up a list of Jobs
624 * returns: 0 on error
625 * JobId on success and fills in JOB_DBR
627 int get_job_dbr(UAContext *ua, JOB_DBR *jr)
631 for (i=1; i<ua->argc; i++) {
632 if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0 && ua->argv[i]) {
634 bstrncpy(jr->Job, ua->argv[i], sizeof(jr->Job));
635 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0 && ua->argv[i]) {
636 jr->JobId = str_to_int64(ua->argv[i]);
641 if (!db_get_job_record(ua->jcr, ua->db, jr)) {
642 bsendmsg(ua, _("Could not find Job \"%s\": ERR=%s"), ua->argv[i],
643 db_strerror(ua->db));
653 for (i=1; i<ua->argc; i++) {
654 if ((strcasecmp(ua->argk[i], NT_("jobname")) == 0 ||
655 strcasecmp(ua->argk[i], NT_("job")) == 0) && ua->argv[i]) {
657 bstrncpy(jr->Name, ua->argv[i], sizeof(jr->Name));
661 if (!select_job_dbr(ua, jr)) { /* try once more */
668 * Implement unique set of prompts
670 void start_prompt(UAContext *ua, const char *msg)
672 if (ua->max_prompts == 0) {
673 ua->max_prompts = 10;
674 ua->prompt = (char **)bmalloc(sizeof(char *) * ua->max_prompts);
677 ua->prompt[0] = bstrdup(msg);
681 * Add to prompts -- keeping them unique
683 void add_prompt(UAContext *ua, const char *prompt)
686 if (ua->num_prompts == ua->max_prompts) {
687 ua->max_prompts *= 2;
688 ua->prompt = (char **)brealloc(ua->prompt, sizeof(char *) *
691 for (i=1; i < ua->num_prompts; i++) {
692 if (strcmp(ua->prompt[i], prompt) == 0) {
696 ua->prompt[ua->num_prompts++] = bstrdup(prompt);
700 * Display prompts and get user's choice
702 * Returns: -1 on error
703 * index base 0 on success, and choice
704 * is copied to prompt if not NULL
705 * prompt is set to the chosen prompt item string
707 int do_prompt(UAContext *ua, const char *automsg, const char *msg, char *prompt, int max_prompt)
710 char pmsg[MAXSTRING];
715 if (ua->num_prompts == 2) {
718 bstrncpy(prompt, ua->prompt[1], max_prompt);
720 bsendmsg(ua, _("Automatically selected %s: %s\n"), automsg, ua->prompt[1]);
723 /* If running non-interactive, bail out */
725 bsendmsg(ua, _("Cannot select %s in batch mode.\n"), automsg);
729 // bnet_sig(ua->UA_sock, BNET_START_SELECT);
730 bsendmsg(ua, ua->prompt[0]);
731 for (i=1; i < ua->num_prompts; i++) {
732 bsendmsg(ua, "%6d: %s\n", i, ua->prompt[i]);
734 // bnet_sig(ua->UA_sock, BNET_END_SELECT);
737 /* First item is the prompt string, not the items */
738 if (ua->num_prompts == 1) {
739 bsendmsg(ua, _("Selection is empty!\n"));
740 item = -1; /* list is empty ! */
743 if (ua->num_prompts == 2) {
745 bsendmsg(ua, _("Item 1 selected automatically.\n"));
747 bstrncpy(prompt, ua->prompt[1], max_prompt);
751 sprintf(pmsg, "%s (1-%d): ", msg, ua->num_prompts-1);
753 /* Either a . or an @ will get you out of the loop */
754 if (!get_pint(ua, pmsg)) {
755 item = -1; /* error */
756 bsendmsg(ua, _("Selection aborted, nothing done.\n"));
759 item = ua->pint32_val;
760 if (item < 1 || item >= ua->num_prompts) {
761 bsendmsg(ua, _("Please enter a number between 1 and %d\n"), ua->num_prompts-1);
765 bstrncpy(prompt, ua->prompt[item], max_prompt);
771 for (i=0; i < ua->num_prompts; i++) {
775 return item>0 ? item-1 : item;
780 * We scan what the user has entered looking for
781 * storage=<storage-resource>
784 * ? (prompt him with storage list)
785 * <some-error> (prompt him with storage list)
787 * If use_default is set, we assume that any keyword without a value
788 * is the name of the Storage resource wanted.
790 STORE *get_storage_resource(UAContext *ua, bool use_default)
792 char *store_name = NULL;
799 for (i=1; i<ua->argc; i++) {
800 if (use_default && !ua->argv[i]) {
801 /* Ignore slots, scan and barcode(s) keywords */
802 if (strcasecmp("scan", ua->argk[i]) == 0 ||
803 strcasecmp("barcode", ua->argk[i]) == 0 ||
804 strcasecmp("barcodes", ua->argk[i]) == 0 ||
805 strcasecmp("slots", ua->argk[i]) == 0) {
808 /* Default argument is storage */
810 bsendmsg(ua, _("Storage name given twice.\n"));
813 store_name = ua->argk[i];
814 if (*store_name == '?') {
819 if (strcasecmp(ua->argk[i], NT_("storage")) == 0 ||
820 strcasecmp(ua->argk[i], NT_("sd")) == 0) {
821 store_name = ua->argv[i];
824 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
825 jobid = str_to_int64(ua->argv[i]);
827 bsendmsg(ua, _("Expecting jobid=nn command, got: %s\n"), ua->argk[i]);
830 if (!(jcr=get_jcr_by_id(jobid))) {
831 bsendmsg(ua, _("JobId %s is not running.\n"), edit_int64(jobid, ed1));
838 } else if (strcasecmp(ua->argk[i], NT_("job")) == 0 ||
839 strcasecmp(ua->argk[i], NT_("jobname")) == 0) {
841 bsendmsg(ua, _("Expecting job=xxx, got: %s.\n"), ua->argk[i]);
844 if (!(jcr=get_jcr_by_partial_name(ua->argv[i]))) {
845 bsendmsg(ua, _("Job \"%s\" is not running.\n"), ua->argv[i]);
851 } else if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0) {
853 bsendmsg(ua, _("Expecting ujobid=xxx, got: %s.\n"), ua->argk[i]);
856 if (!(jcr=get_jcr_by_full_name(ua->argv[i]))) {
857 bsendmsg(ua, _("Job \"%s\" is not running.\n"), ua->argv[i]);
866 if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
870 if (!store && store_name && store_name[0] != 0) {
871 store = (STORE *)GetResWithName(R_STORAGE, store_name);
873 bsendmsg(ua, _("Storage resource \"%s\": not found\n"), store_name);
876 if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
879 /* No keywords found, so present a selection list */
881 store = select_storage_resource(ua);
886 /* Get drive that we are working with for this storage */
887 int get_storage_drive(UAContext *ua, STORE *store)
890 /* Get drive for autochanger if possible */
891 i = find_arg_with_value(ua, "drive");
893 drive = atoi(ua->argv[i]);
894 } else if (store && store->autochanger) {
895 /* If our structure is not set ask SD for # drives */
896 if (store->drives == 0) {
897 store->drives = get_num_drives_from_SD(ua);
899 /* If only one drive, default = 0 */
900 if (store->drives == 1) {
903 /* Ask user to enter drive number */
905 if (!get_cmd(ua, _("Enter autochanger drive[0]: "))) {
906 drive = -1; /* None */
908 drive = atoi(ua->cmd);
915 /* Get slot that we are working with for this storage */
916 int get_storage_slot(UAContext *ua, STORE *store)
919 /* Get slot for autochanger if possible */
920 i = find_arg_with_value(ua, "slot");
922 slot = atoi(ua->argv[i]);
923 } else if (store && store->autochanger) {
924 /* Ask user to enter slot number */
926 if (!get_cmd(ua, _("Enter autochanger slot: "))) {
927 slot = -1; /* None */
929 slot = atoi(ua->cmd);
938 * Scan looking for mediatype=
940 * if not found or error, put up selection list
942 * Returns: 0 on error
943 * 1 on success, MediaType is set
945 int get_media_type(UAContext *ua, char *MediaType, int max_media)
950 i = find_arg_with_value(ua, "mediatype");
952 bstrncpy(MediaType, ua->argv[i], max_media);
956 start_prompt(ua, _("Media Types defined in conf file:\n"));
958 foreach_res(store, R_STORAGE) {
959 add_prompt(ua, store->media_type);
962 return (do_prompt(ua, _("Media Type"), _("Select the Media Type"), MediaType, max_media) < 0) ? 0 : 1;
965 bool get_level_from_name(JCR *jcr, const char *level_name)
967 /* Look up level name and pull code */
969 for (int i=0; joblevels[i].level_name; i++) {
970 if (strcasecmp(level_name, joblevels[i].level_name) == 0) {
971 jcr->JobLevel = joblevels[i].level;