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