]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/autochanger.c
Merge remote branch 'sf/master' into purge
[bacula/bacula] / bacula / src / stored / autochanger.c
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2002-2008 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU 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    General Public License for more details.
17
18    You should have received a copy of the GNU 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    Bacula® is a registered trademark of Kern Sibbald.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *
30  *  Routines for handling the autochanger.
31  *
32  *   Kern Sibbald, August MMII
33  *                            
34  *   Version $Id$
35  */
36
37 #include "bacula.h"                   /* pull in global headers */
38 #include "stored.h"                   /* pull in Storage Deamon headers */
39
40 /* Forward referenced functions */
41 static void lock_changer(DCR *dcr);
42 static void unlock_changer(DCR *dcr);
43 static bool unload_other_drive(DCR *dcr, int slot);
44
45 /* Init all the autochanger resources found */
46 bool init_autochangers()
47 {
48    bool OK = true;
49    AUTOCHANGER *changer;
50    /* Ensure that the media_type for each device is the same */
51    foreach_res(changer, R_AUTOCHANGER) {
52       DEVRES *device;
53       foreach_alist(device, changer->device) {
54          /*
55           * If the device does not have a changer name or changer command
56           *   defined, used the one from the Autochanger resource 
57           */
58          if (!device->changer_name && changer->changer_name) {
59             device->changer_name = bstrdup(changer->changer_name);
60          }
61          if (!device->changer_command && changer->changer_command) {
62             device->changer_command = bstrdup(changer->changer_command);
63          }
64          if (!device->changer_name) {
65             Jmsg(NULL, M_ERROR, 0, 
66                _("No Changer Name given for device %s. Cannot continue.\n"),
67                device->hdr.name);
68             OK = false;
69          }   
70          if (!device->changer_command) {
71             Jmsg(NULL, M_ERROR, 0, 
72                _("No Changer Command given for device %s. Cannot continue.\n"),
73                device->hdr.name);
74             OK = false;
75          }   
76
77 #ifdef xxx_needed
78          if (media_type == NULL) {
79             media_type = device->media_type;     /* get Media Type of first device */
80             continue;
81          }     
82          /* Ensure that other devices Media Types are the same */
83          if (strcmp(media_type, device->media_type) != 0) {
84             Jmsg(NULL, M_ERROR, 0, 
85                _("Media Type not the same for all devices in changer %s. Cannot continue.\n"),
86                changer->hdr.name);
87             OK = false;
88             continue;
89          }
90 #endif
91       }
92    }
93    return OK;
94 }
95
96
97 /*
98  * Called here to do an autoload using the autochanger, if
99  *  configured, and if a Slot has been defined for this Volume.
100  *  On success this routine loads the indicated tape, but the
101  *  label is not read, so it must be verified.
102  *
103  *  Note if dir is not NULL, it is the console requesting the
104  *   autoload for labeling, so we respond directly to the
105  *   dir bsock.
106  *
107  *  Returns: 1 on success
108  *           0 on failure (no changer available)
109  *          -1 on error on autochanger
110  */
111 int autoload_device(DCR *dcr, int writing, BSOCK *dir)
112 {
113    JCR *jcr = dcr->jcr;
114    DEVICE * volatile dev = dcr->dev;
115    int slot;
116    int drive = dev->drive_index;
117    int rtn_stat = -1;                 /* error status */
118    POOLMEM *changer;
119
120    if (!dev->is_autochanger()) {
121       Dmsg1(100, "Device %s is not an autochanger\n", dev->print_name());
122       return 0;
123    }
124
125    /* An empty ChangerCommand => virtual disk autochanger */
126    if (dcr->device->changer_command && dcr->device->changer_command[0] == 0) {
127       Dmsg0(100, "ChangerCommand=0, virtual disk changer\n");
128       return 1;                       /* nothing to load */
129    }
130
131    slot = dcr->VolCatInfo.InChanger ? dcr->VolCatInfo.Slot : 0;
132    Dmsg3(100, "autoload: slot=%d InChgr=%d Vol=%s\n", dcr->VolCatInfo.Slot,
133          dcr->VolCatInfo.InChanger, dcr->VolCatInfo.VolCatName);
134    /*
135     * Handle autoloaders here.  If we cannot autoload it, we
136     *  will return 0 so that the sysop will be asked to load it.
137     */
138    if (writing && slot <= 0) {
139       if (dir) {
140          return 0;                    /* For user, bail out right now */
141       }
142       /* ***FIXME*** this really should not be here */
143       if (dir_find_next_appendable_volume(dcr)) {
144          slot = dcr->VolCatInfo.InChanger ? dcr->VolCatInfo.Slot : 0;
145       } else {
146          slot = 0;
147       }
148    }
149    Dmsg1(400, "Want changer slot=%d\n", slot);
150
151    changer = get_pool_memory(PM_FNAME);
152    if (slot <= 0) {
153       Jmsg(jcr, M_INFO, 0, _("Invalid slot=%d defined in catalog for Volume \"%s\" "
154            "on %s. Manual load may be required.\n"), slot, dcr->VolCatInfo.VolCatName,
155            dev->print_name());
156       rtn_stat = 0;
157    } else if (!dcr->device->changer_name) {
158       Jmsg(jcr, M_INFO, 0, _("No \"Changer Device\" for %s. Manual load of Volume may be required.\n"),
159            dev->print_name());
160       rtn_stat = 0;
161   } else if (!dcr->device->changer_command) {
162       Jmsg(jcr, M_INFO, 0, _("No \"Changer Command\" for %s. Manual load of Volume may be requird.\n"),
163            dev->print_name());
164       rtn_stat = 0;
165   } else {
166       /* Attempt to load the Volume */
167
168       uint32_t timeout = dcr->device->max_changer_wait;
169       int loaded, status;
170
171       loaded = get_autochanger_loaded_slot(dcr);
172
173       if (loaded != slot) {
174          POOL_MEM results(PM_MESSAGE);
175
176          /* Unload anything in our drive */
177          if (!unload_autochanger(dcr, loaded)) {
178             goto bail_out;
179          }
180             
181          /* Make sure desired slot is unloaded */
182          if (!unload_other_drive(dcr, slot)) {
183             goto bail_out;
184          }
185
186          /*
187           * Load the desired cassette
188           */
189          lock_changer(dcr);
190          Dmsg2(100, "Doing changer load slot %d %s\n", slot, dev->print_name());
191          Jmsg(jcr, M_INFO, 0,
192               _("3304 Issuing autochanger \"load slot %d, drive %d\" command.\n"),
193               slot, drive);
194          dcr->VolCatInfo.Slot = slot;    /* slot to be loaded */
195          changer = edit_device_codes(dcr, changer, dcr->device->changer_command, "load");
196          dev->close();
197          Dmsg1(200, "Run program=%s\n", changer);
198          status = run_program_full_output(changer, timeout, results.addr());
199          if (status == 0) {
200             Jmsg(jcr, M_INFO, 0, _("3305 Autochanger \"load slot %d, drive %d\", status is OK.\n"),
201                     slot, drive);
202             Dmsg2(100, "load slot %d, drive %d, status is OK.\n", slot, drive);
203             dev->set_slot(slot);      /* set currently loaded slot */
204          } else {
205             berrno be;
206             be.set_errno(status);
207             Dmsg3(100, "load slot %d, drive %d, bad stats=%s.\n", slot, drive,
208                be.bstrerror());
209             Jmsg(jcr, M_FATAL, 0, _("3992 Bad autochanger \"load slot %d, drive %d\": "
210                  "ERR=%s.\nResults=%s\n"),
211                     slot, drive, be.bstrerror(), results.c_str());
212             rtn_stat = -1;            /* hard error */
213             dev->set_slot(-1);        /* mark unknown */
214          }
215          Dmsg2(100, "load slot %d status=%d\n", slot, status);
216          unlock_changer(dcr);
217       } else {
218          status = 0;                  /* we got what we want */
219          dev->set_slot(slot);         /* set currently loaded slot */
220       }
221       Dmsg1(100, "After changer, status=%d\n", status);
222       if (status == 0) {              /* did we succeed? */
223          rtn_stat = 1;                /* tape loaded by changer */
224       }
225    }
226    free_pool_memory(changer);
227    return rtn_stat;
228
229 bail_out:
230    free_pool_memory(changer);
231    return -1;
232
233 }
234
235 /*
236  * Returns: -1 if error from changer command
237  *          slot otherwise
238  *  Note, this is safe to do without releasing the drive
239  *   since it does not attempt load/unload a slot.
240  */
241 int get_autochanger_loaded_slot(DCR *dcr)
242 {
243    JCR *jcr = dcr->jcr;
244    DEVICE *dev = dcr->dev;
245    int status, loaded;
246    uint32_t timeout = dcr->device->max_changer_wait;
247    int drive = dcr->dev->drive_index;
248    POOL_MEM results(PM_MESSAGE);
249    POOLMEM *changer;
250
251    if (!dev->is_autochanger()) {
252       return -1;
253    }
254    if (!dcr->device->changer_command) {
255 //    Jmsg(jcr, M_FATAL, 0, _("3992 Missing Changer command.\n"));
256       return -1;
257    }
258    if (dev->get_slot() > 0) {
259       return dev->get_slot();
260    }
261
262    /* Virtual disk autochanger */
263    if (dcr->device->changer_command[0] == 0) {
264       return 1;
265    }
266
267    /* Find out what is loaded, zero means device is unloaded */
268    changer = get_pool_memory(PM_FNAME);
269    lock_changer(dcr);
270    Jmsg(jcr, M_INFO, 0, _("3301 Issuing autochanger \"loaded? drive %d\" command.\n"),
271         drive);
272    changer = edit_device_codes(dcr, changer, dcr->device->changer_command, "loaded");
273    Dmsg1(100, "Run program=%s\n", changer);
274    status = run_program_full_output(changer, timeout, results.addr());
275    Dmsg3(100, "run_prog: %s stat=%d result=%s", changer, status, results.c_str());
276    if (status == 0) {
277       loaded = str_to_int32(results.c_str());
278       if (loaded > 0) {
279          Jmsg(jcr, M_INFO, 0, _("3302 Autochanger \"loaded? drive %d\", result is Slot %d.\n"),
280               drive, loaded);
281          dev->set_slot(loaded);
282       } else {
283          Jmsg(jcr, M_INFO, 0, _("3302 Autochanger \"loaded? drive %d\", result: nothing loaded.\n"),
284               drive);
285          dev->clear_slot();   /* unknown */
286       }
287    } else {
288       berrno be;
289       be.set_errno(status);
290       Jmsg(jcr, M_INFO, 0, _("3991 Bad autochanger \"loaded? drive %d\" command: "
291            "ERR=%s.\nResults=%s\n"), drive, be.bstrerror(), results.c_str());
292       loaded = -1;              /* force unload */
293    }
294    unlock_changer(dcr);
295    free_pool_memory(changer);
296    return loaded;
297 }
298
299 static void lock_changer(DCR *dcr)
300 {
301    AUTOCHANGER *changer_res = dcr->device->changer_res;
302    if (changer_res) {
303       Dmsg1(200, "Locking changer %s\n", changer_res->hdr.name);
304       P(changer_res->changer_mutex);  /* Lock changer script */
305    }
306 }
307
308 static void unlock_changer(DCR *dcr)
309 {
310    AUTOCHANGER *changer_res = dcr->device->changer_res;
311    if (changer_res) {
312       Dmsg1(200, "Unlocking changer %s\n", changer_res->hdr.name);
313       V(changer_res->changer_mutex);  /* Unlock changer script */
314    }
315 }
316
317 /*
318  * Unload the volume, if any, in this drive
319  *  On entry: loaded == 0 -- nothing to do
320  *            loaded  < 0 -- check if anything to do
321  *            loaded  > 0 -- load slot == loaded
322  */
323 bool unload_autochanger(DCR *dcr, int loaded)
324 {
325    DEVICE *dev = dcr->dev;
326    JCR *jcr = dcr->jcr;
327    int slot;
328    uint32_t timeout = dcr->device->max_changer_wait;
329    bool ok = true;
330
331    if (loaded == 0) {
332       return true;
333    }
334
335    if (!dev->is_autochanger() || !dcr->device->changer_name ||
336        !dcr->device->changer_command) {
337       return false;
338    }
339
340    /* Virtual disk autochanger */
341    if (dcr->device->changer_command[0] == 0) {
342       dev->clear_unload();
343       return true;
344    }
345
346    if (loaded < 0) {
347       loaded = get_autochanger_loaded_slot(dcr);
348    }
349
350    if (loaded > 0) {
351       POOL_MEM results(PM_MESSAGE);
352       POOLMEM *changer = get_pool_memory(PM_FNAME);
353       lock_changer(dcr);
354       Jmsg(jcr, M_INFO, 0,
355            _("3307 Issuing autochanger \"unload slot %d, drive %d\" command.\n"),
356            loaded, dev->drive_index);
357       slot = dcr->VolCatInfo.Slot;
358       dcr->VolCatInfo.Slot = loaded;
359       changer = edit_device_codes(dcr, changer, 
360                    dcr->device->changer_command, "unload");
361       dev->close();
362       Dmsg1(100, "Run program=%s\n", changer);
363       int stat = run_program_full_output(changer, timeout, results.addr());
364       dcr->VolCatInfo.Slot = slot;
365       if (stat != 0) {
366          berrno be;
367          be.set_errno(stat);
368          Jmsg(jcr, M_INFO, 0, _("3995 Bad autochanger \"unload slot %d, drive %d\": "
369               "ERR=%s\nResults=%s\n"),
370                  loaded, dev->drive_index, be.bstrerror(), results.c_str());
371          ok = false;
372          dev->clear_slot();        /* unknown */
373       } else {
374          dev->set_slot(0);         /* nothing loaded */
375       }
376       unlock_changer(dcr);
377
378       free_volume(dev);            /* Free any volume associated with this drive */
379       free_pool_memory(changer);
380    }
381    if (ok) {
382       dev->clear_unload();
383    }
384    return ok;
385 }
386
387 /*
388  * Unload the slot if mounted in a different drive
389  */
390 static bool unload_other_drive(DCR *dcr, int slot)
391 {
392    DEVICE *dev = NULL;
393    bool found = false;
394    AUTOCHANGER *changer = dcr->dev->device->changer_res;
395    DEVRES *device;
396    int retries = 0;                /* wait for device retries */
397
398    if (!changer) {
399       return false;
400    }
401    if (changer->device->size() == 1) {
402       return true;
403    }
404
405    foreach_alist(device, changer->device) {
406       if (device->dev && device->dev->get_slot() == slot) {
407          found = true;
408          dev = device->dev;
409          break;
410       }
411    }
412    if (!found) {
413       return true;
414    }
415
416    /* The Volume we want is on another device. */
417    if (dev->is_busy()) {
418       Dmsg4(100, "Vol %s for dev=%s in use dev=%s slot=%d\n",
419            dcr->VolumeName, dcr->dev->print_name(),
420            dev->print_name(), slot);
421    }   
422    for (int i=0; i < 3; i++) {
423       if (dev->is_busy()) {
424          wait_for_device(dcr->jcr, retries);
425          continue;
426       }
427       break;
428    }
429    if (dev->is_busy()) {
430       Jmsg(dcr->jcr, M_WARNING, 0, _("Volume \"%s\" wanted on %s is in use by device %s\n"),
431            dcr->VolumeName, dcr->dev->print_name(), dev->print_name());
432       Dmsg4(100, "Vol %s for dev=%s is busy dev=%s slot=%d\n",
433            dcr->VolumeName, dcr->dev->print_name(), dev->print_name(), dev->get_slot());
434       Dmsg2(100, "num_writ=%d reserv=%d\n", dev->num_writers, dev->num_reserved());
435       volume_unused(dcr);
436       return false;
437    }
438    return unload_dev(dcr, dev);
439 }
440
441 /*
442  * Unconditionally unload a specified drive
443  */
444 bool unload_dev(DCR *dcr, DEVICE *dev)
445 {
446    JCR *jcr = dcr->jcr;
447    bool ok = true;
448    uint32_t timeout = dcr->device->max_changer_wait;
449    AUTOCHANGER *changer = dcr->dev->device->changer_res;
450    DEVICE *save_dev;
451    int save_slot;
452
453    if (!changer) {
454       return false;
455    }
456
457    save_dev = dcr->dev;               /* save dcr device */
458    dcr->dev = dev;                    /* temporarily point dcr at other device */
459
460    if (dev->get_slot() <= 0 && get_autochanger_loaded_slot(dcr) <= 0) {
461       dcr->dev = save_dev;
462       return false;
463    }
464    save_slot = dcr->VolCatInfo.Slot;
465    dcr->VolCatInfo.Slot = dev->get_slot();
466
467    dev->dlock();
468
469    POOLMEM *changer_cmd = get_pool_memory(PM_FNAME);
470    POOL_MEM results(PM_MESSAGE);
471    lock_changer(dcr);
472    Jmsg(jcr, M_INFO, 0,
473         _("3307 Issuing autochanger \"unload slot %d, drive %d\" command.\n"),
474         dev->get_slot(), dev->drive_index);
475
476    Dmsg2(100, "Issuing autochanger \"unload slot %d, drive %d\" command.\n",
477         dev->get_slot(), dev->drive_index);
478
479    changer_cmd = edit_device_codes(dcr, changer_cmd, 
480                 dcr->device->changer_command, "unload");
481    dev->close();
482    Dmsg2(200, "close dev=%s reserve=%d\n", dev->print_name(), 
483       dev->num_reserved());
484    Dmsg1(100, "Run program=%s\n", changer_cmd);
485    int stat = run_program_full_output(changer_cmd, timeout, results.addr());
486    dcr->VolCatInfo.Slot = save_slot;
487    dcr->dev = save_dev;
488    if (stat != 0) {
489       berrno be;
490       be.set_errno(stat);
491       Jmsg(jcr, M_INFO, 0, _("3995 Bad autochanger \"unload slot %d, drive %d\": ERR=%s.\n"),
492               dev->get_slot(), dev->drive_index, be.bstrerror());
493
494       Dmsg3(100, "Bad autochanger \"unload slot %d, drive %d\": ERR=%s.\n",
495               dev->get_slot(), dev->drive_index, be.bstrerror());
496       ok = false;
497       dev->clear_slot();          /* unknown */
498    } else {
499       Dmsg2(100, "Slot %d unloaded %s\n", dev->get_slot(), dev->print_name());
500       dev->set_slot(0);           /* nothing loaded */
501    }
502    if (ok) {
503       dev->clear_unload();
504    }
505    unlock_changer(dcr);
506
507    dev->dunlock();
508
509    free_volume(dev);               /* Free any volume associated with this drive */
510    free_pool_memory(changer_cmd);
511    return ok;
512 }
513
514
515
516 /*
517  * List the Volumes that are in the autoloader possibly
518  *   with their barcodes.
519  *   We assume that it is always the Console that is calling us.
520  */
521 bool autochanger_cmd(DCR *dcr, BSOCK *dir, const char *cmd)  
522 {
523    DEVICE *dev = dcr->dev;
524    uint32_t timeout = dcr->device->max_changer_wait;
525    POOLMEM *changer;
526    BPIPE *bpipe;
527    int len = sizeof_pool_memory(dir->msg) - 1;
528    bool ok = false;
529    int stat;
530
531    if (!dev->is_autochanger() || !dcr->device->changer_name ||
532        !dcr->device->changer_command) {
533       if (strcmp(cmd, "drives") == 0) {
534          dir->fsend("drives=1\n");
535       }
536       dir->fsend(_("3993 Device %s not an autochanger device.\n"),
537          dev->print_name());
538       return false;
539    }
540
541    if (strcmp(cmd, "drives") == 0) {
542       AUTOCHANGER *changer_res = dcr->device->changer_res;
543       int drives = 1;
544       if (changer_res) {
545          drives = changer_res->device->size();
546       }
547       dir->fsend("drives=%d\n", drives);
548       Dmsg1(100, "drives=%d\n", drives);
549       return true;
550    }
551
552    changer = get_pool_memory(PM_FNAME);
553    lock_changer(dcr);
554    /* Now issue the command */
555    changer = edit_device_codes(dcr, changer, 
556                  dcr->device->changer_command, cmd);
557    dir->fsend(_("3306 Issuing autochanger \"%s\" command.\n"), cmd);
558    bpipe = open_bpipe(changer, timeout, "r");
559    if (!bpipe) {
560       dir->fsend(_("3996 Open bpipe failed.\n"));
561       goto bail_out;
562    }
563    if (bstrcmp(cmd, "list") || bstrcmp(cmd, "listall")) {
564       /* Get output from changer */
565       while (fgets(dir->msg, len, bpipe->rfd)) {
566          dir->msglen = strlen(dir->msg);
567          Dmsg1(100, "<stored: %s\n", dir->msg);
568          bnet_send(dir);
569       }
570    } else if (strcmp(cmd, "slots") == 0 ) {
571       char buf[100], *p;
572       /* For slots command, read a single line */
573       buf[0] = 0;
574       fgets(buf, sizeof(buf)-1, bpipe->rfd);
575       buf[sizeof(buf)-1] = 0;
576       /* Strip any leading space in front of # of slots */
577       for (p=buf; B_ISSPACE(*p); p++)
578         { }
579       dir->fsend("slots=%s", p);
580       Dmsg1(100, "<stored: %s", dir->msg);
581    } 
582                  
583    stat = close_bpipe(bpipe);
584    if (stat != 0) {
585       berrno be;
586       be.set_errno(stat);
587       dir->fsend(_("Autochanger error: ERR=%s\n"), be.bstrerror());
588    }
589    bnet_sig(dir, BNET_EOD);
590    ok = true;
591
592 bail_out:
593    unlock_changer(dcr);
594    free_pool_memory(changer);
595    return true;
596 }
597
598
599 /*
600  * Edit codes into ChangerCommand
601  *  %% = %
602  *  %a = archive device name
603  *  %c = changer device name
604  *  %d = changer drive index
605  *  %f = Client's name
606  *  %j = Job name
607  *  %o = command
608  *  %s = Slot base 0
609  *  %S = Slot base 1
610  *  %v = Volume name
611  *
612  *
613  *  omsg = edited output message
614  *  imsg = input string containing edit codes (%x)
615  *  cmd = command string (load, unload, ...)
616  *
617  */
618 char *edit_device_codes(DCR *dcr, char *omsg, const char *imsg, const char *cmd)
619 {
620    const char *p;
621    const char *str;
622    char add[20];
623
624    *omsg = 0;
625    Dmsg1(1800, "edit_device_codes: %s\n", imsg);
626    for (p=imsg; *p; p++) {
627       if (*p == '%') {
628          switch (*++p) {
629          case '%':
630             str = "%";
631             break;
632          case 'a':
633             str = dcr->dev->archive_name();
634             break;
635          case 'c':
636             str = NPRT(dcr->device->changer_name);
637             break;
638          case 'd':
639             sprintf(add, "%d", dcr->dev->drive_index);
640             str = add;
641             break;
642          case 'o':
643             str = NPRT(cmd);
644             break;
645          case 's':
646             sprintf(add, "%d", dcr->VolCatInfo.Slot - 1);
647             str = add;
648             break;
649          case 'S':
650             sprintf(add, "%d", dcr->VolCatInfo.Slot);
651             str = add;
652             break;
653          case 'j':                    /* Job name */
654             str = dcr->jcr->Job;
655             break;
656          case 'v':
657             str = NPRT(dcr->VolumeName);
658             break;
659          case 'f':
660             str = NPRT(dcr->jcr->client_name);
661             break;
662
663          default:
664             add[0] = '%';
665             add[1] = *p;
666             add[2] = 0;
667             str = add;
668             break;
669          }
670       } else {
671          add[0] = *p;
672          add[1] = 0;
673          str = add;
674       }
675       Dmsg1(1900, "add_str %s\n", str);
676       pm_strcat(&omsg, (char *)str);
677       Dmsg1(1800, "omsg=%s\n", omsg);
678    }
679    Dmsg1(800, "omsg=%s\n", omsg);
680    return omsg;
681 }