1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2013 Free Software Foundation Europe e.V.
5    Copyright (C) 2015-2021 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation and included
10    in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    Affero General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /*
23  * Kern Sibbald, MM
24  * Split from reserve.c October 2008
25  */
26 /**
27  * @file
28  * Volume management functions for Storage Daemon
29  */
30 
31 #include "include/bareos.h"
32 #include "stored/stored.h"
33 #include "stored/stored_globals.h"
34 #include "stored/device_control_record.h"
35 #include "stored/autochanger.h"
36 #include "include/jcr.h"
37 #include "lib/berrno.h"
38 
39 namespace storagedaemon {
40 
41 const int debuglevel = 150;
42 
43 static brwlock_t vol_list_lock;
44 static dlist* vol_list = NULL;
45 static dlist* read_vol_list = NULL;
46 static pthread_mutex_t read_vol_lock = PTHREAD_MUTEX_INITIALIZER;
47 
48 /* Global static variables */
49 static int vol_list_lock_count = 0;
50 static int read_vol_list_lock_count = 0;
51 
52 /* Forward referenced functions */
53 static void FreeVolItem(VolumeReservationItem* vol);
54 static void FreeReadVolItem(VolumeReservationItem* vol);
55 static VolumeReservationItem* new_vol_item(DeviceControlRecord* dcr,
56                                            const char* VolumeName);
57 static void DebugListVolumes(const char* imsg);
58 
59 /**
60  * For append volumes the key is the VolumeName.
61  */
CompareByVolumename(void * item1,void * item2)62 static int CompareByVolumename(void* item1, void* item2)
63 {
64   VolumeReservationItem* vol1 = (VolumeReservationItem*)item1;
65   VolumeReservationItem* vol2 = (VolumeReservationItem*)item2;
66 
67   ASSERT(vol1->vol_name);
68   ASSERT(vol2->vol_name);
69 
70   return strcmp(vol1->vol_name, vol2->vol_name);
71 }
72 
73 /**
74  * For read volumes the key is JobId, VolumeName.
75  */
ReadCompare(void * item1,void * item2)76 static int ReadCompare(void* item1, void* item2)
77 {
78   VolumeReservationItem* vol1 = (VolumeReservationItem*)item1;
79   VolumeReservationItem* vol2 = (VolumeReservationItem*)item2;
80 
81   ASSERT(vol1->vol_name);
82   ASSERT(vol2->vol_name);
83 
84   if (vol1->GetJobid() == vol2->GetJobid()) {
85     return strcmp(vol1->vol_name, vol2->vol_name);
86   }
87 
88   if (vol1->GetJobid() < vol2->GetJobid()) { return -1; }
89 
90   return 1;
91 }
92 
IsVolListEmpty()93 bool IsVolListEmpty() { return vol_list->empty(); }
94 
95 /**
96  *  Initialized the main volume list. Note, we are using a recursive lock.
97  */
InitVolListLock()98 void InitVolListLock()
99 {
100   int errstat;
101 
102   if ((errstat = RwlInit(&vol_list_lock, PRIO_SD_VOL_LIST)) != 0) {
103     BErrNo be;
104     Emsg1(M_ABORT, 0, _("Unable to initialize volume list lock. ERR=%s\n"),
105           be.bstrerror(errstat));
106   }
107 }
108 
TermVolListLock()109 void TermVolListLock() { RwlDestroy(&vol_list_lock); }
110 
111 /**
112  * This allows a given thread to recursively call to LockVolumes()
113  */
_lockVolumes(const char * file,int line)114 void _lockVolumes(const char* file, int line)
115 {
116   int errstat;
117 
118   vol_list_lock_count++;
119   if ((errstat = RwlWritelock_p(&vol_list_lock, file, line)) != 0) {
120     BErrNo be;
121     Emsg2(M_ABORT, 0, "RwlWritelock failure. stat=%d: ERR=%s\n", errstat,
122           be.bstrerror(errstat));
123   }
124 }
125 
_unLockVolumes()126 void _unLockVolumes()
127 {
128   int errstat;
129 
130   vol_list_lock_count--;
131   if ((errstat = RwlWriteunlock(&vol_list_lock)) != 0) {
132     BErrNo be;
133     Emsg2(M_ABORT, 0, "RwlWriteunlock failure. stat=%d: ERR=%s\n", errstat,
134           be.bstrerror(errstat));
135   }
136 }
137 
_lockReadVolumes(const char * file,int line)138 void _lockReadVolumes(const char* file, int line)
139 {
140   read_vol_list_lock_count++;
141   pthread_mutex_lock(&read_vol_lock);
142 }
143 
_unLockReadVolumes()144 void _unLockReadVolumes()
145 {
146   read_vol_list_lock_count--;
147   pthread_mutex_unlock(&read_vol_lock);
148 }
149 
150 /**
151  * Add a volume to the read list.
152  *
153  * Note, we use VolumeReservationItem because it simplifies the code
154  * even though, the only part of VolumeReservationItem that we need is
155  * the volume name.  The same volume may be in the list
156  * multiple times, but each one is distinguished by the
157  * JobId.  We use JobId, VolumeName as the key.
158  *
159  * We can get called multiple times for the same volume because
160  * when parsing the bsr, the volume name appears multiple times.
161  */
AddReadVolume(JobControlRecord * jcr,const char * VolumeName)162 void AddReadVolume(JobControlRecord* jcr, const char* VolumeName)
163 {
164   VolumeReservationItem *nvol, *vol;
165 
166   nvol = new_vol_item(NULL, VolumeName);
167   nvol->SetJobid(jcr->JobId);
168   nvol->SetReading();
169   LockReadVolumes();
170   vol = (VolumeReservationItem*)read_vol_list->binary_insert(nvol, ReadCompare);
171   if (vol != nvol) {
172     FreeReadVolItem(nvol);
173     Dmsg2(debuglevel, "read_vol=%s JobId=%d already in list.\n", VolumeName,
174           jcr->JobId);
175   } else {
176     Dmsg2(debuglevel, "add_read_vol=%s JobId=%d\n", VolumeName, jcr->JobId);
177   }
178   UnlockReadVolumes();
179 }
180 
181 /**
182  * Remove a given volume name from the read list.
183  */
RemoveReadVolume(JobControlRecord * jcr,const char * VolumeName)184 void RemoveReadVolume(JobControlRecord* jcr, const char* VolumeName)
185 {
186   VolumeReservationItem vol, *fvol;
187 
188   LockReadVolumes();
189   vol.vol_name = strdup(VolumeName);
190   vol.SetJobid(jcr->JobId);
191 
192   fvol
193       = (VolumeReservationItem*)read_vol_list->binary_search(&vol, ReadCompare);
194   free(vol.vol_name);
195 
196   if (fvol) {
197     Dmsg3(debuglevel, "remove_read_vol=%s JobId=%d found=%d\n", VolumeName,
198           jcr->JobId, fvol != NULL);
199   }
200   if (fvol) {
201     read_vol_list->remove(fvol);
202     FreeReadVolItem(fvol);
203   }
204   UnlockReadVolumes();
205   // pthread_cond_broadcast(&wait_next_vol);
206 }
207 
208 /**
209  * Search for a Volume name in the read Volume list.
210  *
211  * Returns: VolumeReservationItem entry on success
212  *          NULL if the Volume is not in the list
213  */
find_read_volume(const char * VolumeName)214 static VolumeReservationItem* find_read_volume(const char* VolumeName)
215 {
216   VolumeReservationItem vol, *fvol;
217 
218   if (read_vol_list->empty()) {
219     Dmsg0(debuglevel, "find_read_vol: read_vol_list empty.\n");
220     return NULL;
221   }
222 
223   /*
224    * Do not lock reservations here
225    */
226   LockReadVolumes();
227   vol.vol_name = strdup(VolumeName);
228 
229   /*
230    * Note, we do want a simple CompareByVolumename on volume name only here
231    */
232   fvol = (VolumeReservationItem*)read_vol_list->binary_search(
233       &vol, CompareByVolumename);
234   free(vol.vol_name);
235 
236   Dmsg2(debuglevel, "find_read_vol=%s found=%d\n", VolumeName, fvol != NULL);
237   UnlockReadVolumes();
238 
239   return fvol;
240 }
241 
242 /**
243  * List Volumes -- this should be moved to status.c
244  */
245 enum
246 {
247   debug_lock = true,
248   debug_nolock = false
249 };
250 
DebugListVolumes(const char * imsg)251 static void DebugListVolumes(const char* imsg)
252 {
253   VolumeReservationItem* vol;
254   PoolMem msg(PM_MESSAGE);
255 
256   foreach_vol (vol) {
257     if (vol->dev) {
258       Mmsg(msg, "List %s: %s in_use=%d swap=%d on device %s\n", imsg,
259            vol->vol_name, vol->IsInUse(), vol->IsSwapping(),
260            vol->dev->print_name());
261     } else {
262       Mmsg(msg, "List %s: %s in_use=%d swap=%d no dev\n", imsg, vol->vol_name,
263            vol->IsInUse(), vol->IsSwapping());
264     }
265     Dmsg1(debuglevel, "%s", msg.c_str());
266   }
267   endeach_vol(vol);
268 }
269 
270 /**
271  * Create a Volume item to put in the Volume list
272  * Ensure that the device points to it.
273  */
new_vol_item(DeviceControlRecord * dcr,const char * VolumeName)274 static VolumeReservationItem* new_vol_item(DeviceControlRecord* dcr,
275                                            const char* VolumeName)
276 {
277   VolumeReservationItem* vol;
278   VolumeReservationItem emptyVol;
279 
280   vol = (VolumeReservationItem*)malloc(sizeof(VolumeReservationItem));
281   *vol = emptyVol;
282   vol->vol_name = strdup(VolumeName);
283   if (dcr) {
284     vol->dev = dcr->dev;
285     Dmsg3(debuglevel, "new Vol=%s at %p dev=%s\n", VolumeName, vol->vol_name,
286           vol->dev->print_name());
287   }
288   vol->InitMutex();
289   vol->IncUseCount();
290 
291   return vol;
292 }
293 
FreeVolItem(VolumeReservationItem * vol)294 static void FreeVolItem(VolumeReservationItem* vol)
295 {
296   Device* dev = NULL;
297 
298   vol->DecUseCount();
299   vol->Lock();
300   if (vol->UseCount() > 0) {
301     vol->Unlock();
302     return;
303   }
304   vol->Unlock();
305   free(vol->vol_name);
306   if (vol->dev) { dev = vol->dev; }
307   vol->DestroyMutex();
308   free(vol);
309   if (dev) { dev->vol = NULL; }
310 }
311 
FreeReadVolItem(VolumeReservationItem * vol)312 static void FreeReadVolItem(VolumeReservationItem* vol)
313 {
314   Device* dev = NULL;
315 
316   vol->DecUseCount();
317   vol->Lock();
318   if (vol->UseCount() > 0) {
319     vol->Unlock();
320     return;
321   }
322   vol->Unlock();
323   free(vol->vol_name);
324   if (vol->dev) { dev = vol->dev; }
325   vol->DestroyMutex();
326   free(vol);
327   if (dev) { dev->vol = NULL; }
328 }
329 
330 /**
331  * Put a new Volume entry in the Volume list. This
332  * effectively reserves the volume so that it will
333  * not be mounted again.
334  *
335  * If the device has any current volume associated with it,
336  * and it is a different Volume, and the device is not busy,
337  * we release the old Volume item and insert the new one.
338  *
339  * It is assumed that the device is free and locked so that
340  * we can change the device structure.
341  *
342  * Some details of the Volume list handling:
343  *
344  *  1. The Volume list entry is attached to the drive (rather than
345  *     attached to a job as it was previously. I.e. the drive that "owns"
346  *     the volume (in use, mounted)
347  *     must point to the volume (still to be maintained in a list).
348  *
349  *  2. The Volume is entered in the list when a drive is reserved.
350  *
351  *  3. When a drive is in use, the device code must appropriately update the
352  *     volume name as it changes.
353  *
354  *     This code keeps the same list entry as long as the drive
355  *     has any volume associated with it but the volume name in the list
356  *     must be updated when the drive has a different volume mounted.
357  *
358  *  4. A job that has reserved a volume, can un-reserve the volume, and if the
359  *     volume is not mounted, and not reserved, and not in use, it will be
360  *     removed from the list.
361  *
362  *  5. If a job wants to reserve a drive with a different Volume from the one on
363  *     the drive, it can re-use the drive for the new Volume.
364  *
365  *  6. If a job wants a Volume that is in a different drive, it can either use
366  * the other drive or take the volume, only if the other drive is not in use or
367  *     not reserved.
368  *
369  *  One nice aspect of this is that the reserve use count and the writer use
370  * count already exist and are correctly programmed and will need no changes --
371  * use counts are always very tricky.
372  *
373  *  The old code had a concept of "reserving" a Volume, but was changed
374  *  to reserving and using a drive.  A volume is must be attached to (owned by)
375  * a drive and can move from drive to drive or be unused given certain specific
376  *  conditions of the drive.  The key is that the drive must "own" the Volume.
377  *
378  *  Return: VolumeReservationItem entry on success
379  *          NULL volume busy on another drive
380  */
reserve_volume(DeviceControlRecord * dcr,const char * VolumeName)381 VolumeReservationItem* reserve_volume(DeviceControlRecord* dcr,
382                                       const char* VolumeName)
383 {
384   VolumeReservationItem *vol, *nvol;
385   Device* volatile dev = dcr->dev;
386 
387   if (JobCanceled(dcr->jcr)) { return NULL; }
388   ASSERT(dev != NULL);
389 
390   Dmsg2(debuglevel, "enter reserve_volume=%s drive=%s\n", VolumeName,
391         dcr->dev->print_name());
392 
393   /*
394    * If aquiring a volume for writing it may not be on the read volume list.
395    */
396   if (me->filedevice_concurrent_read && dcr->IsWriting()
397       && find_read_volume(VolumeName)) {
398     Mmsg(dcr->jcr->errmsg,
399          _("Could not reserve volume \"%s\" for append, because it is read by "
400            "another Job.\n"),
401          dev->VolHdr.VolumeName);
402     return NULL;
403   }
404 
405   /*
406    * We lock the reservations system here to ensure when adding a new volume
407    * that no newly scheduled job can reserve it.
408    */
409   LockVolumes();
410   if (debug_level >= debuglevel) { DebugListVolumes("begin reserve_volume"); }
411 
412   /*
413    * First, remove any old volume attached to this device as it is no longer
414    * used.
415    */
416   if (dev->vol) {
417     vol = dev->vol;
418     Dmsg4(debuglevel, "Vol attached=%s, newvol=%s volinuse=%d on %s\n",
419           vol->vol_name, VolumeName, vol->IsInUse(), dev->print_name());
420     /*
421      * Make sure we don't remove the current volume we are inserting
422      * because it was probably inserted by another job, or it
423      * is not being used and is marked as not reserved.
424      */
425     if (bstrcmp(vol->vol_name, VolumeName)) {
426       Dmsg2(debuglevel, "=== set reserved vol=%s dev=%s\n", VolumeName,
427             vol->dev->print_name());
428       goto get_out; /* Volume already on this device */
429     } else {
430       /*
431        * Don't release a volume if it was reserved by someone other than us
432        */
433       if (vol->IsInUse() && !dcr->reserved_volume) {
434         Dmsg1(debuglevel, "Cannot free vol=%s. It is reserved.\n",
435               vol->vol_name);
436         vol = NULL; /* vol in use */
437         goto get_out;
438       }
439       Dmsg2(debuglevel, "reserve_vol free vol=%s at %p\n", vol->vol_name,
440             vol->vol_name);
441 
442       /*
443        * If old Volume is still mounted, must unload it
444        */
445       if (bstrcmp(vol->vol_name, dev->VolHdr.VolumeName)) {
446         Dmsg0(50, "SetUnload\n");
447         dev->SetUnload(); /* have to unload current volume */
448       }
449       FreeVolume(dev); /* Release old volume entry */
450 
451       if (debug_level >= debuglevel) { DebugListVolumes("reserve_vol free"); }
452     }
453   }
454 
455   /*
456    * Create a new Volume entry
457    */
458   nvol = new_vol_item(dcr, VolumeName);
459 
460   /*
461    * See if this is a request for reading a file type device which can be
462    * accesses by multiple readers at once without disturbing each other.
463    */
464   if (me->filedevice_concurrent_read && !dcr->IsWriting() && dev->IsFile()) {
465     nvol->SetJobid(dcr->jcr->JobId);
466     nvol->SetReading();
467     vol = nvol;
468     dev->vol = vol;
469 
470     /*
471      * Read volumes on file based devices are not inserted into the write volume
472      * list.
473      */
474     goto get_out;
475   } else {
476     /*
477      * Now try to insert the new Volume
478      */
479     vol = (VolumeReservationItem*)vol_list->binary_insert(nvol,
480                                                           CompareByVolumename);
481   }
482 
483   if (vol != nvol) {
484     Dmsg2(debuglevel, "Found vol=%s dev-same=%d\n", vol->vol_name,
485           dev == vol->dev);
486 
487     /*
488      * At this point, a Volume with this name already is in the list,
489      * so we simply release our new Volume entry. Note, this should
490      * only happen if we are moving the volume from one drive to another.
491      */
492     Dmsg2(debuglevel, "reserve_vol free-tmp vol=%s at %p\n", vol->vol_name,
493           vol->vol_name);
494 
495     /*
496      * Clear dev pointer so that FreeVolItem() doesn't take away our volume.
497      */
498     nvol->dev = NULL; /* don't zap dev entry */
499     FreeVolItem(nvol);
500 
501     if (vol->dev) {
502       Dmsg2(debuglevel, "dev=%s vol->dev=%s\n", dev->print_name(),
503             vol->dev->print_name());
504     }
505 
506     /*
507      * Check if we are trying to use the Volume on a different drive dev is our
508      * device vol->dev is where the Volume we want is
509      */
510     if (dev != vol->dev) {
511       /*
512        * Caller wants to switch Volume to another device
513        */
514 
515       // should be different devices with different names
516       if (bstrcmp(dev->print_name(), vol->dev->print_name())) {
517         // names are same
518         Dmsg1(100, "device pointers are different but have same name %s\n",
519               dev->print_name());
520       }
521 
522       if (!vol->dev->IsBusy() && !vol->IsSwapping()) {
523         slot_number_t slot;
524 
525         Dmsg3(debuglevel, "==== Swap vol=%s from dev=%s to %s\n", VolumeName,
526               vol->dev->print_name(), dev->print_name());
527         FreeVolume(dev); /* free any volume attached to our drive */
528         Dmsg1(50, "SetUnload dev=%s\n", dev->print_name());
529         dev->SetUnload();      /* Unload any volume that is on our drive */
530         dcr->SetDev(vol->dev); /* temp point to other dev */
531         slot = GetAutochangerLoadedSlot(dcr); /* get slot on other drive */
532         dcr->SetDev(dev);                     /* restore dev */
533         vol->SetSlotNumber(slot);             /* save slot */
534         vol->dev->SetUnload();                /* unload the other drive */
535         vol->SetSwapping();                   /* swap from other drive */
536         dev->swap_dev = vol->dev;             /* remember to get this vol */
537         dev->SetLoad();                       /* then reload on our drive */
538         vol->dev->vol = NULL; /* remove volume from other drive */
539         vol->dev = dev;       /* point the Volume at our drive */
540         dev->vol = vol;       /* point our drive at the Volume */
541       } else {
542         Jmsg7(dcr->jcr, M_WARNING, 0,
543               "Need volume from other drive, but swap not possible. "
544               "Status: read=%d num_writers=%d num_reserve=%d swap=%d "
545               "vol=%s from dev=%s to %s\n",
546               vol->dev->CanRead(), vol->dev->num_writers,
547               vol->dev->NumReserved(), vol->IsSwapping(), VolumeName,
548               vol->dev->print_name(), dev->print_name());
549         if (vol->IsSwapping() && dev->swap_dev) {
550           Dmsg3(debuglevel, "Swap failed vol=%s from=%s to dev=%s\n",
551                 vol->vol_name, dev->swap_dev->print_name(), dev->print_name());
552         } else {
553           Dmsg3(debuglevel, "Swap failed vol=%s from=%p to dev=%s\n",
554                 vol->vol_name, dev->swap_dev, dev->print_name());
555         }
556 
557         if (debug_level >= debuglevel) { DebugListVolumes("failed swap"); }
558 
559         vol = NULL; /* device busy */
560         goto get_out;
561       }
562     } else {
563       dev->vol = vol;
564     }
565   } else {
566     dev->vol = vol; /* point to newly inserted volume */
567   }
568 
569 get_out:
570   if (vol) {
571     Dmsg2(debuglevel, "=== set in_use. vol=%s dev=%s\n", vol->vol_name,
572           vol->dev->print_name());
573     vol->SetInUse();
574     dcr->reserved_volume = true;
575     bstrncpy(dcr->VolumeName, vol->vol_name, sizeof(dcr->VolumeName));
576   }
577 
578   if (debug_level >= debuglevel) { DebugListVolumes("end new volume"); }
579 
580   UnlockVolumes();
581   return vol;
582 }
583 
584 /**
585  * Start walk of vol chain
586  * The proper way to walk the vol chain is:
587  *
588  * VolumeReservationItem *vol;
589  * foreach_vol(vol) {
590  *    ...
591  * }
592  * endeach_vol(vol);
593  *
594  * It is possible to leave out the endeach_vol(vol), but in that case,
595  * the last vol referenced must be explicitly released with:
596  *
597  * FreeVolItem(vol);
598  */
vol_walk_start()599 VolumeReservationItem* vol_walk_start()
600 {
601   VolumeReservationItem* vol;
602   LockVolumes();
603   vol = (VolumeReservationItem*)vol_list->first();
604   if (vol) {
605     vol->IncUseCount();
606     Dmsg2(debuglevel, "Inc walk_start UseCount=%d volname=%s\n",
607           vol->UseCount(), vol->vol_name);
608   }
609   UnlockVolumes();
610 
611   return vol;
612 }
613 
614 /**
615  * Get next vol from chain, and release current one
616  */
VolWalkNext(VolumeReservationItem * prev_vol)617 VolumeReservationItem* VolWalkNext(VolumeReservationItem* prev_vol)
618 {
619   VolumeReservationItem* vol;
620 
621   LockVolumes();
622   vol = (VolumeReservationItem*)vol_list->next(prev_vol);
623   if (vol) {
624     vol->IncUseCount();
625     Dmsg2(debuglevel, "Inc walk_next UseCount=%d volname=%s\n", vol->UseCount(),
626           vol->vol_name);
627   }
628   if (prev_vol) { FreeVolItem(prev_vol); }
629   UnlockVolumes();
630 
631   return vol;
632 }
633 
634 /**
635  * Release last vol referenced
636  */
VolWalkEnd(VolumeReservationItem * vol)637 void VolWalkEnd(VolumeReservationItem* vol)
638 {
639   if (vol) {
640     LockVolumes();
641     Dmsg2(debuglevel, "Free walk_end UseCount=%d volname=%s\n", vol->UseCount(),
642           vol->vol_name);
643     FreeVolItem(vol);
644     UnlockVolumes();
645   }
646 }
647 
648 /*
649  * Start walk of vol chain
650  * The proper way to walk the vol chain is:
651  *
652  * VolumeReservationItem *vol;
653  * foreach_read_vol(vol) {
654  *    ...
655  * }
656  * endeach_read_vol(vol);
657  *
658  * It is possible to leave out the endeach_read_vol(vol), but in that case,
659  * the last vol referenced must be explicitly released with:
660  *
661  * FreeReadVolItem(vol);
662  */
read_vol_walk_start()663 VolumeReservationItem* read_vol_walk_start()
664 {
665   VolumeReservationItem* vol;
666   LockReadVolumes();
667   vol = (VolumeReservationItem*)read_vol_list->first();
668   if (vol) {
669     vol->IncUseCount();
670     Dmsg2(debuglevel, "Inc walk_start UseCount=%d volname=%s\n",
671           vol->UseCount(), vol->vol_name);
672   }
673   UnlockReadVolumes();
674 
675   return vol;
676 }
677 
678 /*
679  * Get next vol from chain, and release current one
680  */
ReadVolWalkNext(VolumeReservationItem * prev_vol)681 VolumeReservationItem* ReadVolWalkNext(VolumeReservationItem* prev_vol)
682 {
683   VolumeReservationItem* vol;
684 
685   LockReadVolumes();
686   vol = (VolumeReservationItem*)read_vol_list->next(prev_vol);
687   if (vol) {
688     vol->IncUseCount();
689     Dmsg2(debuglevel, "Inc walk_next UseCount=%d volname=%s\n", vol->UseCount(),
690           vol->vol_name);
691   }
692   if (prev_vol) { FreeReadVolItem(prev_vol); }
693   UnlockReadVolumes();
694 
695   return vol;
696 }
697 
698 /*
699  * Release last vol referenced
700  */
ReadVolWalkEnd(VolumeReservationItem * vol)701 void ReadVolWalkEnd(VolumeReservationItem* vol)
702 {
703   if (vol) {
704     LockReadVolumes();
705     Dmsg2(debuglevel, "Free walk_end UseCount=%d volname=%s\n", vol->UseCount(),
706           vol->vol_name);
707     FreeReadVolItem(vol);
708     UnlockReadVolumes();
709   }
710 }
711 
712 /**
713  * Search for a Volume name in the Volume list.
714  *
715  * Returns: VolumeReservationItem entry on success
716  *          NULL if the Volume is not in the list
717  */
find_volume(const char * VolumeName)718 static VolumeReservationItem* find_volume(const char* VolumeName)
719 {
720   VolumeReservationItem vol, *fvol;
721 
722   if (vol_list->empty()) { return NULL; }
723   /* Do not lock reservations here */
724   LockVolumes();
725   vol.vol_name = strdup(VolumeName);
726   fvol = (VolumeReservationItem*)vol_list->binary_search(&vol,
727                                                          CompareByVolumename);
728   free(vol.vol_name);
729   Dmsg2(debuglevel, "find_vol=%s found=%d\n", VolumeName, fvol != NULL);
730 
731   if (debug_level >= debuglevel) { DebugListVolumes("find_volume"); }
732 
733   UnlockVolumes();
734   return fvol;
735 }
736 
737 /**
738  * Free a Volume from the Volume list if it is no longer used
739  * Note, for tape drives we want to remember where the Volume
740  * was when last used, so rather than free the volume entry,
741  * we simply mark it "not reserved" so when the drive is really
742  * needed for another volume, we can reuse it.
743  *
744  * Returns: true if the Volume found and "removed" from the list
745  *          false if the Volume is not in the list or is in use
746  */
VolumeUnused(DeviceControlRecord * dcr)747 bool VolumeUnused(DeviceControlRecord* dcr)
748 {
749   Device* dev = dcr->dev;
750 
751   if (!dev->vol) {
752     Dmsg1(debuglevel, "vol_unused: no vol on %s\n", dev->print_name());
753     if (debug_level >= debuglevel) {
754       DebugListVolumes("null vol cannot unreserve_volume");
755     }
756 
757     return false;
758   }
759 
760   Dmsg1(debuglevel, "=== clear in_use vol=%s\n", dev->vol->vol_name);
761   dev->vol->ClearInUse();
762 
763   if (dev->vol->IsSwapping()) {
764     Dmsg1(debuglevel, "vol_unused: vol being swapped on %s\n",
765           dev->print_name());
766 
767     if (debug_level >= debuglevel) {
768       DebugListVolumes("swapping vol cannot FreeVolume");
769     }
770     return false;
771   }
772 
773   /*
774    * If this is a tape, we do not free the volume, rather we wait
775    * until the autoloader unloads it, or until another tape is
776    * explicitly read in this drive. This allows the SD to remember
777    * where the tapes are or last were.
778    */
779   Dmsg4(debuglevel,
780         "=== set not reserved vol=%s num_writers=%d dev_reserved=%d dev=%s\n",
781         dev->vol->vol_name, dev->num_writers, dev->NumReserved(),
782         dev->print_name());
783   if (dev->IsTape() || dev->AttachedToAutochanger()) {
784     return true;
785   } else {
786     /*
787      * Note, this frees the volume reservation entry, but the file descriptor
788      * remains open with the OS.
789      */
790     return FreeVolume(dev);
791   }
792 }
793 
794 /**
795  * Unconditionally release the volume entry
796  */
FreeVolume(Device * dev)797 bool FreeVolume(Device* dev)
798 {
799   VolumeReservationItem* vol;
800 
801   LockVolumes();
802   vol = dev->vol;
803   if (vol == NULL) {
804     Dmsg1(debuglevel, "No vol on dev %s\n", dev->print_name());
805     UnlockVolumes();
806     return false;
807   }
808 
809   /*
810    * Don't free a volume while it is being swapped
811    */
812   if (!vol->IsSwapping()) {
813     Dmsg1(debuglevel, "=== clear in_use vol=%s\n", vol->vol_name);
814     dev->vol = NULL;
815 
816     /*
817      * Volume is on write volume list if one of the folling is applicable:
818      *  - The volume is written to.
819      *  - Config option filedevice_concurrent_read is not on.
820      *  - The device is not of type File.
821      */
822     if (vol->IsWriting() || !me->filedevice_concurrent_read || !dev->IsFile()) {
823       vol_list->remove(vol);
824     }
825     Dmsg2(debuglevel, "=== remove volume %s dev=%s\n", vol->vol_name,
826           dev->print_name());
827     FreeVolItem(vol);
828 
829     if (debug_level >= debuglevel) { DebugListVolumes("FreeVolume"); }
830   } else {
831     Dmsg1(debuglevel, "=== cannot clear swapping vol=%s\n", vol->vol_name);
832   }
833   UnlockVolumes();
834   // pthread_cond_broadcast(&wait_next_vol);
835 
836   return true;
837 }
838 
839 /**
840  * Create the Volume list
841  */
CreateVolumeLists()842 void CreateVolumeLists()
843 {
844   VolumeReservationItem* vol = NULL;
845   if (vol_list == NULL) { vol_list = new dlist(vol, &vol->link); }
846   if (read_vol_list == NULL) { read_vol_list = new dlist(vol, &vol->link); }
847 }
848 
849 /**
850  * Free normal append volumes list
851  */
FreeVolumeList(const char * what,dlist * vollist)852 static inline void FreeVolumeList(const char* what, dlist* vollist)
853 {
854   VolumeReservationItem* vol;
855 
856   foreach_dlist (vol, vollist) {
857     if (vol->dev) {
858       Dmsg3(debuglevel, "free %s Volume=%s dev=%s\n", what, vol->vol_name,
859             vol->dev->print_name());
860     } else {
861       Dmsg2(debuglevel, "free %s Volume=%s No dev\n", what, vol->vol_name);
862     }
863     free(vol->vol_name);
864     vol->vol_name = NULL;
865     vol->DestroyMutex();
866   }
867 }
868 
869 /**
870  * Release all Volumes from the list
871  */
FreeVolumeLists()872 void FreeVolumeLists()
873 {
874   if (vol_list) {
875     LockVolumes();
876     FreeVolumeList("vol_list", vol_list);
877     delete vol_list;
878     vol_list = NULL;
879     UnlockVolumes();
880   }
881 
882   if (read_vol_list) {
883     LockReadVolumes();
884     FreeVolumeList("read_vol_list", read_vol_list);
885     delete read_vol_list;
886     read_vol_list = NULL;
887     UnlockReadVolumes();
888   }
889 }
890 
891 /**
892  * Determine if caller can write on volume
893  */
Can_i_write_volume()894 bool DeviceControlRecord::Can_i_write_volume()
895 {
896   VolumeReservationItem* vol;
897 
898   vol = find_read_volume(VolumeName);
899   if (vol) {
900     Dmsg1(100, "Found in read list; cannot write vol=%s\n", VolumeName);
901     return false;
902   }
903 
904   return Can_i_use_volume();
905 }
906 
907 /**
908  * Determine if caller can read or write volume
909  */
Can_i_use_volume()910 bool DeviceControlRecord::Can_i_use_volume()
911 {
912   bool rtn = true;
913   VolumeReservationItem* vol;
914 
915   if (JobCanceled(jcr)) { return false; }
916   LockVolumes();
917   vol = find_volume(VolumeName);
918   if (!vol) {
919     Dmsg1(debuglevel, "Vol=%s not in use.\n", VolumeName);
920     goto get_out; /* vol not in list */
921   }
922   ASSERT(vol->dev != NULL);
923 
924   if (dev == vol->dev) { /* same device OK */
925     Dmsg1(debuglevel, "Vol=%s on same dev.\n", VolumeName);
926     goto get_out;
927   } else {
928     Dmsg3(debuglevel, "Vol=%s on %s we have %s\n", VolumeName,
929           vol->dev->print_name(), dev->print_name());
930   }
931   /* ***FIXME*** check this ... */
932   if (!vol->dev->IsBusy()) {
933     Dmsg2(debuglevel, "Vol=%s dev=%s not busy.\n", VolumeName,
934           vol->dev->print_name());
935     goto get_out;
936   } else {
937     Dmsg2(debuglevel, "Vol=%s dev=%s busy.\n", VolumeName,
938           vol->dev->print_name());
939   }
940   Dmsg2(debuglevel, "Vol=%s in use by %s.\n", VolumeName,
941         vol->dev->print_name());
942   rtn = false;
943 
944 get_out:
945   UnlockVolumes();
946   return rtn;
947 }
948 
949 /**
950  * Create a temporary copy of the volume list.  We do this,
951  * to avoid having the volume list locked during the
952  * call to ReserveDevice(), which would cause a deadlock.
953  *
954  * Note, we may want to add an update counter on the vol_list
955  * so that if it is modified while we are traversing the copy
956  * we can take note and act accordingly (probably redo the
957  * search at least a few times).
958  */
dup_vol_list(JobControlRecord * jcr)959 dlist* dup_vol_list(JobControlRecord* jcr)
960 {
961   dlist* temp_vol_list;
962   VolumeReservationItem* vol = NULL;
963 
964   Dmsg0(debuglevel, "lock volumes\n");
965 
966   Dmsg0(debuglevel, "duplicate vol list\n");
967   temp_vol_list = new dlist(vol, &vol->link);
968   foreach_vol (vol) {
969     VolumeReservationItem *nvol, *tvol;
970 
971     tvol = new_vol_item(NULL, vol->vol_name);
972     tvol->dev = vol->dev;
973     nvol = (VolumeReservationItem*)temp_vol_list->binary_insert(
974         tvol, CompareByVolumename);
975     if (tvol != nvol) {
976       tvol->dev = NULL; /* don't zap dev entry */
977       FreeVolItem(tvol);
978       Pmsg0(000, "Logic error. Duplicating vol list hit duplicate.\n");
979       Jmsg(jcr, M_WARNING, 0,
980            "Logic error. Duplicating vol list hit duplicate.\n");
981     }
982   }
983   endeach_vol(vol);
984   Dmsg0(debuglevel, "unlock volumes\n");
985 
986   return temp_vol_list;
987 }
988 
989 /**
990  * Free the specified temp list.
991  */
FreeTempVolList(dlist * temp_vol_list)992 void FreeTempVolList(dlist* temp_vol_list)
993 {
994   FreeVolumeList("temp_vol_list", temp_vol_list);
995   delete temp_vol_list;
996 }
997 
998 } /* namespace storagedaemon */
999