2 * Drive reservation functions for Storage Daemon
6 * Split from job.c and acquire.c June 2005
12 Copyright (C) 2000-2005 Kern Sibbald
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License
16 version 2 as amended with additional clauses defined in the
17 file LICENSE in the main source directory.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 the file LICENSE for additional details.
30 * Use Device command from Director
31 * He tells is what Device Name to use, the Media Type,
32 * the Pool Name, and the Pool Type.
34 * Ensure that the device exists and is opened, then store
35 * the media and pool info in the JCR. This class is used
36 * only temporarily in this file.
42 char name[MAX_NAME_LENGTH];
43 char media_type[MAX_NAME_LENGTH];
44 char pool_name[MAX_NAME_LENGTH];
45 char pool_type[MAX_NAME_LENGTH];
56 bool PreferMountedVols;
59 static dlist *vol_list = NULL;
60 static pthread_mutex_t vol_list_lock = PTHREAD_MUTEX_INITIALIZER;
62 /* Forward referenced functions */
63 static int can_reserve_drive(DCR *dcr, bool PerferMountedVols);
64 static int search_res_for_device(RCTX &rctx);
65 static int reserve_device(RCTX &rctx);
66 static bool reserve_device_for_read(DCR *dcr);
67 static bool reserve_device_for_append(DCR *dcr, bool PreferMountedVols);
68 static bool use_storage_cmd(JCR *jcr);
69 bool find_suitable_device_for_job(JCR *jcr, RCTX &rctx);
71 /* Requests from the Director daemon */
72 static char use_storage[] = "use storage=%127s media_type=%127s "
73 "pool_name=%127s pool_type=%127s append=%d copy=%d stripe=%d\n";
74 static char use_device[] = "use device=%127s\n";
76 /* Responses sent to Director daemon */
77 static char OK_device[] = "3000 OK use device device=%s\n";
78 static char NO_device[] = "3924 Device \"%s\" not in SD Device resources.\n";
79 static char BAD_use[] = "3913 Bad use command: %s\n";
81 bool use_cmd(JCR *jcr)
84 * Get the device, media, and pool information
86 if (!use_storage_cmd(jcr)) {
87 set_jcr_job_status(jcr, JS_ErrorTerminated);
88 memset(jcr->sd_auth_key, 0, strlen(jcr->sd_auth_key));
94 static int my_compare(void *item1, void *item2)
96 return strcmp(((VOLRES *)item1)->vol_name, ((VOLRES *)item2)->vol_name);
101 * Put a new Volume entry in the Volume list. This
102 * effectively reserves the volume so that it will
103 * not be mounted again.
105 * Return: VOLRES entry on success
106 * NULL if the Volume is already in the list
108 VOLRES *new_volume(DCR *dcr, const char *VolumeName)
111 vol = (VOLRES *)malloc(sizeof(VOLRES));
112 memset(vol, 0, sizeof(VOLRES));
113 vol->vol_name = bstrdup(VolumeName);
117 nvol = (VOLRES *)vol_list->binary_insert(vol, my_compare);
123 nvol->dev = dcr->dev;
131 * Search for a Volume name in the Volume list.
133 * Returns: VOLRES entry on success
134 * NULL if the Volume is not in the list
136 VOLRES *find_volume(const char *VolumeName)
139 vol.vol_name = bstrdup(VolumeName);
141 fvol = (VOLRES *)vol_list->binary_search(&vol, my_compare);
148 * Free a Volume from the Volume list
150 * Returns: true if the Volume found and removed from the list
151 * false if the Volume is not in the list
153 bool free_volume(DEVICE *dev)
157 if (dev->VolHdr.VolumeName[0] == 0) {
160 vol.vol_name = bstrdup(dev->VolHdr.VolumeName);
162 fvol = (VOLRES *)vol_list->binary_search(&vol, my_compare);
164 vol_list->remove(fvol);
165 free(fvol->vol_name);
170 dev->VolHdr.VolumeName[0] = 0;
174 /* Free volume reserved by this dcr but not attached to a dev */
175 void free_unused_volume(DCR *dcr)
179 for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
180 if (vol->dcr == dcr && (vol->dev == NULL ||
181 strcmp(vol->vol_name, vol->dev->VolHdr.VolumeName) != 0)) {
182 vol_list->remove(vol);
192 * List Volumes -- this should be moved to status.c
194 void list_volumes(BSOCK *user)
197 for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
198 bnet_fsend(user, "%s\n", vol->vol_name);
202 /* Create the Volume list */
203 void create_volume_list()
205 VOLRES *dummy = NULL;
206 if (vol_list == NULL) {
207 vol_list = New(dlist(dummy, &dummy->link));
211 /* Release all Volumes from the list */
212 void free_volume_list()
218 for (vol=(VOLRES *)vol_list->first(); vol; vol=(VOLRES *)vol_list->next(vol)) {
219 Dmsg3(000, "Unreleased Volume=%s dcr=0x%x dev=0x%x\n", vol->vol_name,
226 bool is_volume_in_use(DCR *dcr)
228 VOLRES *vol = find_volume(dcr->VolumeName);
230 return false; /* vol not in list */
232 if (!vol->dev) { /* vol not attached to device */
235 if (dcr->dev == vol->dev) { /* same device OK */
238 if (!vol->dev->is_busy()) {
245 static bool use_storage_cmd(JCR *jcr)
247 POOL_MEM store_name, dev_name, media_type, pool_name, pool_type;
248 BSOCK *dir = jcr->dir_bsock;
258 memset(&rctx, 0, sizeof(RCTX));
261 * If there are multiple devices, the director sends us
262 * use_device for each device that it wants to use.
264 Dmsg1(100, "<dird: %s", dir->msg);
265 jcr->dirstore = New(alist(10, not_owned_by_alist));
267 ok = sscanf(dir->msg, use_storage, store_name.c_str(),
268 media_type.c_str(), pool_name.c_str(),
269 pool_type.c_str(), &append, &Copy, &Stripe) == 7;
273 unbash_spaces(store_name);
274 unbash_spaces(media_type);
275 unbash_spaces(pool_name);
276 unbash_spaces(pool_type);
277 store = new DIRSTORE;
278 jcr->dirstore->append(store);
279 memset(store, 0, sizeof(DIRSTORE));
280 store->device = New(alist(10));
281 bstrncpy(store->name, store_name, sizeof(store->name));
282 bstrncpy(store->media_type, media_type, sizeof(store->media_type));
283 bstrncpy(store->pool_name, pool_name, sizeof(store->pool_name));
284 bstrncpy(store->pool_type, pool_type, sizeof(store->pool_type));
285 store->append = append;
287 /* Now get all devices */
288 while (bnet_recv(dir) >= 0) {
289 ok = sscanf(dir->msg, use_device, dev_name.c_str()) == 1;
293 unbash_spaces(dev_name);
294 store->device->append(bstrdup(dev_name.c_str()));
296 } while (ok && bnet_recv(dir) >= 0);
299 /* This loop is debug code and can be removed */
300 /* ***FIXME**** remove after 1.38 release */
302 foreach_alist(store, jcr->dirstore) {
303 Dmsg4(100, "Storage=%s media_type=%s pool=%s pool_type=%s\n",
304 store->name, store->media_type, store->pool_name,
306 foreach_alist(device_name, store->device) {
307 Dmsg1(100, " Device=%s\n", device_name);
313 * At this point, we have a list of all the Director's Storage
314 * resources indicated for this Job, which include Pool, PoolType,
315 * storage name, and Media type.
316 * Then for each of the Storage resources, we have a list of
317 * device names that were given.
319 * Wiffle through them and find one that can do the backup.
323 * Make up to two passes. The first with PreferMountedVols possibly
324 * set to true. In that case, we look only for an available
325 * drive with something mounted. If that fails, then we
326 * do a second pass with PerferMountedVols set false.
328 rctx.PreferMountedVols = jcr->PreferMountedVols;
329 ok = find_suitable_device_for_job(jcr, rctx);
333 if (rctx.PreferMountedVols) {
334 rctx.PreferMountedVols = false;
335 ok = find_suitable_device_for_job(jcr, rctx);
341 unbash_spaces(dir->msg);
342 pm_strcpy(jcr->errmsg, dir->msg);
343 Jmsg(jcr, M_INFO, 0, _("Failed command: %s\n"), jcr->errmsg);
345 Jmsg(jcr, M_FATAL, 0, _("\n"
346 " Device \"%s\" with MediaType \"%s\" requested by DIR not found in SD Device resources.\n"),
347 dev_name.c_str(), media_type.c_str());
348 bnet_fsend(dir, NO_device, dev_name.c_str());
350 for (error=(char*)rctx->errors.first(); error;
351 error=(char*)rctx->errors.next()) {
352 Jmsg(jcr, M_INFO, 0, "%s", error);
355 Dmsg1(100, ">dird: %s\n", dir->msg);
357 unbash_spaces(dir->msg);
358 pm_strcpy(jcr->errmsg, dir->msg);
360 Jmsg(jcr, M_INFO, 0, _("Failed command: %s\n"), jcr->errmsg);
362 bnet_fsend(dir, BAD_use, jcr->errmsg);
363 Dmsg1(100, ">dird: %s\n", dir->msg);
367 foreach_alist(store, jcr->dirstore) {
368 delete store->device;
371 delete jcr->dirstore;
373 for (error=(char*)rctx->errors.first(); error;
374 error=(char*)rctx->errors.next()) {
383 * Search for a device suitable for this job.
385 bool find_suitable_device_for_job(JCR *jcr, RCTX &rctx)
393 init_jcr_device_wait_timers(jcr);
395 int need_wait = false;
396 foreach_alist(store, jcr->dirstore) {
398 foreach_alist(device_name, store->device) {
400 rctx.device_name = device_name;
401 stat = search_res_for_device(rctx);
402 if (stat == 1) { /* found available device */
406 } else if (stat == 0) { /* device busy */
409 /* otherwise error */
410 // rctx->errors.push(bstrdup(jcr->errmsg));
414 * If there is some device for which we can wait, then
415 * wait and try again until the wait time expires
417 if (!need_wait || !wait_for_device(jcr, first)) {
422 for (error=(char*)rctx->errors.first(); error;
423 error=(char*)rctx->errors.next()) {
436 * Search for a particular storage device with particular storage
437 * characteristics (MediaType).
439 static int search_res_for_device(RCTX &rctx)
441 AUTOCHANGER *changer;
442 BSOCK *dir = rctx.jcr->dir_bsock;
446 Dmsg1(100, "Search res for %s\n", rctx.device_name);
447 foreach_res(rctx.device, R_DEVICE) {
448 Dmsg1(100, "Try res=%s\n", rctx.device->hdr.name);
449 /* Find resource, and make sure we were able to open it */
450 if (fnmatch(rctx.device_name, rctx.device->hdr.name, 0) == 0 &&
451 strcmp(rctx.device->media_type, rctx.store->media_type) == 0) {
452 stat = reserve_device(rctx);
456 Dmsg1(220, "Got: %s", dir->msg);
457 bash_spaces(rctx.device_name);
458 ok = bnet_fsend(dir, OK_device, rctx.device_name);
459 Dmsg1(100, ">dird: %s\n", dir->msg);
463 foreach_res(changer, R_AUTOCHANGER) {
464 Dmsg1(100, "Try changer res=%s\n", changer->hdr.name);
465 /* Find resource, and make sure we were able to open it */
466 if (fnmatch(rctx.device_name, changer->hdr.name, 0) == 0) {
467 /* Try each device in this AutoChanger */
468 foreach_alist(rctx.device, changer->device) {
469 Dmsg1(100, "Try changer device %s\n", rctx.device->hdr.name);
470 stat = reserve_device(rctx);
471 if (stat == -1) { /* hard error */
474 if (stat == 0) { /* must wait, try next one */
478 Dmsg1(100, "Device %s opened.\n", rctx.device_name);
479 pm_strcpy(dev_name, rctx.device->hdr.name);
480 bash_spaces(dev_name);
481 ok = bnet_fsend(dir, OK_device, dev_name.c_str()); /* Return real device name */
482 Dmsg1(100, ">dird: %s\n", dir->msg);
487 return 0; /* nothing found */
491 * Try to reserve a specific device.
493 * Returns: 1 -- OK, have DCR
497 static int reserve_device(RCTX &rctx)
501 const int name_len = MAX_NAME_LENGTH;
502 if (!rctx.device->dev) {
503 rctx.device->dev = init_dev(rctx.jcr, rctx.device);
505 if (!rctx.device->dev) {
506 if (rctx.device->changer_res) {
507 Jmsg(rctx.jcr, M_WARNING, 0, _("\n"
508 " Device \"%s\" in changer \"%s\" requested by DIR could not be opened or does not exist.\n"),
509 rctx.device->hdr.name, rctx.device_name);
511 Jmsg(rctx.jcr, M_WARNING, 0, _("\n"
512 " Device \"%s\" requested by DIR could not be opened or does not exist.\n"),
515 return -1; /* no use waiting */
517 Dmsg1(100, "Found device %s\n", rctx.device->hdr.name);
518 dcr = new_dcr(rctx.jcr, rctx.device->dev);
520 BSOCK *dir = rctx.jcr->dir_bsock;
521 bnet_fsend(dir, _("3926 Could not get dcr for device: %s\n"), rctx.device_name);
522 Dmsg1(100, ">dird: %s\n", dir->msg);
526 bstrncpy(dcr->pool_name, rctx.store->pool_name, name_len);
527 bstrncpy(dcr->pool_type, rctx.store->pool_type, name_len);
528 bstrncpy(dcr->media_type, rctx.store->media_type, name_len);
529 bstrncpy(dcr->dev_name, rctx.device_name, name_len);
530 if (rctx.store->append == SD_APPEND) {
531 ok = reserve_device_for_append(dcr, rctx.PreferMountedVols);
532 Dmsg3(200, "dev_name=%s mediatype=%s ok=%d\n", dcr->dev_name, dcr->media_type, ok);
534 ok = reserve_device_for_read(dcr);
537 free_dcr(rctx.jcr->dcr);
544 * We "reserve" the drive by setting the ST_READ bit. No one else
545 * should touch the drive until that is cleared.
546 * This allows the DIR to "reserve" the device before actually
549 static bool reserve_device_for_read(DCR *dcr)
551 DEVICE *dev = dcr->dev;
557 dev->block(BST_DOING_ACQUIRE);
559 if (is_device_unmounted(dev)) {
560 Dmsg1(200, "Device %s is BLOCKED due to user unmount.\n", dev->print_name());
561 Mmsg(jcr->errmsg, _("Device %s is BLOCKED due to user unmount.\n"),
566 if (dev->is_busy()) {
567 Dmsg4(200, "Device %s is busy ST_READ=%d num_writers=%d reserved=%d.\n", dev->print_name(),
568 dev->state & ST_READ?1:0, dev->num_writers, dev->reserved_device);
569 Mmsg1(jcr->errmsg, _("Device %s is busy.\n"),
585 * We reserve the device for appending by incrementing the
586 * reserved_device. We do virtually all the same work that
587 * is done in acquire_device_for_append(), but we do
588 * not attempt to mount the device. This routine allows
589 * the DIR to reserve multiple devices before *really*
590 * starting the job. It also permits the SD to refuse
591 * certain devices (not up, ...).
593 * Note, in reserving a device, if the device is for the
594 * same pool and the same pool type, then it is acceptable.
595 * The Media Type has already been checked. If we are
596 * the first tor reserve the device, we put the pool
597 * name and pool type in the device record.
599 static bool reserve_device_for_append(DCR *dcr, bool PreferMountedVols)
602 DEVICE *dev = dcr->dev;
607 dev->block(BST_DOING_ACQUIRE);
609 if (dev->can_read()) {
610 Mmsg1(jcr->errmsg, _("Device %s is busy reading.\n"), dev->print_name());
611 Dmsg1(100, "%s", jcr->errmsg);
615 if (is_device_unmounted(dev)) {
616 Mmsg(jcr->errmsg, _("Device %s is BLOCKED due to user unmount.\n"), dev->print_name());
617 Dmsg1(100, "%s", jcr->errmsg);
621 Dmsg1(190, "reserve_append device is %s\n", dev->is_tape()?"tape":"disk");
623 if (can_reserve_drive(dcr, PreferMountedVols) != 1) {
624 Mmsg1(jcr->errmsg, _("Device %s is busy writing on another Volume.\n"), dev->print_name());
625 Dmsg1(100, "%s", jcr->errmsg);
629 dev->reserved_device++;
630 Dmsg1(200, "Inc reserve=%d\n", dev->reserved_device);
631 dcr->reserved_device = true;
640 * Returns: 1 if drive can be reserved
641 * 0 if we should wait
644 static int can_reserve_drive(DCR *dcr, bool PreferMountedVols)
646 DEVICE *dev = dcr->dev;
649 if (PreferMountedVols && !dev->VolHdr.VolumeName[0] &&
650 dev->is_tape() && !dev->is_autochanger()) {
651 return 0; /* No volume mounted */
655 * Handle the case that the drive is not yet in append mode
657 if (!dev->can_append() && dev->num_writers == 0) {
658 /* Now check if there are any reservations on the drive */
659 if (dev->reserved_device) {
660 /* Yes, now check if we want the same Pool and pool type */
661 if (strcmp(dev->pool_name, dcr->pool_name) == 0 &&
662 strcmp(dev->pool_type, dcr->pool_type) == 0) {
663 /* OK, compatible device */
665 /* Drive not suitable for us */
669 /* Device is available but not yet reserved, reserve it for us */
670 bstrncpy(dev->pool_name, dcr->pool_name, sizeof(dev->pool_name));
671 bstrncpy(dev->pool_type, dcr->pool_type, sizeof(dev->pool_type));
673 return 1; /* reserve drive */
677 * Check if device in append mode with no writers (i.e. available)
679 if (dev->can_append() && dev->num_writers == 0) {
680 /* Device is available but not yet reserved, reserve it for us */
681 bstrncpy(dev->pool_name, dcr->pool_name, sizeof(dev->pool_name));
682 bstrncpy(dev->pool_type, dcr->pool_type, sizeof(dev->pool_type));
686 * Check if the device is in append mode with writers (i.e.
687 * available if pool is the same).
689 if (dev->can_append() || dev->num_writers > 0) {
690 Dmsg0(190, "device already in append.\n");
691 /* Yes, now check if we want the same Pool and pool type */
692 if (strcmp(dev->pool_name, dcr->pool_name) == 0 &&
693 strcmp(dev->pool_type, dcr->pool_type) == 0) {
694 /* OK, compatible device */
697 /* Drive not suitable for us */
698 Jmsg(jcr, M_WARNING, 0, _("Wanted Pool \"%s\", but device %s is using Pool \"%s\" .\n"),
699 dcr->pool_name, dev->print_name(), dev->pool_name);
703 Pmsg0(000, "Logic error!!!! Should not get here.\n");
704 Jmsg0(jcr, M_FATAL, 0, _("Logic error!!!! Should not get here.\n"));
705 return -1; /* error, should not get here */