2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-2014 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from many
7 others, a complete list can be found in the file AUTHORS.
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.
14 Bacula® is a registered trademark of Kern Sibbald.
18 * Bacula Director -- User Agent Output Commands
19 * I.e. messages, listing database, showing resources, ...
21 * Kern Sibbald, September MM
29 /* Imported subroutines */
31 /* Imported variables */
33 /* Imported functions */
35 /* Forward referenced functions */
36 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist);
37 static bool list_nextvol(UAContext *ua, int ndays);
40 * Turn auto display of console messages on/off
42 int autodisplay_cmd(UAContext *ua, const char *cmd)
44 static const char *kw[] = {
49 switch (find_arg_keyword(ua, kw)) {
51 ua->auto_display_messages = true;
54 ua->auto_display_messages = false;
57 ua->error_msg(_("ON or OFF keyword missing.\n"));
64 * Turn GUI mode on/off
66 int gui_cmd(UAContext *ua, const char *cmd)
68 static const char *kw[] = {
73 switch (find_arg_keyword(ua, kw)) {
75 ua->jcr->gui = ua->gui = true;
78 ua->jcr->gui = ua->gui = false;
81 ua->error_msg(_("ON or OFF keyword missing.\n"));
88 * Enter with Resources locked
90 static void show_disabled_jobs(UAContext *ua)
94 foreach_res(job, R_JOB) {
95 if (!acl_access_ok(ua, Job_ACL, job->name())) {
101 ua->send_msg(_("Disabled Jobs:\n"));
103 ua->send_msg(" %s\n", job->name());
107 ua->send_msg(_("No disabled Jobs.\n"));
111 struct showstruct {const char *res_name; int type;};
112 static struct showstruct reses[] = {
113 {NT_("directors"), R_DIRECTOR},
114 {NT_("clients"), R_CLIENT},
115 {NT_("counters"), R_COUNTER},
116 {NT_("devices"), R_DEVICE},
117 {NT_("jobs"), R_JOB},
118 {NT_("storages"), R_STORAGE},
119 {NT_("catalogs"), R_CATALOG},
120 {NT_("schedules"), R_SCHEDULE},
121 {NT_("filesets"), R_FILESET},
122 {NT_("pools"), R_POOL},
123 {NT_("messages"), R_MSGS},
134 * show <resource-keyword-name> e.g. show directors
135 * show <resource-keyword-name>=<name> e.g. show director=HeadMan
136 * show disabled shows disabled jobs
139 int show_cmd(UAContext *ua, const char *cmd)
146 Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
150 for (i=1; i<ua->argc; i++) {
151 if (strcasecmp(ua->argk[i], _("disabled")) == 0) {
152 show_disabled_jobs(ua);
156 res_name = ua->argk[i];
157 if (!ua->argv[i]) { /* was a name given? */
158 /* No name, dump all resources of specified type */
160 len = strlen(res_name);
161 for (j=0; reses[j].res_name; j++) {
162 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
163 type = reses[j].type;
165 res = res_head[type-r_first];
174 /* Dump a single resource with specified name */
176 len = strlen(res_name);
177 for (j=0; reses[j].res_name; j++) {
178 if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
179 type = reses[j].type;
180 res = (RES *)GetResWithName(type, ua->argv[i]);
191 for (j=r_first; j<=r_last; j++) {
192 /* Skip R_DEVICE since it is really not used or updated */
194 dump_resource(j, res_head[j-r_first], bsendmsg, ua);
199 ua->send_msg(_("Keywords for the show command are:\n"));
200 for (j=0; reses[j].res_name; j++) {
201 ua->error_msg("%s\n", _(reses[j].res_name));
205 ua->error_msg(_("%s resource %s not found.\n"), res_name, ua->argv[i]);
208 ua->error_msg(_("Resource %s not found\n"), res_name);
211 dump_resource(recurse?type:-type, res, bsendmsg, ua);
224 * List contents of database
226 * list jobs - lists all jobs run
227 * list jobid=nnn - list job data for jobid
228 * list ujobid=uname - list job data for unique jobid
229 * list job=name - list all jobs with "name"
230 * list jobname=name - same as above
231 * list jobmedia jobid=<nn>
232 * list jobmedia job=name
233 * list joblog jobid=<nn>
234 * list joblog job=name
235 * list files jobid=<nn> - list files saved for job nn
236 * list files job=name
237 * list pools - list pool records
238 * list jobtotals - list totals for all jobs
239 * list media - list media for given pool (deprecated)
240 * list volumes - list Volumes
241 * list clients - list clients
242 * list nextvol job=xx - list the next vol to be used by job
243 * list nextvolume job=xx - same as above.
244 * list copies jobid=x,y,z
248 /* Do long or full listing */
249 int llist_cmd(UAContext *ua, const char *cmd)
251 return do_list_cmd(ua, cmd, VERT_LIST);
254 /* Do short or summary listing */
255 int list_cmd(UAContext *ua, const char *cmd)
257 return do_list_cmd(ua, cmd, HORZ_LIST);
260 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
269 if (!open_client_db(ua))
272 memset(&jr, 0, sizeof(jr));
273 memset(&pr, 0, sizeof(pr));
275 Dmsg1(20, "list: %s\n", cmd);
278 ua->error_msg(_("Hey! DB is NULL\n"));
281 /* Apply any limit */
282 j = find_arg_with_value(ua, NT_("limit"));
284 jr.limit = atoi(ua->argv[j]);
287 /* Scan arguments looking for things to do */
288 for (i=1; i<ua->argc; i++) {
290 if (strcasecmp(ua->argk[i], NT_("jobs")) == 0) {
291 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
294 } else if (strcasecmp(ua->argk[i], NT_("jobtotals")) == 0) {
295 db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
298 } else if (strcasecmp(ua->argk[i], NT_("jobid")) == 0) {
300 jobid = str_to_int64(ua->argv[i]);
303 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
308 } else if ((strcasecmp(ua->argk[i], NT_("job")) == 0 ||
309 strcasecmp(ua->argk[i], NT_("jobname")) == 0) && ua->argv[i]) {
310 bstrncpy(jr.Name, ua->argv[i], MAX_NAME_LENGTH);
312 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
314 /* List UJOBID=xxx */
315 } else if (strcasecmp(ua->argk[i], NT_("ujobid")) == 0 && ua->argv[i]) {
316 bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
318 db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
320 /* List Base files */
321 } else if (strcasecmp(ua->argk[i], NT_("basefiles")) == 0) {
322 /* TODO: cleanup this block */
323 for (j=i+1; j<ua->argc; j++) {
324 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
325 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
327 db_get_job_record(ua->jcr, ua->db, &jr);
329 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
330 jobid = str_to_int64(ua->argv[j]);
335 db_list_base_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
340 } else if (strcasecmp(ua->argk[i], NT_("files")) == 0) {
342 for (j=i+1; j<ua->argc; j++) {
343 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
344 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
346 db_get_job_record(ua->jcr, ua->db, &jr);
348 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
349 jobid = str_to_int64(ua->argv[j]);
354 db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
359 } else if (strcasecmp(ua->argk[i], NT_("jobmedia")) == 0) {
361 for (j=i+1; j<ua->argc; j++) {
362 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
363 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
365 db_get_job_record(ua->jcr, ua->db, &jr);
367 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
368 jobid = str_to_int64(ua->argv[j]);
372 db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
376 /* List for all jobs (jobid=0) */
377 db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
381 } else if (strcasecmp(ua->argk[i], NT_("joblog")) == 0) {
383 for (j=i+1; j<ua->argc; j++) {
384 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
385 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
387 db_get_job_record(ua->jcr, ua->db, &jr);
389 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
390 jobid = str_to_int64(ua->argv[j]);
394 db_list_joblog_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
398 /* List for all jobs (jobid=0) */
399 db_list_joblog_records(ua->jcr, ua->db, 0, prtit, ua, llist);
404 } else if (strcasecmp(ua->argk[i], NT_("pool")) == 0 ||
405 strcasecmp(ua->argk[i], NT_("pools")) == 0) {
407 memset(&pr, 0, sizeof(pr));
409 bstrncpy(pr.Name, ua->argv[i], sizeof(pr.Name));
411 db_list_pool_records(ua->jcr, ua->db, &pr, prtit, ua, llist);
413 } else if (strcasecmp(ua->argk[i], NT_("clients")) == 0) {
414 db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
417 /* List MEDIA or VOLUMES */
418 } else if (strcasecmp(ua->argk[i], NT_("media")) == 0 ||
419 strcasecmp(ua->argk[i], NT_("volume")) == 0 ||
420 strcasecmp(ua->argk[i], NT_("volumes")) == 0) {
422 for (j=i+1; j<ua->argc; j++) {
423 if (strcasecmp(ua->argk[j], NT_("ujobid")) == 0 && ua->argv[j]) {
424 bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
426 db_get_job_record(ua->jcr, ua->db, &jr);
428 } else if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
429 jobid = str_to_int64(ua->argv[j]);
433 VolumeName = get_pool_memory(PM_FNAME);
434 n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
435 ua->send_msg(_("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
436 free_pool_memory(VolumeName);
440 /* if no job or jobid keyword found, then we list all media */
444 /* List a specific volume? */
446 bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
447 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
450 /* Is a specific pool wanted? */
451 for (i=1; i<ua->argc; i++) {
452 if (strcasecmp(ua->argk[i], NT_("pool")) == 0) {
453 if (!get_pool_dbr(ua, &pr)) {
454 ua->error_msg(_("No Pool specified.\n"));
457 mr.PoolId = pr.PoolId;
458 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
463 /* List Volumes in all pools */
464 if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
465 ua->error_msg(_("Error obtaining pool ids. ERR=%s\n"),
466 db_strerror(ua->db));
469 if (num_pools <= 0) {
472 for (i=0; i < num_pools; i++) {
474 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
475 ua->send_msg(_("Pool: %s\n"), pr.Name);
478 db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
483 /* List next volume */
484 } else if (strcasecmp(ua->argk[i], NT_("nextvol")) == 0 ||
485 strcasecmp(ua->argk[i], NT_("nextvolume")) == 0) {
487 j = find_arg_with_value(ua, NT_("days"));
489 n = atoi(ua->argv[j]);
490 if ((n < 0) || (n > 50)) {
491 ua->warning_msg(_("Ignoring invalid value for days. Max is 50.\n"));
496 } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
499 for (j=i+1; j<ua->argc; j++) {
500 if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
501 if (is_a_number_list(ua->argv[j])) {
502 jobids = ua->argv[j];
504 } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
505 limit = atoi(ua->argv[j]);
508 db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
509 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
510 || strcasecmp(ua->argk[i], NT_("days")) == 0) {
513 ua->error_msg(_("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
519 static bool list_nextvol(UAContext *ua, int ndays)
530 int i = find_arg_with_value(ua, "job");
532 if ((job = select_job_resource(ua)) == NULL) {
536 job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
538 Jmsg(ua->jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
539 if ((job = select_job_resource(ua)) == NULL) {
545 jcr = new_jcr(sizeof(JCR), dird_free_jcr);
546 for (run=NULL; (run = find_next_run(run, job, runtime, ndays)); ) {
547 if (!complete_jcr_for_job(jcr, job, run->pool)) {
551 if (!jcr->jr.PoolId) {
552 ua->error_msg(_("Could not find Pool for Job %s\n"), job->name());
555 memset(&pr, 0, sizeof(pr));
556 pr.PoolId = jcr->jr.PoolId;
557 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
558 bstrncpy(pr.Name, "*UnknownPool*", sizeof(pr.Name));
560 mr.PoolId = jcr->jr.PoolId;
561 get_job_storage(&store, job, run);
562 set_storageid_in_mr(store.store, &mr);
563 /* no need to set ScratchPoolId, since we use fnv_no_create_vol */
564 if (!find_next_volume_for_append(jcr, &mr, 1, fnv_no_create_vol, fnv_prune)) {
565 ua->error_msg(_("Could not find next Volume for Job %s (Pool=%s, Level=%s).\n"),
566 job->name(), pr.Name, level_to_str(run->level));
569 _("The next Volume to be used by Job \"%s\" (Pool=%s, Level=%s) will be %s\n"),
570 job->name(), pr.Name, level_to_str(run->level), mr.VolumeName);
577 db_close_database(jcr, jcr->db);
582 ua->error_msg(_("Could not find next Volume for Job %s.\n"),
591 * For a given job, we examine all his run records
592 * to see if it is scheduled today or tomorrow.
594 RUN *find_next_run(RUN *run, JOB *job, utime_t &runtime, int ndays)
596 time_t now, future, endtime;
599 int mday, wday, month, wom, i;
604 sched = job->schedule;
605 if (sched == NULL) { /* scheduled? */
606 return NULL; /* no nothing to report */
609 /* Break down the time into components */
611 endtime = now + (ndays * 60 * 60 * 24);
618 for ( ; run; run=run->next) {
620 * Find runs in next 24 hours. Day 0 is today, so if
621 * ndays=1, look at today and tomorrow.
623 for (day = 0; day <= ndays; day++) {
624 future = now + (day * 60 * 60 * 24);
626 /* Break down the time into components */
627 (void)localtime_r(&future, &tm);
628 mday = tm.tm_mday - 1;
632 woy = tm_woy(future);
633 ldom = tm_ldom(month, tm.tm_year + 1900);
635 is_scheduled = (bit_is_set(mday, run->mday) &&
636 bit_is_set(wday, run->wday) &&
637 bit_is_set(month, run->month) &&
638 bit_is_set(wom, run->wom) &&
639 bit_is_set(woy, run->woy)) ||
640 (bit_is_set(month, run->month) &&
641 bit_is_set(31, run->mday) && mday == ldom);
644 Pmsg2(000, "day=%d is_scheduled=%d\n", day, is_scheduled);
645 Pmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
646 Pmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
647 Pmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
648 Pmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
649 Pmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
652 if (is_scheduled) { /* Jobs scheduled on that day */
654 char buf[300], num[10];
655 bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
656 for (i=0; i<24; i++) {
657 if (bit_is_set(i, run->hour)) {
658 bsnprintf(num, sizeof(num), "%d ", i);
659 bstrncat(buf, num, sizeof(buf));
662 bstrncat(buf, "\n", sizeof(buf));
663 Pmsg1(000, "%s", buf);
665 /* find time (time_t) job is to be run */
666 (void)localtime_r(&future, &runtm);
667 for (i= 0; i < 24; i++) {
668 if (bit_is_set(i, run->hour)) {
670 runtm.tm_min = run->minute;
672 runtime = mktime(&runtm);
673 Dmsg2(200, "now=%d runtime=%lld\n", now, runtime);
674 if ((runtime > now) && (runtime < endtime)) {
675 Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
676 return run; /* found it, return run resource */
682 } /* end for loop over runs */
688 * Fill in the remaining fields of the jcr as if it
689 * is going to run the job.
691 bool complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
695 memset(&pr, 0, sizeof(POOL_DBR));
696 set_jcr_defaults(jcr, job);
698 jcr->pool = pool; /* override */
701 Dmsg0(100, "complete_jcr close db\n");
702 db_close_database(jcr, jcr->db);
706 Dmsg0(100, "complete_jcr open db\n");
707 jcr->db = db_init_database(jcr, jcr->catalog->db_driver, jcr->catalog->db_name,
708 jcr->catalog->db_user,
709 jcr->catalog->db_password, jcr->catalog->db_address,
710 jcr->catalog->db_port, jcr->catalog->db_socket,
711 jcr->catalog->mult_db_connections,
712 jcr->catalog->disable_batch_insert);
713 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
714 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
715 jcr->catalog->db_name);
717 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
718 db_close_database(jcr, jcr->db);
723 bstrncpy(pr.Name, jcr->pool->name(), sizeof(pr.Name));
724 while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
725 /* Try to create the pool */
726 if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
727 Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name,
728 db_strerror(jcr->db));
730 db_close_database(jcr, jcr->db);
735 Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
738 jcr->jr.PoolId = pr.PoolId;
743 static void con_lock_release(void *arg)
748 void do_messages(UAContext *ua, const char *cmd)
752 bool do_truncate = false;
755 pthread_cleanup_push(con_lock_release, (void *)NULL);
757 while (fgets(msg, sizeof(msg), con_fd)) {
759 ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
760 strcpy(ua->UA_sock->msg, msg);
761 ua->UA_sock->msglen = mlen;
766 (void)ftruncate(fileno(con_fd), 0L);
768 console_msg_pending = FALSE;
769 ua->user_notified_msg_pending = FALSE;
770 pthread_cleanup_pop(0);
775 int qmessagescmd(UAContext *ua, const char *cmd)
777 if (console_msg_pending && ua->auto_display_messages) {
778 do_messages(ua, cmd);
783 int messagescmd(UAContext *ua, const char *cmd)
785 if (console_msg_pending) {
786 do_messages(ua, cmd);
788 ua->UA_sock->fsend(_("You have no messages.\n"));
794 * Callback routine for "printing" database file listing
796 void prtit(void *ctx, const char *msg)
798 UAContext *ua = (UAContext *)ctx;
800 ua->UA_sock->fsend("%s", msg);
804 * Format message and send to other end.
806 * If the UA_sock is NULL, it means that there is no user
807 * agent, so we are being called from Bacula core. In
808 * that case direct the messages to the Job.
811 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
813 BSOCK *bs = ua->UA_sock;
822 msg = get_pool_memory(PM_EMSG);
826 maxlen = sizeof_pool_memory(msg) - 1;
827 va_copy(ap, arg_ptr);
828 len = bvsnprintf(msg, maxlen, fmt, ap);
830 if (len < 0 || len >= maxlen) {
831 msg = realloc_pool_memory(msg, maxlen + maxlen/2);
839 } else { /* No UA, send to Job */
840 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
841 free_pool_memory(msg);
846 #else /* no va_copy() -- brain damaged version of variable arguments */
848 void bmsg(UAContext *ua, const char *fmt, va_list arg_ptr)
850 BSOCK *bs = ua->UA_sock;
858 msg = get_memory(5000);
861 maxlen = sizeof_pool_memory(msg) - 1;
863 msg = realloc_pool_memory(msg, 5000);
866 len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
867 if (len < 0 || len >= maxlen) {
868 pm_strcpy(msg, _("Message too long to display.\n"));
876 } else { /* No UA, send to Job */
877 Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
878 free_pool_memory(msg);
884 void bsendmsg(void *ctx, const char *fmt, ...)
887 va_start(arg_ptr, fmt);
888 bmsg((UAContext *)ctx, fmt, arg_ptr);
893 * The following UA methods are mainly intended for GUI
897 * This is a message that should be displayed on the user's
900 void UAContext::send_msg(const char *fmt, ...)
903 va_start(arg_ptr, fmt);
904 bmsg(this, fmt, arg_ptr);
910 * This is an error condition with a command. The gui should put
911 * up an error or critical dialog box. The command is aborted.
913 void UAContext::error_msg(const char *fmt, ...)
918 if (bs && api) bs->signal(BNET_ERROR_MSG);
919 va_start(arg_ptr, fmt);
920 bmsg(this, fmt, arg_ptr);
925 * This is a warning message, that should bring up a warning
926 * dialog box on the GUI. The command is not aborted, but something
929 void UAContext::warning_msg(const char *fmt, ...)
934 if (bs && api) bs->signal(BNET_WARNING_MSG);
935 va_start(arg_ptr, fmt);
936 bmsg(this, fmt, arg_ptr);
941 * This is an information message that should probably be put
942 * into the status line of a GUI program.
944 void UAContext::info_msg(const char *fmt, ...)
949 if (bs && api) bs->signal(BNET_INFO_MSG);
950 va_start(arg_ptr, fmt);
951 bmsg(this, fmt, arg_ptr);