]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/dvd.c
- Add check for df path for dvd_freespace
[bacula/bacula] / bacula / src / stored / dvd.c
1 /*
2  *
3  *   dvd.c  -- Routines specific to DVD devices (and
4  *             possibly other removable hard media). 
5  *
6  *    Nicolas Boichat, MMV
7  *
8  *   Version $Id$
9  */
10 /*
11    Copyright (C) 2005 Kern Sibbald
12
13    This program is free software; you can redistribute it and/or
14    modify it under the terms of the GNU General Public License
15    version 2 as amended with additional clauses defined in the
16    file LICENSE in the main source directory.
17
18    This program is distributed in the hope that it will be useful,
19    but WITHOUT ANY WARRANTY; without even the implied warranty of
20    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
21    the file LICENSE for additional details.
22
23  */
24
25 #include "bacula.h"
26 #include "stored.h"
27
28 /* Forward referenced functions */
29 static void edit_device_codes_dev(DEVICE *dev, POOL_MEM &omsg, const char *imsg);
30 static bool do_mount_dev(DEVICE* dev, int mount, int dotimeout);
31 static void add_file_and_part_name(DEVICE *dev, POOL_MEM &archive_name);
32
33 /* 
34  * Write the current volume/part filename to archive_name.
35  */
36 void make_mounted_dvd_filename(DEVICE *dev, POOL_MEM &archive_name) 
37 {
38    pm_strcpy(archive_name, dev->device->mount_point);
39    add_file_and_part_name(dev, archive_name);
40    dev->set_part_spooled(false);
41 }
42
43 void make_spooled_dvd_filename(DEVICE *dev, POOL_MEM &archive_name)
44 {
45    /* Use the working directory if spool directory is not defined */
46    if (dev->device->spool_directory) {
47       pm_strcpy(archive_name, dev->device->spool_directory);
48    } else {
49       pm_strcpy(archive_name, working_directory);
50    }
51    add_file_and_part_name(dev, archive_name);
52    dev->set_part_spooled(true);
53 }      
54
55 static void add_file_and_part_name(DEVICE *dev, POOL_MEM &archive_name)
56 {
57    char partnumber[20];
58    if (archive_name.c_str()[strlen(archive_name.c_str())-1] != '/') {
59       pm_strcat(archive_name, "/");
60    }
61
62    pm_strcat(archive_name, dev->VolCatInfo.VolCatName);
63    /* if part > 1, append .# to the filename (where # is the part number) */
64    if (dev->part > 1) {
65       pm_strcat(archive_name, ".");
66       bsnprintf(partnumber, sizeof(partnumber), "%d", dev->part);
67       pm_strcat(archive_name, partnumber);
68    }
69    Dmsg1(100, "Exit make_dvd_filename: arch=%s\n", archive_name.c_str());
70 }  
71
72 /* Mount the device.
73  * If timeout, wait until the mount command returns 0.
74  * If !timeout, try to mount the device only once.
75  */
76 bool mount_dev(DEVICE* dev, int timeout) 
77 {
78    Dmsg0(900, "Enter mount_dev\n");
79    if (dev->is_mounted()) {
80       return true;
81    } else if (dev->requires_mount()) {
82       return do_mount_dev(dev, 1, timeout);
83    }       
84    return true;
85 }
86
87 /* Unmount the device
88  * If timeout, wait until the unmount command returns 0.
89  * If !timeout, try to unmount the device only once.
90  */
91 bool unmount_dev(DEVICE *dev, int timeout) 
92 {
93    Dmsg0(900, "Enter unmount_dev\n");
94    if (dev->is_mounted()) {
95       return do_mount_dev(dev, 0, timeout);
96    }
97    return true;
98 }
99
100 /* (Un)mount the device */
101 static bool do_mount_dev(DEVICE* dev, int mount, int dotimeout) 
102 {
103    POOL_MEM ocmd(PM_FNAME);
104    POOLMEM *results;
105    char *icmd;
106    int status, timeout;
107    
108    sm_check(__FILE__, __LINE__, false);
109    if (mount) {
110       if (dev->is_mounted()) {
111          Dmsg0(200, "======= DVD mount=1\n");
112          return true;
113       }
114       icmd = dev->device->mount_command;
115    } else {
116       if (!dev->is_mounted()) {
117          Dmsg0(200, "======= DVD mount=0\n");
118          return true;
119       }
120       icmd = dev->device->unmount_command;
121    }
122    
123    edit_device_codes_dev(dev, ocmd, icmd);
124    
125    Dmsg2(200, "do_mount_dev: cmd=%s mounted=%d\n", ocmd.c_str(), !!dev->is_mounted());
126
127    if (dotimeout) {
128       /* Try at most 1 time to (un)mount the device. This should perhaps be configurable. */
129       timeout = 1;
130    } else {
131       timeout = 0;
132    }
133    results = get_memory(2000);
134    results[0] = 0;
135    /* If busy retry each second */
136    while ((status = run_program_full_output(ocmd.c_str(), 
137                        dev->max_open_wait/2, results)) != 0) {
138       if (fnmatch("*is already mounted on", results, 0) == 0) {
139          break;
140       }
141       if (timeout-- > 0) {
142          /* Sometimes the device cannot be mounted because it is already mounted.
143           * Try to unmount it, then remount it */
144          if (mount) {
145             Dmsg1(400, "Trying to unmount the device %s...\n", dev->print_name());
146             do_mount_dev(dev, 0, 0);
147          }
148          bmicrosleep(1, 0);
149          continue;
150       }
151       Dmsg2(40, "Device %s cannot be mounted. ERR=%s\n", dev->print_name(), results);
152       Mmsg(dev->errmsg, "Device %s cannot be mounted. ERR=%s\n", 
153            dev->print_name(), results);
154       /*
155        * Now, just to be sure it is not mounted, try to read the
156        *  filesystem.
157        */
158       DIR* dp;
159       struct dirent *entry, *result;
160       int name_max;
161       int count = 0;
162       
163       name_max = pathconf(".", _PC_NAME_MAX);
164       if (name_max < 1024) {
165          name_max = 1024;
166       }
167          
168       if (!(dp = opendir(dev->device->mount_point))) {
169          berrno be;
170          dev->dev_errno = errno;
171          Dmsg3(29, "open_mounted_dev: failed to open dir %s (dev=%s), ERR=%s\n", 
172                dev->device->mount_point, dev->print_name(), be.strerror());
173          goto get_out;
174       }
175       
176       entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
177       while (1) {
178          if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
179             dev->dev_errno = ENOENT;
180             Dmsg2(29, "open_mounted_dev: failed to find suitable file in dir %s (dev=%s)\n", 
181                   dev->device->mount_point, dev->print_name());
182             break;
183          }
184          count++;
185       }
186       free(entry);
187       closedir(dp);
188       if (count > 2) {
189          mount = 1;                      /* If we got more than . and .. */
190          break;                          /*   there must be something mounted */
191       }
192 get_out:
193       dev->set_mounted(false);
194       sm_check(__FILE__, __LINE__, false);
195       free_pool_memory(results);
196       Dmsg0(200, "============ DVD mount=0\n");
197       return false;
198    }
199    
200    dev->set_mounted(mount);              /* set/clear mounted flag */
201    free_pool_memory(results);
202    update_free_space_dev(dev);
203    Dmsg1(200, "============ DVD mount=%d\n", mount);
204    return true;
205 }
206
207 /* Update the free space on the device */
208 void update_free_space_dev(DEVICE* dev) 
209 {
210    POOL_MEM ocmd(PM_FNAME);
211    POOLMEM* results;
212    char* icmd;
213    int timeout;
214    long long int free;
215    char ed1[50];
216    
217    sm_check(__FILE__, __LINE__, false);
218    icmd = dev->device->free_space_command;
219    
220    if (!icmd) {
221       dev->free_space = 0;
222       dev->free_space_errno = 0;
223       dev->clear_media();
224       Dmsg2(29, "update_free_space_dev: free_space=%d, free_space_errno=%d (!icmd)\n", dev->free_space, dev->free_space_errno);
225       return;
226    }
227    
228    edit_device_codes_dev(dev, ocmd, icmd);
229    
230    Dmsg1(29, "update_free_space_dev: cmd=%s\n", ocmd.c_str());
231
232    results = get_pool_memory(PM_MESSAGE);
233    
234    /* Try at most 3 times to get the free space on the device. This should perhaps be configurable. */
235    timeout = 3;
236    
237    while (1) {
238       if (run_program_full_output(ocmd.c_str(), dev->max_open_wait/2, results) == 0) {
239          Dmsg1(100, "Free space program run : %s\n", results);
240          free = str_to_int64(results);
241          if (free >= 0) {
242             dev->free_space = free;
243             dev->free_space_errno = 1;
244             dev->set_media();
245             Mmsg0(dev->errmsg, "");
246             break;
247          }
248       }
249       dev->free_space = 0;
250       dev->free_space_errno = -EPIPE;
251       Mmsg1(dev->errmsg, "Cannot run free space command (%s)\n", results);
252       
253       if (--timeout > 0) {
254          Dmsg4(40, "Cannot get free space on device %s. free_space=%s, "
255             "free_space_errno=%d ERR=%s\n", dev->print_name(), 
256                edit_uint64(dev->free_space, ed1), dev->free_space_errno, 
257                dev->errmsg);
258          bmicrosleep(1, 0);
259          continue;
260       }
261
262       dev->dev_errno = -dev->free_space_errno;
263       Dmsg4(40, "Cannot get free space on device %s. free_space=%s, "
264          "free_space_errno=%d ERR=%s\n",
265             dev->print_name(), edit_uint64(dev->free_space, ed1),
266             dev->free_space_errno, dev->errmsg);
267       break;
268    }
269    
270    free_pool_memory(results);
271    Dmsg3(29, "update_free_space_dev: free_space=%s, free_space_errno=%d have_media=%d\n", 
272       edit_uint64(dev->free_space, ed1), dev->free_space_errno, dev->have_media());
273    sm_check(__FILE__, __LINE__, false);
274    return;
275 }
276
277 /*
278  * Write a part (Vol, Vol.1, ...) from the spool to the DVD   
279  */
280 static bool dvd_write_part(DCR *dcr) 
281 {
282    DEVICE *dev = dcr->dev;
283    POOL_MEM ocmd(PM_FNAME);
284    char* icmd;
285    int status;
286    int timeout;
287    char ed1[50];
288    
289    sm_check(__FILE__, __LINE__, false);
290    Dmsg1(29, "dvd_write_part: device is %s\n", dev->print_name());
291    icmd = dev->device->write_part_command;
292    
293    edit_device_codes_dev(dev, ocmd, icmd);
294       
295    /*
296     * Wait at most the time a maximum size part is written in DVD 0.5x speed
297     * FIXME: Minimum speed should be in device configuration 
298     */
299    timeout = dev->max_open_wait + (dev->max_part_size/(1350*1024/2));
300    
301    Dmsg2(29, "dvd_write_part: cmd=%s timeout=%d\n", ocmd.c_str(), timeout);
302       
303 {
304    POOL_MEM results(PM_MESSAGE);
305    sm_check(__FILE__, __LINE__, false);
306    status = run_program_full_output(ocmd.c_str(), timeout, results.c_str());
307    sm_check(__FILE__, __LINE__, false);
308    if (status != 0) {
309       Mmsg1(dev->errmsg, "Error while writing current part to the DVD: %s", 
310             results.c_str());
311       Dmsg1(000, "%s", dev->errmsg);
312       dev->dev_errno = EIO;
313       return false;
314    }
315    sm_check(__FILE__, __LINE__, false);
316 }
317
318 {
319    POOL_MEM archive_name(PM_FNAME);
320    /* Delete spool file */
321    make_spooled_dvd_filename(dev, archive_name);
322    unlink(archive_name.c_str());
323    Dmsg1(29, "unlink(%s)\n", archive_name.c_str());
324    sm_check(__FILE__, __LINE__, false);
325 }
326    update_free_space_dev(dev);
327    Jmsg(dcr->jcr, M_INFO, 0, _("Remaining free space %s on %s\n"), 
328       edit_uint64_with_commas(dev->free_space, ed1), dev->print_name());
329    sm_check(__FILE__, __LINE__, false);
330    return true;
331 }
332
333 /* Open the next part file.
334  *  - Close the fd
335  *  - Increment part number 
336  *  - Reopen the device
337  */
338 int open_next_part(DCR *dcr)
339 {
340    DEVICE *dev = dcr->dev;
341
342    Dmsg5(29, "Enter: ==== open_next_part part=%d npart=%d dev=%s vol=%s mode=%d\n", 
343       dev->part, dev->num_parts, dev->print_name(),
344          dev->VolCatInfo.VolCatName, dev->openmode);
345    if (!dev->is_dvd()) {
346       Dmsg1(000, "Device %s is not dvd!!!!\n", dev->print_name()); 
347       return -1;
348    }
349       
350    /* When appending, do not open a new part if the current is empty */
351    if (dev->can_append() && (dev->part >= dev->num_parts) && 
352        (dev->part_size == 0)) {
353       Dmsg0(29, "open_next_part exited immediately (dev->part_size == 0).\n");
354       return dev->fd;
355    }
356    
357    if (dev->fd >= 0) {
358       close(dev->fd);
359    }
360    
361    dev->fd = -1;
362    dev->clear_opened();
363    
364    /*
365     * If we have a part open for write, then write it to
366     *  DVD before opening the next part.
367     */
368    if (dev->is_dvd() && (dev->part >= dev->num_parts) && dev->can_append()) {
369       if (!dvd_write_part(dcr)) {
370          return -1;
371       }
372    }
373      
374    if (dev->part > dev->num_parts) {
375       Dmsg2(000, "In open_next_part: part=%d nump=%d\n", dev->part, dev->num_parts);
376       ASSERT(dev->part <= dev->num_parts);
377    }
378    dev->part_start += dev->part_size;
379    dev->part++;
380    
381    Dmsg2(29, "part=%d num_parts=%d\n", dev->part, dev->num_parts);
382    /* I think this dev->can_append() should not be there */
383    if ((dev->num_parts < dev->part) && dev->can_append()) {
384       POOL_MEM archive_name(PM_FNAME);
385       struct stat buf;
386       /* 
387        * First check what is on DVD.  If out part is there, we
388        *   are in trouble, so bail out.
389        */
390       make_mounted_dvd_filename(dev, archive_name);   /* makes dvd name */
391       if (stat(archive_name.c_str(), &buf) == 0) {
392          /* bad news bail out */
393          Mmsg1(&dev->errmsg, _("Next Volume part already exists on DVD. Cannot continue: %s\n"),
394             archive_name.c_str());
395          return -1;
396       }
397
398       Dmsg2(100, "Set npart=%d to part=%d\n", dev->num_parts, dev->part);
399       dev->num_parts = dev->part;
400       dev->VolCatInfo.VolCatParts = dev->part;
401       make_spooled_dvd_filename(dev, archive_name);   /* makes spool name */
402       
403       /* Check if the next part exists in spool directory . */
404       if ((stat(archive_name.c_str(), &buf) == 0) || (errno != ENOENT)) {
405          Dmsg1(29, "open_next_part %s is in the way, moving it away...\n", archive_name.c_str());
406          /* Then try to unlink it */
407          if (unlink(archive_name.c_str()) < 0) {
408             berrno be;
409             dev->dev_errno = errno;
410             Mmsg2(dev->errmsg, _("open_next_part can't unlink existing part %s, ERR=%s\n"), 
411                    archive_name.c_str(), be.strerror());
412             return -1;
413          }
414       }
415    }
416    if (dev->num_parts < dev->part) {
417       Dmsg2(100, "Set npart=%d to part=%d\n", dev->num_parts, dev->part);
418       dev->num_parts = dev->part;
419       dev->VolCatInfo.VolCatParts = dev->part;
420    }
421    Dmsg2(50, "Call dev->open(vol=%s, mode=%d\n", dev->VolCatInfo.VolCatName, 
422          dev->openmode);
423    /* Open next part */
424    if (dev->open(dcr, dev->openmode) < 0) {
425       return -1;
426    } 
427    dev->set_labeled();          /* all next parts are "labeled" */
428    return dev->fd;
429 }
430
431 /* Open the first part file.
432  *  - Close the fd
433  *  - Reopen the device
434  *
435  *   I don't see why this is necessary unless the current
436  *   part is not zero.
437  */
438 int open_first_part(DCR *dcr, int mode)
439 {
440    DEVICE *dev = dcr->dev;
441
442    Dmsg3(29, "Enter: ==== open_first_part dev=%s Vol=%s mode=%d\n", dev->print_name(), 
443          dev->VolCatInfo.VolCatName, dev->openmode);
444
445    if (dev->fd >= 0) {
446       close(dev->fd);
447    }
448    dev->fd = -1;
449    dev->clear_opened();
450    
451    dev->part_start = 0;
452    dev->part = 1;
453    
454    Dmsg2(50, "Call dev->open(vol=%s, mode=%d)\n", dcr->VolCatInfo.VolCatName, 
455          mode);
456    if (dev->open(dcr, mode) < 0) {
457       Dmsg0(50, "open dev() failed\n");
458       return -1;
459    }
460    Dmsg1(50, "Leave open_first_part state=%s\n", dev->is_open()?"open":"not open");
461    return dev->fd;
462 }
463
464
465 /* Protected version of lseek, which opens the right part if necessary */
466 off_t lseek_dev(DEVICE *dev, off_t offset, int whence)
467 {
468    DCR *dcr;
469    off_t pos;
470    
471    Dmsg3(100, "Enter lseek_dev fd=%d part=%d nparts=%d\n", dev->fd,
472       dev->part, dev->num_parts);
473    if (!dev->is_dvd()) { 
474       Dmsg0(100, "Using sys lseek\n");
475       return lseek(dev->fd, offset, whence);
476    }
477       
478    dcr = (DCR *)dev->attached_dcrs->first();  /* any dcr will do */
479    switch(whence) {
480    case SEEK_SET:
481       Dmsg1(100, "lseek_dev SEEK_SET to %d\n", (int)offset);
482       if ((uint64_t)offset >= dev->part_start) {
483          offset -= dev->part_start; /* adjust for start of this part */
484          if (offset == 0 || (uint64_t)offset < dev->part_size) {
485             /* We are staying in the current part, just seek */
486             if ((pos = lseek(dev->fd, offset, SEEK_SET)) < 0) {
487                return pos;   
488             } else {
489                return pos + dev->part_start;
490             }
491          } else {
492             /* Load next part, and start again */
493             if (open_next_part(dcr) < 0) {
494                Dmsg0(100, "lseek_dev failed while trying to open the next part\n");
495                return -1;
496             }
497             return lseek_dev(dev, offset, SEEK_SET);
498          }
499       } else {
500          /*
501           * pos < dev->part_start :
502           * We need to access a previous part, 
503           * so just load the first one, and seek again
504           * until the right one is loaded
505           */
506          if (open_first_part(dcr, dev->openmode) < 0) {
507             Dmsg0(100, "lseek_dev failed while trying to open the first part\n");
508             return -1;
509          }
510          return lseek_dev(dev, offset, SEEK_SET);
511       }
512       break;
513    case SEEK_CUR:
514       Dmsg1(100, "lseek_dev SEEK_CUR to %d\n", (int)offset);
515       if ((pos = lseek(dev->fd, (off_t)0, SEEK_CUR)) < 0) {
516          return pos;   
517       }
518       pos += dev->part_start;
519       if (offset == 0) {
520          return pos;
521       } else { /* Not used in Bacula, but should work */
522          return lseek_dev(dev, pos, SEEK_SET);
523       }
524       break;
525    case SEEK_END:
526       Dmsg1(100, "lseek_dev SEEK_END to %d\n", (int)offset);
527       /*
528        * Bacula does not use offsets for SEEK_END
529        *  Also, Bacula uses seek_end only when it wants to
530        *  append to the volume, so for a dvd that means
531        *  that the volume must be spooled since the DVD
532        *  itself is read-only (as currently implemented).
533        */
534       if (offset > 0) { /* Not used by bacula */
535          Dmsg1(100, "lseek_dev SEEK_END called with an invalid offset %d\n", (int)offset);
536          errno = EINVAL;
537          return -1;
538       }
539       /* If we are already on a spooled part and have the
540        *  right part number, simply seek
541        */
542       if (dev->is_part_spooled() && dev->part == dev->num_parts) {
543          if ((pos = lseek(dev->fd, (off_t)0, SEEK_END)) < 0) {
544             return pos;   
545          } else {
546             return pos + dev->part_start;
547          }
548       } else {
549          /*
550           * Load the first part, then load the next until we reach the last one.
551           * This is the only way to be sure we compute the right file address.
552           *
553           * Save previous openmode, and open all but last part read-only 
554           * (useful for DVDs) 
555           */
556          int modesave = dev->openmode;
557          /* Works because num_parts > 0. */
558          if (open_first_part(dcr, OPEN_READ_ONLY) < 0) {
559             Dmsg0(100, "lseek_dev failed while trying to open the first part\n");
560             return -1;
561          }
562          while (dev->part < (dev->num_parts-1)) {
563             if (open_next_part(dcr) < 0) {
564                Dmsg0(100, "lseek_dev failed while trying to open the next part\n");
565                return -1;
566             }
567          }
568          dev->openmode = modesave;
569          if (open_next_part(dcr) < 0) {
570             Dmsg0(100, "lseek_dev failed while trying to open the next part\n");
571             return -1;
572          }
573          return lseek_dev(dev, 0, SEEK_END);
574       }
575       break;
576    default:
577       errno = EINVAL;
578       return -1;
579    }
580 }
581
582 bool dvd_close_job(DCR *dcr)
583 {
584    DEVICE *dev = dcr->dev;
585    JCR *jcr = dcr->jcr;
586    bool ok = true;
587
588    /* If the device is a dvd and WritePartAfterJob
589     * is set to yes, open the next part, so, in case of a device
590     * that requires mount, it will be written to the device.
591     */
592    if (dev->is_dvd() && jcr->write_part_after_job && (dev->part_size > 0)) {
593       Dmsg1(100, "Writing last part=%d write_partafter_job is set.\n",
594          dev->part);
595       if (dev->part < dev->num_parts) {
596          Jmsg3(jcr, M_FATAL, 0, _("Error while writing, current part number is less than the total number of parts (%d/%d, device=%s)\n"),
597                dev->part, dev->num_parts, dev->print_name());
598          dev->dev_errno = EIO;
599          ok = false;
600       }
601       
602       /* This should be !dvd_write_part(dcr) */
603 //    if (ok && open_next_part(dcr) < 0) {
604       if (ok && !dvd_write_part(dcr)) {
605          Jmsg2(jcr, M_FATAL, 0, _("Unable to write part %s: ERR=%s\n"),
606                dev->print_name(), strerror_dev(dev));
607          dev->dev_errno = EIO;
608          ok = false;
609       }
610    }
611    Dmsg1(200, "Set VolCatParts=%d\n", dev->num_parts);
612    dev->VolCatInfo.VolCatParts = dev->num_parts;
613    return ok;
614 }
615
616
617 /*
618  * Edit codes into (Un)MountCommand, Write(First)PartCommand
619  *  %% = %
620  *  %a = archive device name
621  *  %e = erase (set if cannot mount and first part)  
622  *  %m = mount point
623  *  %v = last part name
624  *
625  *  omsg = edited output message
626  *  imsg = input string containing edit codes (%x)
627  *
628  */
629 static void edit_device_codes_dev(DEVICE* dev, POOL_MEM &omsg, const char *imsg)
630 {
631    const char *p;
632    const char *str;
633    char add[20];
634    
635    POOL_MEM archive_name(PM_FNAME);
636
637    omsg.c_str()[0] = 0;
638    Dmsg1(800, "edit_device_codes: %s\n", imsg);
639    for (p=imsg; *p; p++) {
640       if (*p == '%') {
641          switch (*++p) {
642          case '%':
643             str = "%";
644             break;
645          case 'a':
646             str = dev->dev_name;
647             break;
648          case 'e':
649             if (dev->part == 1 && !dev->is_mounted()) {
650                str = "1";
651             } else {
652                str = "0";
653             }
654             break;
655          case 'n':
656             bsnprintf(add, sizeof(add), "%d", dev->part);
657             str = add;
658             break;
659          case 'm':
660             str = dev->device->mount_point;
661             break;
662          case 'v':
663             make_spooled_dvd_filename(dev, archive_name);
664             str = archive_name.c_str();
665             break;
666          default:
667             add[0] = '%';
668             add[1] = *p;
669             add[2] = 0;
670             str = add;
671             break;
672          }
673       } else {
674          add[0] = *p;
675          add[1] = 0;
676          str = add;
677       }
678       Dmsg1(1900, "add_str %s\n", str);
679       pm_strcat(omsg, (char *)str);
680       Dmsg1(1800, "omsg=%s\n", omsg.c_str());
681    }
682 }