1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2001-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2013-2019 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  * BAREOS Thread Read/Write locking code. It permits
24  * multiple readers but only one writer.  Note, however,
25  * that the writer thread is permitted to make multiple
26  * nested write lock calls.
27  *
28  * Kern Sibbald, January MMI
29  *
30  * This code adapted from "Programming with POSIX Threads", by
31  * David R. Butenhof
32  */
33 
34 #include "include/bareos.h"
35 #include "lib/devlock.h"
36 
37 /*
38  * Initialize a read/write lock
39  *
40  *  Returns: 0 on success
41  *           errno on failure
42  */
43 
new_devlock()44 DevLock* DevLock::new_devlock()
45 {
46   DevLock* lock;
47   lock = (DevLock*)malloc(sizeof(DevLock));
48   lock->mutex = PTHREAD_MUTEX_INITIALIZER;
49   lock->read = PTHREAD_COND_INITIALIZER;
50   lock->write = PTHREAD_COND_INITIALIZER;
51   lock->writer_id = 0;
52   lock->priority = 0;
53   lock->valid = 0;
54   lock->r_active = 0;
55   lock->w_active = 0;
56   lock->r_wait = 0;
57   lock->w_wait = 0;
58   lock->reason = 0;
59   lock->prev_reason = 0;
60   lock->can_take = false;
61   return lock;
62 }
63 
init(int initial_priority)64 int DevLock::init(int initial_priority)
65 {
66   int status;
67   DevLock* rwl = this;
68 
69   rwl->r_active = rwl->w_active = 0;
70   rwl->r_wait = rwl->w_wait = 0;
71   rwl->priority = initial_priority;
72   if ((status = pthread_mutex_init(&rwl->mutex, NULL)) != 0) { return status; }
73   if ((status = pthread_cond_init(&rwl->read, NULL)) != 0) {
74     pthread_mutex_destroy(&rwl->mutex);
75     return status;
76   }
77   if ((status = pthread_cond_init(&rwl->write, NULL)) != 0) {
78     pthread_cond_destroy(&rwl->read);
79     pthread_mutex_destroy(&rwl->mutex);
80     return status;
81   }
82   rwl->valid = DEVLOCK_VALID;
83   return 0;
84 }
85 
86 /*
87  * Destroy a read/write lock
88  *
89  * Returns: 0 on success
90  *          errno on failure
91  */
destroy()92 int DevLock::destroy()
93 {
94   DevLock* rwl = this;
95   int status, status1, status2;
96 
97   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
98   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
99 
100   /*
101    * If any threads are active, report EBUSY
102    */
103   if (rwl->r_active > 0 || rwl->w_active) {
104     pthread_mutex_unlock(&rwl->mutex);
105     return EBUSY;
106   }
107 
108   /*
109    * If any threads are waiting, report EBUSY
110    */
111   if (rwl->r_wait > 0 || rwl->w_wait > 0) {
112     pthread_mutex_unlock(&rwl->mutex);
113     return EBUSY;
114   }
115 
116   rwl->valid = 0;
117   if ((status = pthread_mutex_unlock(&rwl->mutex)) != 0) { return status; }
118   status = pthread_mutex_destroy(&rwl->mutex);
119   status1 = pthread_cond_destroy(&rwl->read);
120   status2 = pthread_cond_destroy(&rwl->write);
121   return (status != 0 ? status : (status1 != 0 ? status1 : status2));
122 }
123 
124 /*
125  * Handle cleanup when the read lock condition variable
126  * wait is released.
127  */
DevlockReadRelease(void * arg)128 static void DevlockReadRelease(void* arg)
129 {
130   DevLock* rwl = (DevLock*)arg;
131   rwl->ReadRelease();
132 }
133 
ReadRelease()134 void DevLock::ReadRelease()
135 {
136   r_wait--;
137   pthread_mutex_unlock(&mutex);
138 }
139 
140 /*
141  * Handle cleanup when the write lock condition variable wait
142  * is released.
143  */
DevlockWriteRelease(void * arg)144 static void DevlockWriteRelease(void* arg)
145 {
146   DevLock* rwl = (DevLock*)arg;
147   rwl->WriteRelease();
148 }
149 
WriteRelease()150 void DevLock::WriteRelease()
151 {
152   w_wait--;
153   pthread_mutex_unlock(&mutex);
154 }
155 
156 /*
157  * Lock for read access, wait until locked (or error).
158  */
readlock()159 int DevLock::readlock()
160 {
161   DevLock* rwl = this;
162   int status;
163 
164   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
165   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
166   if (rwl->w_active) {
167     rwl->r_wait++; /* indicate that we are waiting */
168     pthread_cleanup_push(DevlockReadRelease, (void*)rwl);
169     while (rwl->w_active) {
170       status = pthread_cond_wait(&rwl->read, &rwl->mutex);
171       if (status != 0) { break; /* error, bail out */ }
172     }
173     pthread_cleanup_pop(0);
174     rwl->r_wait--; /* we are no longer waiting */
175   }
176   if (status == 0) { rwl->r_active++; /* we are running */ }
177   pthread_mutex_unlock(&rwl->mutex);
178   return status;
179 }
180 
181 /*
182  * Attempt to lock for read access, don't wait
183  */
readtrylock()184 int DevLock::readtrylock()
185 {
186   DevLock* rwl = this;
187   int status, status2;
188 
189   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
190   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
191   if (rwl->w_active) {
192     status = EBUSY;
193   } else {
194     rwl->r_active++; /* we are running */
195   }
196   status2 = pthread_mutex_unlock(&rwl->mutex);
197   return (status == 0 ? status2 : status);
198 }
199 
200 /*
201  * Unlock read lock
202  */
readunlock()203 int DevLock::readunlock()
204 {
205   DevLock* rwl = this;
206   int status, status2;
207 
208   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
209   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
210   rwl->r_active--;
211   if (rwl->r_active == 0 && rwl->w_wait > 0) { /* if writers waiting */
212     status = pthread_cond_broadcast(&rwl->write);
213   }
214   status2 = pthread_mutex_unlock(&rwl->mutex);
215   return (status == 0 ? status2 : status);
216 }
217 
218 
219 /*
220  * Lock for write access, wait until locked (or error).
221  *   Multiple nested write locking is permitted.
222  */
Writelock(int areason,bool acan_take)223 int DevLock::Writelock(int areason, bool acan_take)
224 {
225   DevLock* rwl = this;
226   int status;
227 
228   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
229   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
230   if (rwl->w_active && pthread_equal(rwl->writer_id, pthread_self())) {
231     rwl->w_active++;
232     pthread_mutex_unlock(&rwl->mutex);
233     return 0;
234   }
235   if (rwl->w_active || rwl->r_active > 0) {
236     rwl->w_wait++; /* indicate that we are waiting */
237     pthread_cleanup_push(DevlockWriteRelease, (void*)rwl);
238     while (rwl->w_active || rwl->r_active > 0) {
239       if ((status = pthread_cond_wait(&rwl->write, &rwl->mutex)) != 0) {
240         break; /* error, bail out */
241       }
242     }
243     pthread_cleanup_pop(0);
244     rwl->w_wait--; /* we are no longer waiting */
245   }
246   if (status == 0) {
247     rwl->w_active++;                 /* we are running */
248     rwl->writer_id = pthread_self(); /* save writer thread's id */
249   }
250   rwl->reason = areason;
251   rwl->can_take = acan_take;
252   pthread_mutex_unlock(&rwl->mutex);
253   return status;
254 }
255 
256 /*
257  * Attempt to lock for write access, don't wait
258  */
writetrylock()259 int DevLock::writetrylock()
260 {
261   DevLock* rwl = this;
262   int status, status2;
263 
264   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
265   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
266   if (rwl->w_active && pthread_equal(rwl->writer_id, pthread_self())) {
267     rwl->w_active++;
268     pthread_mutex_unlock(&rwl->mutex);
269     return 0;
270   }
271   if (rwl->w_active || rwl->r_active > 0) {
272     status = EBUSY;
273   } else {
274     rwl->w_active = 1;               /* we are running */
275     rwl->writer_id = pthread_self(); /* save writer thread's id */
276   }
277   status2 = pthread_mutex_unlock(&rwl->mutex);
278   return (status == 0 ? status2 : status);
279 }
280 
281 /*
282  * Unlock write lock
283  *  Start any waiting writers in preference to waiting readers
284  */
writeunlock()285 int DevLock::writeunlock()
286 {
287   DevLock* rwl = this;
288   int status, status2;
289 
290   if (rwl->valid != DEVLOCK_VALID) { return EINVAL; }
291   if ((status = pthread_mutex_lock(&rwl->mutex)) != 0) { return status; }
292   if (rwl->w_active <= 0) {
293     pthread_mutex_unlock(&rwl->mutex);
294     Jmsg0(NULL, M_ABORT, 0, _("writeunlock called too many times.\n"));
295   }
296   rwl->w_active--;
297   if (!pthread_equal(pthread_self(), rwl->writer_id)) {
298     pthread_mutex_unlock(&rwl->mutex);
299     Jmsg0(NULL, M_ABORT, 0, _("writeunlock by non-owner.\n"));
300   }
301   if (rwl->w_active > 0) {
302     status = 0; /* writers still active */
303   } else {
304     /* No more writers, awaken someone */
305     if (rwl->r_wait > 0) { /* if readers waiting */
306       status = pthread_cond_broadcast(&rwl->read);
307     } else if (rwl->w_wait > 0) {
308       status = pthread_cond_broadcast(&rwl->write);
309     }
310   }
311   status2 = pthread_mutex_unlock(&rwl->mutex);
312   return (status == 0 ? status2 : status);
313 }
314 
TakeLock(take_lock_t * hold,int areason)315 int DevLock::TakeLock(take_lock_t* hold, int areason)
316 {
317   int status;
318 
319   if (valid != DEVLOCK_VALID) { return EINVAL; }
320   if ((status = pthread_mutex_lock(&mutex)) != 0) { return status; }
321   hold->reason = reason;
322   hold->prev_reason = prev_reason;
323   hold->writer_id = writer_id;
324   reason = areason;
325   writer_id = pthread_self();
326   status = pthread_mutex_unlock(&mutex);
327   return status;
328 }
329 
ReturnLock(take_lock_t * hold)330 int DevLock::ReturnLock(take_lock_t* hold)
331 {
332   int status, status2;
333 
334   if (valid != DEVLOCK_VALID) { return EINVAL; }
335   if ((status = pthread_mutex_lock(&mutex)) != 0) { return status; }
336   reason = hold->reason;
337   prev_reason = hold->prev_reason;
338   writer_id = hold->writer_id;
339   writer_id = pthread_self();
340   status2 = pthread_mutex_unlock(&mutex);
341   if (w_active || w_wait) { status = pthread_cond_broadcast(&write); }
342   return (status == 0 ? status2 : status);
343 }
344