3 * Routines for handling the autochanger.
5 * Kern Sibbald, August MMII
10 Copyright (C) 2002-2006 Kern Sibbald
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 version 2 as amended with additional clauses defined in the
15 file LICENSE in the main source directory.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 the file LICENSE for additional details.
24 #include "bacula.h" /* pull in global headers */
25 #include "stored.h" /* pull in Storage Deamon headers */
27 /* Forward referenced functions */
28 static void lock_changer(DCR *dcr);
29 static void unlock_changer(DCR *dcr);
30 static bool unload_other_drive(DCR *dcr, int slot);
32 /* Init all the autochanger resources found */
33 bool init_autochangers()
37 /* Ensure that the media_type for each device is the same */
38 foreach_res(changer, R_AUTOCHANGER) {
40 foreach_alist(device, changer->device) {
42 * If the device does not have a changer name or changer command
43 * defined, used the one from the Autochanger resource
45 if (!device->changer_name && changer->changer_name) {
46 device->changer_name = bstrdup(changer->changer_name);
48 if (!device->changer_command && changer->changer_command) {
49 device->changer_command = bstrdup(changer->changer_command);
51 if (!device->changer_name) {
52 Jmsg(NULL, M_ERROR, 0,
53 _("No Changer Name given for device %s. Cannot continue.\n"),
57 if (!device->changer_command) {
58 Jmsg(NULL, M_ERROR, 0,
59 _("No Changer Command given for device %s. Cannot continue.\n"),
65 if (media_type == NULL) {
66 media_type = device->media_type; /* get Media Type of first device */
69 /* Ensure that other devices Media Types are the same */
70 if (strcmp(media_type, device->media_type) != 0) {
71 Jmsg(NULL, M_ERROR, 0,
72 _("Media Type not the same for all devices in changer %s. Cannot continue.\n"),
85 * Called here to do an autoload using the autochanger, if
86 * configured, and if a Slot has been defined for this Volume.
87 * On success this routine loads the indicated tape, but the
88 * label is not read, so it must be verified.
90 * Note if dir is not NULL, it is the console requesting the
91 * autoload for labeling, so we respond directly to the
94 * Returns: 1 on success
95 * 0 on failure (no changer available)
96 * -1 on error on autochanger
98 int autoload_device(DCR *dcr, int writing, BSOCK *dir)
101 DEVICE *dev = dcr->dev;
103 int drive = dev->drive_index;
104 int rtn_stat = -1; /* error status */
107 if (!dev->is_autochanger()) {
108 Dmsg0(200, "======== NOT AUTOCHANGER ======\n");
111 slot = dcr->VolCatInfo.InChanger ? dcr->VolCatInfo.Slot : 0;
113 * Handle autoloaders here. If we cannot autoload it, we
114 * will return 0 so that the sysop will be asked to load it.
116 if (writing && slot <= 0) {
118 return 0; /* For user, bail out right now */
120 if (dir_find_next_appendable_volume(dcr)) {
121 slot = dcr->VolCatInfo.InChanger ? dcr->VolCatInfo.Slot : 0;
126 Dmsg1(400, "Want changer slot=%d\n", slot);
128 changer = get_pool_memory(PM_FNAME);
129 if (slot > 0 && dcr->device->changer_name && dcr->device->changer_command) {
130 uint32_t timeout = dcr->device->max_changer_wait;
133 loaded = get_autochanger_loaded_slot(dcr);
135 if (loaded != slot) {
137 /* Unload anything in our drive */
138 if (!unload_autochanger(dcr, loaded)) {
142 /* Make sure desired slot is unloaded */
143 if (!unload_other_drive(dcr, slot)) {
148 * Load the desired cassette
151 Dmsg1(400, "Doing changer load slot %d\n", slot);
153 _("3304 Issuing autochanger \"load slot %d, drive %d\" command.\n"),
155 dcr->VolCatInfo.Slot = slot; /* slot to be loaded */
156 changer = edit_device_codes(dcr, changer,
157 dcr->device->changer_command, "load");
159 Dmsg1(200, "Run program=%s\n", changer);
160 status = run_program(changer, timeout, NULL);
162 Jmsg(jcr, M_INFO, 0, _("3305 Autochanger \"load slot %d, drive %d\", status is OK.\n"),
164 dev->Slot = slot; /* set currently loaded slot */
167 be.set_errno(status);
168 Jmsg(jcr, M_FATAL, 0, _("3992 Bad autochanger \"load slot %d, drive %d\": ERR=%s.\n"),
169 slot, drive, be.strerror());
170 rtn_stat = -1; /* hard error */
172 Dmsg2(400, "load slot %d status=%d\n", slot, status);
175 status = 0; /* we got what we want */
176 dev->Slot = slot; /* set currently loaded slot */
178 Dmsg1(400, "After changer, status=%d\n", status);
179 if (status == 0) { /* did we succeed? */
180 rtn_stat = 1; /* tape loaded by changer */
183 rtn_stat = 0; /* no changer found */
185 free_pool_memory(changer);
189 free_pool_memory(changer);
195 * Returns: -1 if error from changer command
197 * Note, this is safe to do without releasing the drive
198 * since it does not attempt load/unload a slot.
200 int get_autochanger_loaded_slot(DCR *dcr)
203 POOLMEM *changer, *results;
205 uint32_t timeout = dcr->device->max_changer_wait;
206 int drive = dcr->dev->drive_index;
208 if (!dcr->device->changer_command) {
209 Jmsg(jcr, M_FATAL, 0, _("3992 Missing Changer command.\n"));
213 results = get_pool_memory(PM_MESSAGE);
214 changer = get_pool_memory(PM_FNAME);
217 /* Find out what is loaded, zero means device is unloaded */
219 Jmsg(jcr, M_INFO, 0, _("3301 Issuing autochanger \"loaded drive %d\" command.\n"),
221 changer = edit_device_codes(dcr, changer, dcr->device->changer_command, "loaded");
223 Dmsg1(200, "Run program=%s\n", changer);
224 status = run_program(changer, timeout, results);
225 Dmsg3(200, "run_prog: %s stat=%d result=%s\n", changer, status, results);
227 loaded = str_to_int32(results);
229 Jmsg(jcr, M_INFO, 0, _("3302 Autochanger \"loaded drive %d\", result is Slot %d.\n"),
231 dcr->dev->Slot = loaded;
233 Jmsg(jcr, M_INFO, 0, _("3302 Autochanger \"loaded drive %d\", result: nothing loaded.\n"),
239 be.set_errno(status);
240 Jmsg(jcr, M_INFO, 0, _("3991 Bad autochanger \"loaded drive %d\" command: ERR=%s.\n"),
241 drive, be.strerror());
242 loaded = -1; /* force unload */
245 free_pool_memory(changer);
246 free_pool_memory(results);
250 static void lock_changer(DCR *dcr)
252 AUTOCHANGER *changer_res = dcr->device->changer_res;
254 Dmsg1(100, "Locking changer %s\n", changer_res->hdr.name);
255 P(changer_res->changer_mutex); /* Lock changer script */
259 static void unlock_changer(DCR *dcr)
261 AUTOCHANGER *changer_res = dcr->device->changer_res;
263 Dmsg1(100, "Unlocking changer %s\n", changer_res->hdr.name);
264 V(changer_res->changer_mutex); /* Unlock changer script */
269 * Unload the volume, if any, in this drive
270 * On entry: loaded == 0 -- nothing to do
271 * loaded < 0 -- check if anything to do
272 * loaded > 0 -- load slot == loaded
274 bool unload_autochanger(DCR *dcr, int loaded)
276 DEVICE *dev = dcr->dev;
279 uint32_t timeout = dcr->device->max_changer_wait;
286 if (!dev->is_autochanger() || !dcr->device->changer_name ||
287 !dcr->device->changer_command) {
292 loaded = get_autochanger_loaded_slot(dcr);
296 POOLMEM *changer = get_pool_memory(PM_FNAME);
299 _("3307 Issuing autochanger \"unload slot %d, drive %d\" command.\n"),
300 loaded, dev->drive_index);
301 slot = dcr->VolCatInfo.Slot;
302 dcr->VolCatInfo.Slot = loaded;
303 changer = edit_device_codes(dcr, changer,
304 dcr->device->changer_command, "unload");
306 Dmsg1(200, "Run program=%s\n", changer);
307 int stat = run_program(changer, timeout, NULL);
308 dcr->VolCatInfo.Slot = slot;
312 Jmsg(jcr, M_INFO, 0, _("3995 Bad autochanger \"unload slot %d, drive %d\": ERR=%s.\n"),
313 slot, dev->drive_index, be.strerror());
316 dev->Slot = 0; /* nothing loaded */
318 free_pool_memory(changer);
325 * Unload the slot if mounted in a different drive
327 static bool unload_other_drive(DCR *dcr, int slot)
333 uint32_t timeout = dcr->device->max_changer_wait;
335 AUTOCHANGER *changer = dcr->dev->device->changer_res;
343 if (changer->device->size() == 1) {
347 foreach_alist(device, changer->device) {
348 if (device->dev && device->dev->Slot == slot) {
358 /* The Volume we want is on another device. */
360 for (int i=0; i < 3; i++) {
361 if (dev->is_busy()) {
362 wait_for_device(dcr->jcr, first);
369 if (dev->is_busy()) {
370 Jmsg(jcr, M_WARNING, 0, _("Volume \"%s\" is in use by device %s\n"),
371 dcr->VolumeName, dev->print_name());
372 Dmsg2(200, "Volume \"%s\" is in use by device %s\n",
373 dcr->VolumeName, dev->print_name());
378 POOLMEM *changer_cmd = get_pool_memory(PM_FNAME);
381 _("3307 Issuing autochanger \"unload slot %d, drive %d\" command.\n"),
382 slot, dev->drive_index);
384 Dmsg2(200, "Issuing autochanger \"unload slot %d, drive %d\" command.\n",
385 slot, dev->drive_index);
389 save_slot = dcr->VolCatInfo.Slot;
390 dcr->VolCatInfo.Slot = slot;
391 changer_cmd = edit_device_codes(dcr, changer_cmd,
392 dcr->device->changer_command, "unload");
394 Dmsg2(200, "close dev=%s reserve=%d\n", dev->print_name(),
395 dev->reserved_device);
396 Dmsg1(200, "Run program=%s\n", changer_cmd);
397 int stat = run_program(changer_cmd, timeout, NULL);
398 dcr->VolCatInfo.Slot = save_slot;
403 Jmsg(jcr, M_INFO, 0, _("3995 Bad autochanger \"unload slot %d, drive %d\": ERR=%s.\n"),
404 slot, dev->drive_index, be.strerror());
406 Dmsg3(200, "Bad autochanger \"unload slot %d, drive %d\": ERR=%s.\n",
407 slot, dev->drive_index, be.strerror());
410 dev->Slot = 0; /* nothing loaded */
411 Dmsg0(200, "Slot unloaded\n");
415 free_pool_memory(changer_cmd);
422 * List the Volumes that are in the autoloader possibly
423 * with their barcodes.
424 * We assume that it is always the Console that is calling us.
426 bool autochanger_cmd(DCR *dcr, BSOCK *dir, const char *cmd)
428 DEVICE *dev = dcr->dev;
429 uint32_t timeout = dcr->device->max_changer_wait;
432 int len = sizeof_pool_memory(dir->msg) - 1;
436 if (!dev->is_autochanger() || !dcr->device->changer_name ||
437 !dcr->device->changer_command) {
438 if (strcmp(cmd, "drives") == 0) {
439 bnet_fsend(dir, "drives=1\n");
441 bnet_fsend(dir, _("3993 Device %s not an autochanger device.\n"),
447 if (strcmp(cmd, "list") == 0) {
448 unload_autochanger(dcr, -1);
450 if (strcmp(cmd, "drives") == 0) {
451 AUTOCHANGER *changer_res = dcr->device->changer_res;
454 drives = changer_res->device->size();
456 bnet_fsend(dir, "drives=%d\n", drives);
457 Dmsg1(100, "drives=%d\n", drives);
461 changer = get_pool_memory(PM_FNAME);
463 /* Now issue the command */
464 changer = edit_device_codes(dcr, changer,
465 dcr->device->changer_command, cmd);
466 bnet_fsend(dir, _("3306 Issuing autochanger \"%s\" command.\n"), cmd);
467 bpipe = open_bpipe(changer, timeout, "r");
469 bnet_fsend(dir, _("3996 Open bpipe failed.\n"));
472 if (strcmp(cmd, "list") == 0) {
473 /* Get output from changer */
474 while (fgets(dir->msg, len, bpipe->rfd)) {
475 dir->msglen = strlen(dir->msg);
476 Dmsg1(100, "<stored: %s\n", dir->msg);
479 } else if (strcmp(cmd, "slots") == 0 ) {
481 /* For slots command, read a single line */
483 fgets(buf, sizeof(buf)-1, bpipe->rfd);
484 buf[sizeof(buf)-1] = 0;
485 /* Strip any leading space in front of # of slots */
486 for (p=buf; B_ISSPACE(*p); p++)
488 bnet_fsend(dir, "slots=%s", p);
489 Dmsg1(100, "<stored: %s", dir->msg);
492 stat = close_bpipe(bpipe);
496 bnet_fsend(dir, _("Autochanger error: ERR=%s\n"), be.strerror());
498 bnet_sig(dir, BNET_EOD);
503 free_pool_memory(changer);
509 * Edit codes into ChangerCommand
511 * %a = archive device name
512 * %c = changer device name
513 * %d = changer drive index
522 * omsg = edited output message
523 * imsg = input string containing edit codes (%x)
524 * cmd = command string (load, unload, ...)
527 char *edit_device_codes(DCR *dcr, char *omsg, const char *imsg, const char *cmd)
534 Dmsg1(1800, "edit_device_codes: %s\n", imsg);
535 for (p=imsg; *p; p++) {
542 str = dcr->dev->archive_name();
545 str = NPRT(dcr->device->changer_name);
548 sprintf(add, "%d", dcr->dev->drive_index);
555 sprintf(add, "%d", dcr->VolCatInfo.Slot - 1);
559 sprintf(add, "%d", dcr->VolCatInfo.Slot);
562 case 'j': /* Job name */
566 str = NPRT(dcr->VolumeName);
569 str = NPRT(dcr->jcr->client_name);
584 Dmsg1(1900, "add_str %s\n", str);
585 pm_strcat(&omsg, (char *)str);
586 Dmsg1(1800, "omsg=%s\n", omsg);
588 Dmsg1(800, "omsg=%s\n", omsg);