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