]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/stored/dvd.c
Minor modifications outside DVD functions.
[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 > 0) {
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(90, "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(90, "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    /* Do not check free space when unmounting (otherwise it will mount it again) */
203    if (mount) {
204       update_free_space_dev(dev);
205    }
206    Dmsg1(200, "============ DVD mount=%d\n", mount);
207    return true;
208 }
209
210 /* Update the free space on the device */
211 void update_free_space_dev(DEVICE* dev) 
212 {
213    POOL_MEM ocmd(PM_FNAME);
214    POOLMEM* results;
215    char* icmd;
216    int timeout;
217    long long int free;
218    char ed1[50];
219    
220    /* The device must be mounted in order to dvd-freespace to work */
221    mount_dev(dev, 1);
222    
223    sm_check(__FILE__, __LINE__, false);
224    icmd = dev->device->free_space_command;
225    
226    if (!icmd) {
227       dev->free_space = 0;
228       dev->free_space_errno = 0;
229       dev->clear_media();
230       Dmsg2(29, "update_free_space_dev: free_space=%d, free_space_errno=%d (!icmd)\n", dev->free_space, dev->free_space_errno);
231       return;
232    }
233    
234    edit_device_codes_dev(dev, ocmd, icmd);
235    
236    Dmsg1(29, "update_free_space_dev: cmd=%s\n", ocmd.c_str());
237
238    results = get_pool_memory(PM_MESSAGE);
239    
240    /* Try at most 3 times to get the free space on the device. This should perhaps be configurable. */
241    timeout = 3;
242    
243    while (1) {
244       if (run_program_full_output(ocmd.c_str(), dev->max_open_wait/2, results) == 0) {
245          Dmsg1(100, "Free space program run : %s\n", results);
246          free = str_to_int64(results);
247          if (free >= 0) {
248             dev->free_space = free;
249             dev->free_space_errno = 1;
250             dev->set_media();
251             Mmsg0(dev->errmsg, "");
252             break;
253          }
254       }
255       dev->free_space = 0;
256       dev->free_space_errno = -EPIPE;
257       Mmsg1(dev->errmsg, "Cannot run free space command (%s)\n", results);
258       
259       if (--timeout > 0) {
260          Dmsg4(40, "Cannot get free space on device %s. free_space=%s, "
261             "free_space_errno=%d ERR=%s\n", dev->print_name(), 
262                edit_uint64(dev->free_space, ed1), dev->free_space_errno, 
263                dev->errmsg);
264          bmicrosleep(1, 0);
265          continue;
266       }
267
268       dev->dev_errno = -dev->free_space_errno;
269       Dmsg4(40, "Cannot get free space on device %s. free_space=%s, "
270          "free_space_errno=%d ERR=%s\n",
271             dev->print_name(), edit_uint64(dev->free_space, ed1),
272             dev->free_space_errno, dev->errmsg);
273       break;
274    }
275    
276    free_pool_memory(results);
277    Dmsg3(29, "update_free_space_dev: free_space=%s, free_space_errno=%d have_media=%d\n", 
278       edit_uint64(dev->free_space, ed1), dev->free_space_errno, dev->have_media());
279    sm_check(__FILE__, __LINE__, false);
280    return;
281 }
282
283 /*
284  * Write a part (Vol, Vol.1, ...) from the spool to the DVD   
285  * This MUST only be called from open_next_part. Otherwise the part number
286  * is not updated.
287  */
288 static bool dvd_write_part(DCR *dcr) 
289 {
290    DEVICE *dev = dcr->dev;
291    POOL_MEM ocmd(PM_FNAME);
292    char* icmd;
293    int status;
294    int timeout;
295    char ed1[50];
296    
297    sm_check(__FILE__, __LINE__, false);
298    Dmsg3(29, "dvd_write_part: device is %s, part is %d, is_mounted=%d\n", dev->print_name(), dev->part, dev->is_mounted());
299    icmd = dev->device->write_part_command;
300    
301    edit_device_codes_dev(dev, ocmd, icmd);
302       
303    /*
304     * Wait at most the time a maximum size part is written in DVD 0.5x speed
305     * FIXME: Minimum speed should be in device configuration 
306     */
307    timeout = dev->max_open_wait + (dev->max_part_size/(1350*1024/2));
308    
309    Dmsg2(29, "dvd_write_part: cmd=%s timeout=%d\n", ocmd.c_str(), timeout);
310       
311 {
312    POOL_MEM results(PM_MESSAGE);
313    sm_check(__FILE__, __LINE__, false);
314    status = run_program_full_output(ocmd.c_str(), timeout, results.c_str());
315    sm_check(__FILE__, __LINE__, false);
316    if (status != 0) {
317       Mmsg1(dev->errmsg, "Error while writing current part to the DVD: %s", 
318             results.c_str());
319       Dmsg1(000, "%s", dev->errmsg);
320       dev->dev_errno = EIO;
321       return false;
322    }
323    sm_check(__FILE__, __LINE__, false);
324 }
325
326 {
327    POOL_MEM archive_name(PM_FNAME);
328    /* Delete spool file */
329    make_spooled_dvd_filename(dev, archive_name);
330    unlink(archive_name.c_str());
331    Dmsg1(29, "unlink(%s)\n", archive_name.c_str());
332    sm_check(__FILE__, __LINE__, false);
333 }
334    
335    /* growisofs umount the device, so remount it (it will update the free space) */
336    dev->clear_mounted();
337    mount_dev(dev, 1);
338    Jmsg(dcr->jcr, M_INFO, 0, _("Remaining free space %s on %s\n"), 
339       edit_uint64_with_commas(dev->free_space, ed1), dev->print_name());
340    sm_check(__FILE__, __LINE__, false);
341    return true;
342 }
343
344 /* Open the next part file.
345  *  - Close the fd
346  *  - Increment part number 
347  *  - Reopen the device
348  */
349 int open_next_part(DCR *dcr)
350 {
351    DEVICE *dev = dcr->dev;
352
353    Dmsg6(29, "Enter: ==== open_next_part part=%d npart=%d dev=%s vol=%s mode=%d file_addr=%d\n", 
354       dev->part, dev->num_parts, dev->print_name(),
355          dev->VolCatInfo.VolCatName, dev->openmode, dev->file_addr);
356    if (!dev->is_dvd()) {
357       Dmsg1(000, "Device %s is not dvd!!!!\n", dev->print_name()); 
358       return -1;
359    }
360    
361    /* When appending, do not open a new part if the current is empty */
362    if (dev->can_append() && (dev->part >= dev->num_parts) && 
363        (dev->part_size == 0)) {
364       Dmsg0(29, "open_next_part exited immediately (dev->part_size == 0).\n");
365       return dev->fd;
366    }
367    
368    if (dev->fd >= 0) {
369       close(dev->fd);
370    }
371    
372    dev->fd = -1;
373    dev->clear_opened();
374    
375    /*
376     * If we have a part open for write, then write it to
377     *  DVD before opening the next part.
378     */
379    if (dev->is_dvd() && (dev->part >= dev->num_parts) && dev->can_append()) {
380       if (!dvd_write_part(dcr)) {
381          return -1;
382       }
383    }
384      
385    if (dev->part > dev->num_parts) {
386       Dmsg2(000, "In open_next_part: part=%d nump=%d\n", dev->part, dev->num_parts);
387       ASSERT(dev->part <= dev->num_parts);
388    }
389    dev->part_start += dev->part_size;
390    dev->part++;
391    
392    Dmsg2(29, "part=%d num_parts=%d\n", dev->part, dev->num_parts);
393    /* I think this dev->can_append() should not be there */
394    if ((dev->num_parts < dev->part) && dev->can_append()) {
395       POOL_MEM archive_name(PM_FNAME);
396       struct stat buf;
397       /* 
398        * First check what is on DVD.  If our part is there, we
399        *   are in trouble, so bail out.
400        * NB: This is however not a problem if we are writing the first part.
401        * It simply means that we are overriding an existing volume...
402        */
403       if (dev->num_parts > 0) {
404          make_mounted_dvd_filename(dev, archive_name);   /* makes dvd name */
405          if (stat(archive_name.c_str(), &buf) == 0) {
406             /* bad news bail out */
407             Mmsg1(&dev->errmsg, _("Next Volume part already exists on DVD. Cannot continue: %s\n"),
408                archive_name.c_str());
409             return -1;
410          }
411       }
412
413       Dmsg2(100, "Set npart=%d to part=%d\n", dev->num_parts, dev->part);
414       dev->num_parts = dev->part;
415       dev->VolCatInfo.VolCatParts = dev->part;
416       make_spooled_dvd_filename(dev, archive_name);   /* makes spool name */
417       
418       /* Check if the next part exists in spool directory . */
419       if ((stat(archive_name.c_str(), &buf) == 0) || (errno != ENOENT)) {
420          Dmsg1(29, "open_next_part %s is in the way, moving it away...\n", archive_name.c_str());
421          /* Then try to unlink it */
422          if (unlink(archive_name.c_str()) < 0) {
423             berrno be;
424             dev->dev_errno = errno;
425             Mmsg2(dev->errmsg, _("open_next_part can't unlink existing part %s, ERR=%s\n"), 
426                    archive_name.c_str(), be.strerror());
427             return -1;
428          }
429       }
430    }
431    if (dev->num_parts < dev->part) {
432       Dmsg2(100, "Set npart=%d to part=%d\n", dev->num_parts, dev->part);
433       dev->num_parts = dev->part;
434       dev->VolCatInfo.VolCatParts = dev->part;
435    }
436    Dmsg2(50, "Call dev->open(vol=%s, mode=%d\n", dev->VolCatInfo.VolCatName, 
437          dev->openmode);
438    /* Open next part */
439    
440    int append = dev->can_append();
441    if (dev->open(dcr, dev->openmode) < 0) {
442       return -1;
443    } 
444    dev->set_labeled();          /* all next parts are "labeled" */
445    if (append && (dev->part == dev->num_parts)) { /* If needed, set the append flag back */
446       dev->set_append();
447    }
448    
449    return dev->fd;
450 }
451
452 /* Open the first part file.
453  *  - Close the fd
454  *  - Reopen the device
455  */
456 int open_first_part(DCR *dcr, int mode)
457 {
458    DEVICE *dev = dcr->dev;
459
460    Dmsg3(29, "Enter: ==== open_first_part dev=%s Vol=%s mode=%d\n", dev->print_name(), 
461          dev->VolCatInfo.VolCatName, dev->openmode);
462
463    if (dev->fd >= 0) {
464       close(dev->fd);
465    }
466    dev->fd = -1;
467    dev->clear_opened();
468    
469    dev->part_start = 0;
470    dev->part = 0;
471    
472    Dmsg2(50, "Call dev->open(vol=%s, mode=%d)\n", dcr->VolCatInfo.VolCatName, 
473          mode);
474    if (dev->open(dcr, mode) < 0) {
475       Dmsg0(50, "open dev() failed\n");
476       return -1;
477    }
478    Dmsg1(50, "Leave open_first_part state=%s\n", dev->is_open()?"open":"not open");
479    return dev->fd;
480 }
481
482
483 /* Protected version of lseek, which opens the right part if necessary */
484 off_t lseek_dev(DEVICE *dev, off_t offset, int whence)
485 {
486    DCR *dcr;
487    off_t pos;
488    
489    Dmsg3(100, "Enter lseek_dev fd=%d part=%d nparts=%d\n", dev->fd,
490       dev->part, dev->num_parts);
491    if (!dev->is_dvd()) { 
492       Dmsg0(100, "Using sys lseek\n");
493       return lseek(dev->fd, offset, whence);
494    }
495       
496    dcr = (DCR *)dev->attached_dcrs->first();  /* any dcr will do */
497    switch(whence) {
498    case SEEK_SET:
499       Dmsg2(100, "lseek_dev SEEK_SET to %d (part_start=%d)\n", (int)offset, (int)dev->part_start);
500       if ((uint64_t)offset >= dev->part_start) {
501          if (((uint64_t)offset == dev->part_start) || ((uint64_t)offset < (dev->part_start+dev->part_size))) {
502             /* We are staying in the current part, just seek */
503             if ((pos = lseek(dev->fd, offset-dev->part_start, SEEK_SET)) < 0) {
504                return pos;
505             } else {
506                return pos + dev->part_start;
507             }
508          } else {
509             /* Load next part, and start again */
510             if (open_next_part(dcr) < 0) {
511                Dmsg0(100, "lseek_dev failed while trying to open the next part\n");
512                return -1;
513             }
514             return lseek_dev(dev, offset, SEEK_SET);
515          }
516       } else {
517          /*
518           * pos < dev->part_start :
519           * We need to access a previous part, 
520           * so just load the first one, and seek again
521           * until the right one is loaded
522           */
523          if (open_first_part(dcr, dev->openmode) < 0) {
524             Dmsg0(100, "lseek_dev failed while trying to open the first part\n");
525             return -1;
526          }
527          return lseek_dev(dev, offset, SEEK_SET);
528       }
529       break;
530    case SEEK_CUR:
531       Dmsg1(100, "lseek_dev SEEK_CUR to %d\n", (int)offset);
532       if ((pos = lseek(dev->fd, (off_t)0, SEEK_CUR)) < 0) {
533          return pos;   
534       }
535       pos += dev->part_start;
536       if (offset == 0) {
537          Dmsg1(100, "lseek_dev SEEK_CUR returns %d\n", pos);
538          return pos;
539       } else { /* Not used in Bacula, but should work */
540          return lseek_dev(dev, pos, SEEK_SET);
541       }
542       break;
543    case SEEK_END:
544       Dmsg1(100, "lseek_dev SEEK_END to %d\n", (int)offset);
545       /*
546        * Bacula does not use offsets for SEEK_END
547        *  Also, Bacula uses seek_end only when it wants to
548        *  append to the volume, so for a dvd that means
549        *  that the volume must be spooled since the DVD
550        *  itself is read-only (as currently implemented).
551        */
552       if (offset > 0) { /* Not used by bacula */
553          Dmsg1(100, "lseek_dev SEEK_END called with an invalid offset %d\n", (int)offset);
554          errno = EINVAL;
555          return -1;
556       }
557       /* If we are already on a spooled part and have the
558        *  right part number, simply seek
559        */
560       if (dev->is_part_spooled() && dev->part == dev->num_parts) {
561          if ((pos = lseek(dev->fd, (off_t)0, SEEK_END)) < 0) {
562             return pos;   
563          } else {
564             Dmsg1(100, "lseek_dev SEEK_END returns %d\n", pos + dev->part_start);
565             return pos + dev->part_start;
566          }
567       } else {
568          /*
569           * Load the first part, then load the next until we reach the last one.
570           * This is the only way to be sure we compute the right file address.
571           *
572           * Save previous openmode, and open all but last part read-only 
573           * (useful for DVDs) 
574           */
575          int modesave = dev->openmode;
576          /* Works because num_parts > 0. */
577          if (open_first_part(dcr, OPEN_READ_ONLY) < 0) {
578             Dmsg0(100, "lseek_dev failed while trying to open the first part\n");
579             return -1;
580          }
581          if (dev->num_parts > 0) {
582             while (dev->part < (dev->num_parts-1)) {
583                if (open_next_part(dcr) < 0) {
584                   Dmsg0(100, "lseek_dev failed while trying to open the next part\n");
585                   return -1;
586                }
587             }
588             dev->openmode = modesave;
589             if (open_next_part(dcr) < 0) {
590                Dmsg0(100, "lseek_dev failed while trying to open the next part\n");
591                return -1;
592             }
593          }
594          return lseek_dev(dev, 0, SEEK_END);
595       }
596       break;
597    default:
598       errno = EINVAL;
599       return -1;
600    }
601 }
602
603 bool dvd_close_job(DCR *dcr)
604 {
605    DEVICE *dev = dcr->dev;
606    JCR *jcr = dcr->jcr;
607    bool ok = true;
608
609    /* If the device is a dvd and WritePartAfterJob
610     * is set to yes, open the next part, so, in case of a device
611     * that requires mount, it will be written to the device.
612     */
613    if (dev->is_dvd() && jcr->write_part_after_job && (dev->part_size > 0)) {
614       Dmsg1(100, "Writing last part=%d write_partafter_job is set.\n",
615          dev->part);
616       if (dev->part < dev->num_parts) {
617          Jmsg3(jcr, M_FATAL, 0, _("Error while writing, current part number is less than the total number of parts (%d/%d, device=%s)\n"),
618                dev->part, dev->num_parts, dev->print_name());
619          dev->dev_errno = EIO;
620          ok = false;
621       }
622       
623       /* This should be !dvd_write_part(dcr)
624          NB: No! If you call dvd_write_part, the part number is not updated.
625          You must open the next part, it will automatically write the part and
626          update the part number. */
627       if (ok && (open_next_part(dcr) < 0)) {
628 //      if (ok && !dvd_write_part(dcr)) {
629          Jmsg2(jcr, M_FATAL, 0, _("Unable to write part %s: ERR=%s\n"),
630                dev->print_name(), strerror_dev(dev));
631          dev->dev_errno = EIO;
632          ok = false;
633       }
634    }
635    Dmsg1(200, "Set VolCatParts=%d\n", dev->num_parts);
636    dev->VolCatInfo.VolCatParts = dev->num_parts;
637    return ok;
638 }
639
640
641 /*
642  * Edit codes into (Un)MountCommand, Write(First)PartCommand
643  *  %% = %
644  *  %a = archive device name
645  *  %e = erase (set if cannot mount and first part)
646  *  %m = mount point
647  *  %v = last part name
648  *
649  *  omsg = edited output message
650  *  imsg = input string containing edit codes (%x)
651  *
652  */
653 static void edit_device_codes_dev(DEVICE* dev, POOL_MEM &omsg, const char *imsg)
654 {
655    const char *p;
656    const char *str;
657    char add[20];
658    
659    POOL_MEM archive_name(PM_FNAME);
660
661    omsg.c_str()[0] = 0;
662    Dmsg1(800, "edit_device_codes: %s\n", imsg);
663    for (p=imsg; *p; p++) {
664       if (*p == '%') {
665          switch (*++p) {
666          case '%':
667             str = "%";
668             break;
669          case 'a':
670             str = dev->dev_name;
671             break;
672          case 'e':
673             if (dev->part == 0 && !dev->is_mounted()) {
674                str = "1";
675             } else {
676                str = "0";
677             }
678             break;
679          case 'n':
680             bsnprintf(add, sizeof(add), "%d", dev->part);
681             str = add;
682             break;
683          case 'm':
684             str = dev->device->mount_point;
685             break;
686          case 'v':
687             make_spooled_dvd_filename(dev, archive_name);
688             str = archive_name.c_str();
689             break;
690          default:
691             add[0] = '%';
692             add[1] = *p;
693             add[2] = 0;
694             str = add;
695             break;
696          }
697       } else {
698          add[0] = *p;
699          add[1] = 0;
700          str = add;
701       }
702       Dmsg1(1900, "add_str %s\n", str);
703       pm_strcat(omsg, (char *)str);
704       Dmsg1(1800, "omsg=%s\n", omsg.c_str());
705    }
706 }