]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_output.c
Put resources on a pointer
[bacula/bacula] / bacula / src / dird / ua_output.c
1 /*
2  *
3  *   Bacula Director -- User Agent Output Commands
4  *     I.e. messages, listing database, showing resources, ...
5  *
6  *     Kern Sibbald, September MM
7  *
8  *   Version $Id$
9  */
10
11 /*
12    Copyright (C) 2000-2004 Kern Sibbald and John Walker
13
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of
17    the License, or (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public
25    License along with this program; if not, write to the Free
26    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27    MA 02111-1307, USA.
28
29  */
30
31 #include "bacula.h"
32 #include "dird.h"
33
34 /* Imported subroutines */
35
36 /* Imported variables */
37 extern int r_first;
38 extern int r_last;
39 extern RES_TABLE resources[];
40 extern RES **res_head;
41 extern int console_msg_pending;
42 extern FILE *con_fd;
43 extern brwlock_t con_lock;
44
45
46 /* Imported functions */
47
48 /* Forward referenced functions */
49 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist);
50
51 /*
52  * Turn auto display of console messages on/off
53  */
54 int autodisplay_cmd(UAContext *ua, const char *cmd)
55 {
56    static const char *kw[] = {
57       N_("on"), 
58       N_("off"),
59       NULL};
60
61    switch (find_arg_keyword(ua, kw)) {
62    case 0:
63       ua->auto_display_messages = 1;
64       break;
65    case 1:
66       ua->auto_display_messages = 0;
67       break;
68    default:
69       bsendmsg(ua, _("ON or OFF keyword missing.\n"));
70       break;
71    }
72    return 1; 
73 }
74
75 /*
76  * Turn gui processing on/off
77  */
78 int gui_cmd(UAContext *ua, const char *cmd)
79 {
80    static const char *kw[] = {
81       N_("on"), 
82       N_("off"),
83       NULL};
84
85    switch (find_arg_keyword(ua, kw)) {
86    case 0:
87       ua->jcr->gui = true;
88       break;
89    case 1:
90       ua->jcr->gui = false;
91       break;
92    default:
93       bsendmsg(ua, _("ON or OFF keyword missing.\n"));
94       break;
95    }
96    return 1; 
97 }
98
99
100
101 struct showstruct {const char *res_name; int type;};
102 static struct showstruct reses[] = {
103    {N_("directors"),  R_DIRECTOR},
104    {N_("clients"),    R_CLIENT},
105    {N_("counters"),   R_COUNTER},
106    {N_("jobs"),       R_JOB},
107    {N_("storages"),   R_STORAGE},
108    {N_("catalogs"),   R_CATALOG},
109    {N_("schedules"),  R_SCHEDULE},
110    {N_("filesets"),   R_FILESET},
111    {N_("pools"),      R_POOL},
112    {N_("messages"),   R_MSGS},
113    {N_("all"),        -1},
114    {N_("help"),       -2},
115    {NULL,           0}
116 };
117
118
119 /*
120  *  Displays Resources
121  *
122  *  show all
123  *  show <resource-keyword-name>  e.g. show directors
124  *  show <resource-keyword-name>=<name> e.g. show director=HeadMan
125  *
126  */
127 int show_cmd(UAContext *ua, const char *cmd)
128 {
129    int i, j, type, len; 
130    int recurse;
131    char *res_name;
132    RES *res = NULL;
133
134    Dmsg1(20, "show: %s\n", ua->UA_sock->msg);
135
136
137    LockRes();
138    for (i=1; i<ua->argc; i++) {
139       type = 0;
140       res_name = ua->argk[i]; 
141       if (!ua->argv[i]) {             /* was a name given? */
142          /* No name, dump all resources of specified type */
143          recurse = 1;
144          len = strlen(res_name);
145          for (j=0; reses[j].res_name; j++) {
146             if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
147                type = reses[j].type;
148                if (type > 0) {
149                   res = res_head[type-r_first];
150 //                res = resources[type-r_first].res_head;
151                } else {
152                   res = NULL;
153                }
154                break;
155             }
156          }
157
158       } else {
159          /* Dump a single resource with specified name */
160          recurse = 0;
161          len = strlen(res_name);
162          for (j=0; reses[j].res_name; j++) {
163             if (strncasecmp(res_name, _(reses[j].res_name), len) == 0) {
164                type = reses[j].type;
165                res = (RES *)GetResWithName(type, ua->argv[i]);
166                if (!res) {
167                   type = -3;
168                }
169                break;
170             }
171          }
172       }
173
174       switch (type) {
175       case -1:                           /* all */
176          for (j=r_first; j<=r_last; j++) {
177             dump_resource(j, res_head[j-r_first], bsendmsg, ua);     
178 //          dump_resource(j, resources[j-r_first].res_head, bsendmsg, ua);     
179          }
180          break;
181       case -2:
182          bsendmsg(ua, _("Keywords for the show command are:\n"));
183          for (j=0; reses[j].res_name; j++) {
184             bsendmsg(ua, "%s\n", _(reses[j].res_name));
185          }
186          goto bail_out;
187       case -3:
188          bsendmsg(ua, _("%s resource %s not found.\n"), res_name, ua->argv[i]);
189          goto bail_out;
190       case 0:
191          bsendmsg(ua, _("Resource %s not found\n"), res_name);
192          goto bail_out;
193       default:
194          dump_resource(recurse?type:-type, res, bsendmsg, ua);
195          break;
196       }
197    }
198 bail_out:
199    UnlockRes();
200    return 1;
201 }
202
203
204
205
206 /*
207  *  List contents of database
208  *
209  *  list jobs           - lists all jobs run
210  *  list jobid=nnn      - list job data for jobid
211  *  list job=name       - list job data for job
212  *  list jobmedia jobid=<nn>
213  *  list jobmedia job=name
214  *  list files jobid=<nn> - list files saved for job nn
215  *  list files job=name
216  *  list pools          - list pool records
217  *  list jobtotals      - list totals for all jobs
218  *  list media          - list media for given pool (deprecated)
219  *  list volumes        - list Volumes
220  *  list clients        - list clients
221  *  list nextvol job=xx  - list the next vol to be used by job
222  *  list nextvolume job=xx - same as above.
223  *
224  */
225
226 /* Do long or full listing */
227 int llist_cmd(UAContext *ua, const char *cmd)
228 {
229    return do_list_cmd(ua, cmd, VERT_LIST);
230 }
231
232 /* Do short or summary listing */
233 int list_cmd(UAContext *ua, const char *cmd)
234 {
235    return do_list_cmd(ua, cmd, HORZ_LIST);
236 }
237
238 static int do_list_cmd(UAContext *ua, const char *cmd, e_list_type llist)
239 {
240    POOLMEM *VolumeName;
241    int jobid, n;
242    int i, j;
243    JOB_DBR jr;
244    POOL_DBR pr;
245    MEDIA_DBR mr;
246
247    if (!open_db(ua))
248       return 1;
249
250    memset(&jr, 0, sizeof(jr));
251    memset(&pr, 0, sizeof(pr));
252    memset(&mr, 0, sizeof(mr));
253
254    Dmsg1(20, "list: %s\n", cmd);
255
256    if (!ua->db) {
257       bsendmsg(ua, _("Hey! DB is NULL\n"));
258    }
259
260    /* Scan arguments looking for things to do */
261    for (i=1; i<ua->argc; i++) {
262       /* List JOBS */
263       if (strcasecmp(ua->argk[i], _("jobs")) == 0) {
264          db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
265
266          /* List JOBTOTALS */
267       } else if (strcasecmp(ua->argk[i], _("jobtotals")) == 0) {
268          db_list_job_totals(ua->jcr, ua->db, &jr, prtit, ua);
269
270       /* List JOBID */
271       } else if (strcasecmp(ua->argk[i], _("jobid")) == 0) {
272          if (ua->argv[i]) {
273             jobid = atoi(ua->argv[i]);
274             if (jobid > 0) {
275                jr.JobId = jobid;
276                db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
277             }
278          }
279
280       /* List JOB */
281       } else if (strcasecmp(ua->argk[i], _("job")) == 0 && ua->argv[i]) {
282          bstrncpy(jr.Job, ua->argv[i], MAX_NAME_LENGTH);
283          jr.JobId = 0;
284          db_list_job_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
285
286       /* List FILES */
287       } else if (strcasecmp(ua->argk[i], _("files")) == 0) {
288
289          for (j=i+1; j<ua->argc; j++) {
290             if (strcasecmp(ua->argk[j], _("job")) == 0 && ua->argv[j]) {
291                bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
292                jr.JobId = 0;
293                db_get_job_record(ua->jcr, ua->db, &jr);
294                jobid = jr.JobId;
295             } else if (strcasecmp(ua->argk[j], _("jobid")) == 0 && ua->argv[j]) {
296                jobid = atoi(ua->argv[j]);
297             } else {
298                continue;
299             }
300             if (jobid > 0) {
301                db_list_files_for_job(ua->jcr, ua->db, jobid, prtit, ua);
302             }
303          }
304       
305       /* List JOBMEDIA */
306       } else if (strcasecmp(ua->argk[i], _("jobmedia")) == 0) {
307          int done = FALSE;
308          for (j=i+1; j<ua->argc; j++) {
309             if (strcasecmp(ua->argk[j], _("job")) == 0 && ua->argv[j]) {
310                bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
311                jr.JobId = 0;
312                db_get_job_record(ua->jcr, ua->db, &jr);
313                jobid = jr.JobId;
314             } else if (strcasecmp(ua->argk[j], _("jobid")) == 0 && ua->argv[j]) {
315                jobid = atoi(ua->argv[j]);
316             } else {
317                continue;
318             }
319             db_list_jobmedia_records(ua->jcr, ua->db, jobid, prtit, ua, llist);
320             done = TRUE;
321          }
322          if (!done) {
323             /* List for all jobs (jobid=0) */
324             db_list_jobmedia_records(ua->jcr, ua->db, 0, prtit, ua, llist);
325          }
326
327       /* List POOLS */
328       } else if (strcasecmp(ua->argk[i], _("pools")) == 0) {
329          db_list_pool_records(ua->jcr, ua->db, prtit, ua, llist);
330
331       } else if (strcasecmp(ua->argk[i], _("clients")) == 0) {
332          db_list_client_records(ua->jcr, ua->db, prtit, ua, llist);
333
334
335       /* List MEDIA or VOLUMES */
336       } else if (strcasecmp(ua->argk[i], _("media")) == 0 ||
337                  strcasecmp(ua->argk[i], _("volumes")) == 0) {
338          bool done = false;
339          for (j=i+1; j<ua->argc; j++) {
340             if (strcasecmp(ua->argk[j], _("job")) == 0 && ua->argv[j]) {
341                bstrncpy(jr.Job, ua->argv[j], MAX_NAME_LENGTH);
342                jr.JobId = 0;
343                db_get_job_record(ua->jcr, ua->db, &jr);
344                jobid = jr.JobId;
345             } else if (strcasecmp(ua->argk[j], _("jobid")) == 0 && ua->argv[j]) {
346                jobid = atoi(ua->argv[j]);
347             } else {
348                continue;
349             }
350             VolumeName = get_pool_memory(PM_FNAME);
351             n = db_get_job_volume_names(ua->jcr, ua->db, jobid, &VolumeName);
352             bsendmsg(ua, _("Jobid %d used %d Volume(s): %s\n"), jobid, n, VolumeName);
353             free_pool_memory(VolumeName);
354             done = true;
355          }
356          /* if no job or jobid keyword found, then we list all media */
357          if (!done) {
358             int num_pools;
359             uint32_t *ids;
360             /* Is a specific pool wanted? */
361             for (i=1; i<ua->argc; i++) {
362                if (strcasecmp(ua->argk[i], _("pool")) == 0) {
363                   if (!get_pool_dbr(ua, &pr)) {
364                      bsendmsg(ua, _("No Pool specified.\n"));
365                      return 1;
366                   }
367                   mr.PoolId = pr.PoolId;
368                   db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
369                   return 1;
370                }
371             }
372             /* List Volumes in all pools */
373             if (!db_get_pool_ids(ua->jcr, ua->db, &num_pools, &ids)) {
374                bsendmsg(ua, _("Error obtaining pool ids. ERR=%s\n"), 
375                         db_strerror(ua->db));
376                return 1;
377             }
378             if (num_pools <= 0) {
379                return 1;
380             }
381             for (i=0; i < num_pools; i++) {
382                pr.PoolId = ids[i];
383                if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
384                   bsendmsg(ua, _("Pool: %s\n"), pr.Name);
385                }
386                mr.PoolId = ids[i];
387                db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
388             }
389             free(ids);
390             return 1;
391          }
392       /* List a specific volume */
393       } else if (strcasecmp(ua->argk[i], _("volume")) == 0) {
394          if (!ua->argv[i]) {
395             bsendmsg(ua, _("No Volume Name specified.\n"));
396             return 1;
397          }
398          bstrncpy(mr.VolumeName, ua->argv[i], sizeof(mr.VolumeName));
399          db_list_media_records(ua->jcr, ua->db, &mr, prtit, ua, llist);
400          return 1;
401       /* List next volume */
402       } else if (strcasecmp(ua->argk[i], _("nextvol")) == 0 || 
403                  strcasecmp(ua->argk[i], _("nextvolume")) == 0) {
404          JOB *job;
405          JCR *jcr = ua->jcr;
406          POOL *pool;
407          RUN *run;
408          time_t runtime;
409          bool found = false;
410
411          i = find_arg_with_value(ua, "job");
412          if (i <= 0) {
413             if ((job = select_job_resource(ua)) == NULL) {
414                return 1;
415             }
416          } else {
417             job = (JOB *)GetResWithName(R_JOB, ua->argv[i]);
418             if (!job) {
419                Jmsg(jcr, M_ERROR, 0, _("%s is not a job name.\n"), ua->argv[i]);
420                if ((job = select_job_resource(ua)) == NULL) {
421                   return 1;
422                }
423             }
424          }
425          for (run=NULL; (run = find_next_run(run, job, runtime)); ) {
426             pool = run ? run->pool : NULL;
427             if (!complete_jcr_for_job(jcr, job, pool)) {
428                return 1;
429             }
430            
431             if (!find_next_volume_for_append(jcr, &mr, 0)) {
432                bsendmsg(ua, _("Could not find next Volume.\n"));
433                if (jcr->db) {
434                   db_close_database(jcr, jcr->db);
435                   jcr->db = NULL;
436                }
437                return 1;
438             } else {
439                bsendmsg(ua, _("The next Volume to be used by Job \"%s\" will be %s\n"), 
440                   job->hdr.name, mr.VolumeName);
441                found = true;
442             }
443             if (jcr->db) {
444                db_close_database(jcr, jcr->db);
445                jcr->db = NULL;
446             }
447          }
448          if (jcr->db) {
449             db_close_database(jcr, jcr->db);
450             jcr->db = NULL;
451          }
452          if (!found) {
453             bsendmsg(ua, _("Could not find next Volume.\n"));
454          }
455          return 1;
456       } else {
457          bsendmsg(ua, _("Unknown list keyword: %s\n"), NPRT(ua->argk[i]));
458       }
459    }
460    return 1;
461 }
462
463 /* 
464  * For a given job, we examine all his run records
465  *  to see if it is scheduled today or tomorrow.
466  */
467 RUN *find_next_run(RUN *run, JOB *job, time_t &runtime)
468 {
469    time_t now, tomorrow;
470    SCHED *sched;
471    struct tm tm;
472    int mday, wday, month, wom, tmday, twday, tmonth, twom, i;
473    int woy, twoy;
474    int tod, tom;
475
476    sched = job->schedule;
477    if (sched == NULL) {            /* scheduled? */
478       return NULL;                 /* no nothing to report */
479    }
480    /* Break down current time into components */ 
481    now = time(NULL);
482    localtime_r(&now, &tm);
483    mday = tm.tm_mday - 1;
484    wday = tm.tm_wday;
485    month = tm.tm_mon;
486    wom = mday / 7;
487    woy = tm_woy(now);
488
489    /* Break down tomorrow into components */
490    tomorrow = now + 60 * 60 * 24;
491    localtime_r(&tomorrow, &tm);
492    tmday = tm.tm_mday - 1;
493    twday = tm.tm_wday;
494    tmonth = tm.tm_mon;
495    twom = tmday / 7;
496    twoy  = tm_woy(tomorrow);
497
498    if (run == NULL) {
499       run = sched->run;
500    } else {
501       run = run->next;
502    }
503    for ( ; run; run=run->next) {
504       /* 
505        * Find runs in next 24 hours
506        */
507       tod = bit_is_set(mday, run->mday) && bit_is_set(wday, run->wday) && 
508             bit_is_set(month, run->month) && bit_is_set(wom, run->wom) &&
509             bit_is_set(woy, run->woy);
510
511       tom = bit_is_set(tmday, run->mday) && bit_is_set(twday, run->wday) &&
512             bit_is_set(tmonth, run->month) && bit_is_set(twom, run->wom) &&
513             bit_is_set(twoy, run->woy);
514
515 #ifdef xxx
516       Dmsg2(000, "tod=%d tom=%d\n", tod, tom);
517       Dmsg1(000, "bit_set_mday=%d\n", bit_is_set(mday, run->mday));
518       Dmsg1(000, "bit_set_wday=%d\n", bit_is_set(wday, run->wday));
519       Dmsg1(000, "bit_set_month=%d\n", bit_is_set(month, run->month));
520       Dmsg1(000, "bit_set_wom=%d\n", bit_is_set(wom, run->wom));
521       Dmsg1(000, "bit_set_woy=%d\n", bit_is_set(woy, run->woy));
522 #endif
523       if (tod) {                   /* Jobs scheduled today (next 24 hours) */
524 #ifdef xxx
525          char buf[300], num[10];
526          bsnprintf(buf, sizeof(buf), "tm.hour=%d hour=", tm.tm_hour);
527          for (i=0; i<24; i++) {
528             if (bit_is_set(i, run->hour)) {
529                bsnprintf(num, sizeof(num), "%d ", i);
530                bstrncat(buf, num, sizeof(buf));
531             }
532          }
533          bstrncat(buf, "\n", sizeof(buf));
534          Dmsg1(000, "%s", buf);
535 #endif 
536          /* find time (time_t) job is to be run */
537          localtime_r(&now, &tm);
538          for (i=tm.tm_hour; i < 24; i++) {
539             if (bit_is_set(i, run->hour)) {
540                tm.tm_hour = i;
541                tm.tm_min = run->minute;
542                tm.tm_sec = 0;
543                runtime = mktime(&tm);
544                Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
545                if (runtime > now) {
546                   Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
547                   return run;         /* found it, return run resource */
548                }
549             }
550          }
551       }
552
553 //    Dmsg2(200, "runtime=%d now=%d\n", runtime, now);
554       if (tom) {                /* look at jobs scheduled tomorrow */
555          localtime_r(&tomorrow, &tm);
556          for (i=0; i < 24; i++) {
557             if (bit_is_set(i, run->hour)) {
558                tm.tm_hour = i;
559                tm.tm_min = run->minute;
560                tm.tm_sec = 0;
561                runtime = mktime(&tm);
562                Dmsg2(200, "now=%d runtime=%d\n", now, runtime);
563                if (runtime < tomorrow) {
564                   Dmsg2(200, "Found it level=%d %c\n", run->level, run->level);
565                   return run;         /* found it, return run resource */
566                }
567             }
568          }
569       }
570    } /* end for loop over runs */ 
571    /* Nothing found */
572    return NULL;
573 }
574 /* 
575  * Fill in the remaining fields of the jcr as if it
576  *  is going to run the job.
577  */
578 int complete_jcr_for_job(JCR *jcr, JOB *job, POOL *pool)
579 {
580    POOL_DBR pr;
581
582    memset(&pr, 0, sizeof(POOL_DBR));   
583    set_jcr_defaults(jcr, job);
584    if (pool) {
585       jcr->pool = pool;               /* override */
586    }
587    jcr->db = jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
588                       jcr->catalog->db_password, jcr->catalog->db_address,
589                       jcr->catalog->db_port, jcr->catalog->db_socket);
590    if (!jcr->db || !db_open_database(jcr, jcr->db)) {
591       Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
592                  jcr->catalog->db_name);
593       if (jcr->db) {
594          Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
595       }
596       return 0;
597    }
598    bstrncpy(pr.Name, jcr->pool->hdr.name, sizeof(pr.Name));
599    while (!db_get_pool_record(jcr, jcr->db, &pr)) { /* get by Name */
600       /* Try to create the pool */
601       if (create_pool(jcr, jcr->db, jcr->pool, POOL_OP_CREATE) < 0) {
602          Jmsg(jcr, M_FATAL, 0, _("Pool %s not in database. %s"), pr.Name, 
603             db_strerror(jcr->db));
604          if (jcr->db) {
605             db_close_database(jcr, jcr->db);
606             jcr->db = NULL;
607          }
608          return 0;
609       } else {
610          Jmsg(jcr, M_INFO, 0, _("Pool %s created in database.\n"), pr.Name);
611       }
612    }
613    jcr->PoolId = pr.PoolId; 
614    jcr->jr.PoolId = pr.PoolId;
615    return 1;
616 }
617
618
619 static void con_lock_release(void *arg)
620 {
621    Vw(con_lock);
622 }
623
624 void do_messages(UAContext *ua, const char *cmd)
625 {
626    char msg[2000];
627    int mlen; 
628    int do_truncate = FALSE;
629
630    Pw(con_lock);
631    pthread_cleanup_push(con_lock_release, (void *)NULL);
632    rewind(con_fd);
633    while (fgets(msg, sizeof(msg), con_fd)) {
634       mlen = strlen(msg);
635       ua->UA_sock->msg = check_pool_memory_size(ua->UA_sock->msg, mlen+1);
636       strcpy(ua->UA_sock->msg, msg);
637       ua->UA_sock->msglen = mlen;
638       bnet_send(ua->UA_sock);
639       do_truncate = TRUE;
640    }
641    if (do_truncate) {
642       ftruncate(fileno(con_fd), 0L);
643    }
644    console_msg_pending = FALSE;
645    ua->user_notified_msg_pending = FALSE;
646    pthread_cleanup_pop(0);
647    Vw(con_lock);
648 }
649
650
651 int qmessagescmd(UAContext *ua, const char *cmd)
652 {
653    if (console_msg_pending && ua->auto_display_messages) {
654       do_messages(ua, cmd);
655    }
656    return 1;
657 }
658
659 int messagescmd(UAContext *ua, const char *cmd)
660 {
661    if (console_msg_pending) {
662       do_messages(ua, cmd);
663    } else {
664       bnet_fsend(ua->UA_sock, _("You have no messages.\n"));
665    }
666    return 1;
667 }
668
669 /*
670  * Callback routine for "printing" database file listing
671  */
672 void prtit(void *ctx, const char *msg)
673 {
674    UAContext *ua = (UAContext *)ctx;
675  
676    bnet_fsend(ua->UA_sock, "%s", msg);
677 }
678
679 /* 
680  * Format message and send to other end.  
681
682  * If the UA_sock is NULL, it means that there is no user
683  * agent, so we are being called from Bacula core. In
684  * that case direct the messages to the Job.
685  */
686 void bsendmsg(void *ctx, const char *fmt, ...)
687 {
688    va_list arg_ptr;
689    UAContext *ua = (UAContext *)ctx;
690    BSOCK *bs = ua->UA_sock;
691    int maxlen, len;
692    POOLMEM *msg;
693
694    if (bs) {
695       msg = bs->msg;
696    } else {
697       msg = get_pool_memory(PM_EMSG);
698    }
699
700 again:
701    maxlen = sizeof_pool_memory(msg) - 1;
702    va_start(arg_ptr, fmt);
703    len = bvsnprintf(msg, maxlen, fmt, arg_ptr);
704    va_end(arg_ptr);
705    if (len < 0 || len >= maxlen) {
706       msg = realloc_pool_memory(msg, maxlen + maxlen/2);
707       goto again;
708    }
709
710    if (bs) {
711       bs->msg = msg;
712       bs->msglen = len;
713       bnet_send(bs);
714    } else {                           /* No UA, send to Job */
715       Jmsg(ua->jcr, M_INFO, 0, "%s", msg);
716       free_pool_memory(msg);
717    }
718
719 }