0
|
1 /* Distributed Checksum Clearinghouse |
|
2 * |
|
3 * threaded version of client locking |
|
4 * |
|
5 * Copyright (c) 2008 by Rhyolite Software, LLC |
|
6 * |
|
7 * This agreement is not applicable to any entity which sells anti-spam |
|
8 * solutions to others or provides an anti-spam solution as part of a |
|
9 * security solution sold to other entities, or to a private network |
|
10 * which employs the DCC or uses data provided by operation of the DCC |
|
11 * but does not provide corresponding data to other users. |
|
12 * |
|
13 * Permission to use, copy, modify, and distribute this software without |
|
14 * changes for any purpose with or without fee is hereby granted, provided |
|
15 * that the above copyright notice and this permission notice appear in all |
|
16 * copies and any distributed versions or copies are either unchanged |
|
17 * or not called anything similar to "DCC" or "Distributed Checksum |
|
18 * Clearinghouse". |
|
19 * |
|
20 * Parties not eligible to receive a license under this agreement can |
|
21 * obtain a commercial license to use DCC by contacting Rhyolite Software |
|
22 * at sales@rhyolite.com. |
|
23 * |
|
24 * A commercial license would be for Distributed Checksum and Reputation |
|
25 * Clearinghouse software. That software includes additional features. This |
|
26 * free license for Distributed ChecksumClearinghouse Software does not in any |
|
27 * way grant permision to use Distributed Checksum and Reputation Clearinghouse |
|
28 * software |
|
29 * |
|
30 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL |
|
31 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES |
|
32 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC |
|
33 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES |
|
34 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, |
|
35 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, |
|
36 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
|
37 * SOFTWARE. |
|
38 * |
|
39 * Rhyolite Software DCC 1.3.103-1.73 $Revision$ |
|
40 */ |
|
41 |
|
42 #include "dcc_ck.h" |
|
43 #include "dcc_clnt.h" |
|
44 #ifdef HAVE_PTHREAD_H |
|
45 #include <pthread.h> |
|
46 #else |
|
47 #include <sys/pthread.h> |
|
48 #endif |
|
49 #include <signal.h> |
|
50 |
|
51 u_char grey_on; |
|
52 u_char grey_query_only; |
|
53 |
|
54 DCC_WF cmn_wf, cmn_tmp_wf; |
|
55 |
|
56 |
|
57 /* many POSIX thread implementations have unexpected side effects on |
|
58 * ordinary system calls, so don't use the threaded version unless |
|
59 * necessary */ |
|
60 |
|
61 /* protect the links among contexts and the miscellaneous global |
|
62 * variables in the DCC client library */ |
|
63 static pthread_mutex_t ctxts_mutex; |
|
64 #ifdef DCC_DEBUG_CLNT_LOCK |
|
65 static pthread_t ctxts_owner; |
|
66 #endif |
|
67 |
|
68 /* make syslog() thread-safe */ |
|
69 static pthread_mutex_t syslog_mutex; |
|
70 static u_char syslog_threaded; |
|
71 |
|
72 #ifdef DCC_DEBUG_HEAP |
|
73 static pthread_mutex_t malloc_mutex; |
|
74 static u_char malloc_threaded; |
|
75 #endif |
|
76 |
|
77 /* make gethostbyname() thread-safe */ |
|
78 static pthread_mutex_t host_mutex; |
|
79 |
|
80 static pthread_t clnt_resolve_tid; |
|
81 static pthread_cond_t clnt_resolve_cond; |
|
82 static u_char clnt_resolve_stopping; |
|
83 |
|
84 /* The threaded DNS blacklist support uses fork() to create helper processes |
|
85 * to wait for the typical single-threaded DNS resolver library. */ |
|
86 static pthread_mutex_t helper_mutex; |
|
87 |
|
88 /* create user logs in a burst while holding a lock |
|
89 * this reduces the total number of file descriptors needed |
|
90 * at a cost of stopping everything while copying from the main |
|
91 * log file to the per-user log files */ |
|
92 pthread_mutex_t user_log_mutex; |
|
93 pthread_t user_log_owner; |
|
94 |
|
95 |
|
96 /* this is used only in the threaded DCC clients */ |
|
97 void |
|
98 clnt_sigs_off(sigset_t *sigsold) |
|
99 { |
|
100 sigset_t sigsnew; |
|
101 int error; |
|
102 |
|
103 sigemptyset(&sigsnew); |
|
104 sigaddset(&sigsnew, SIGHUP); |
|
105 sigaddset(&sigsnew, SIGINT); |
|
106 sigaddset(&sigsnew, SIGTERM); |
|
107 sigaddset(&sigsnew, SIGALRM); |
|
108 error = pthread_sigmask(SIG_BLOCK, &sigsnew, sigsold); |
|
109 if (error) |
|
110 dcc_logbad(EX_SOFTWARE, "pthread_sigmask(): %s", |
|
111 ERROR_STR1(error)); |
|
112 } |
|
113 |
|
114 |
|
115 |
|
116 void |
|
117 dcc_ctxts_lock(void) |
|
118 { |
|
119 int error; |
|
120 |
|
121 #ifdef DCC_DEBUG_CLNT_LOCK |
|
122 if (ctxts_owner == pthread_self()) |
|
123 dcc_logbad(EX_SOFTWARE, "already have ctxts lock"); |
|
124 #endif |
|
125 |
|
126 error = pthread_mutex_lock(&ctxts_mutex); |
|
127 if (error) |
|
128 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(ctxts): %s", |
|
129 ERROR_STR1(error)); |
|
130 #ifdef DCC_DEBUG_CLNT_LOCK |
|
131 ctxts_owner = pthread_self(); |
|
132 #endif |
|
133 } |
|
134 |
|
135 |
|
136 |
|
137 void |
|
138 dcc_ctxts_unlock(void) |
|
139 { |
|
140 int error; |
|
141 |
|
142 #ifdef DCC_DEBUG_CLNT_LOCK |
|
143 ctxts_owner = 0; |
|
144 #endif |
|
145 error = pthread_mutex_unlock(&ctxts_mutex); |
|
146 if (error) |
|
147 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(ctxts): %s", |
|
148 ERROR_STR1(error)); |
|
149 } |
|
150 |
|
151 |
|
152 |
|
153 #ifdef DCC_DEBUG_CLNT_LOCK |
|
154 void |
|
155 assert_ctxts_locked(void) |
|
156 { |
|
157 if (ctxts_owner != pthread_self()) |
|
158 dcc_logbad(EX_SOFTWARE, "don't have ctxts lock"); |
|
159 } |
|
160 |
|
161 |
|
162 |
|
163 void |
|
164 assert_ctxts_unlocked(void) |
|
165 { |
|
166 if (ctxts_owner == pthread_self()) |
|
167 dcc_logbad(EX_SOFTWARE, "have ctxts lock"); |
|
168 } |
|
169 #endif |
|
170 |
|
171 |
|
172 |
|
173 void |
|
174 dcc_syslog_lock(void) |
|
175 { |
|
176 int error; |
|
177 |
|
178 if (!syslog_threaded) |
|
179 return; |
|
180 error = pthread_mutex_lock(&syslog_mutex); |
|
181 if (error) |
|
182 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(syslog): %s", |
|
183 ERROR_STR1(error)); |
|
184 } |
|
185 |
|
186 |
|
187 |
|
188 void |
|
189 dcc_syslog_unlock(void) |
|
190 { |
|
191 int error; |
|
192 |
|
193 if (!syslog_threaded) |
|
194 return; |
|
195 error = pthread_mutex_unlock(&syslog_mutex); |
|
196 if (error) |
|
197 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(syslog): %s", |
|
198 ERROR_STR1(error)); |
|
199 } |
|
200 |
|
201 |
|
202 |
|
203 /* gethostbyname() etc. are usually not reentrant */ |
|
204 u_char dcc_host_locked = 1; |
|
205 |
|
206 /* do not worry about locking gethostbyname() until the locks have |
|
207 * been initialized */ |
|
208 static u_char dcc_host_threaded = 0; |
|
209 |
|
210 /* This function is mentioned in dccifd/dccif-test/dccif-test.c |
|
211 * and so cannot change lightly. */ |
|
212 void |
|
213 dcc_host_lock(void) |
|
214 { |
|
215 int error; |
|
216 |
|
217 if (!dcc_host_threaded) |
|
218 return; |
|
219 |
|
220 error = pthread_mutex_lock(&host_mutex); |
|
221 if (error) |
|
222 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(host): %s", |
|
223 ERROR_STR1(error)); |
|
224 dcc_host_locked = 1; |
|
225 } |
|
226 |
|
227 |
|
228 |
|
229 /* This function is mentioned in dccifd/dccif-test/dccif-test.c |
|
230 * and so cannot change lightly. */ |
|
231 void |
|
232 dcc_host_unlock(void) |
|
233 { |
|
234 int error; |
|
235 |
|
236 if (!dcc_host_threaded) |
|
237 return; |
|
238 |
|
239 dcc_host_locked = 0; |
|
240 error = pthread_mutex_unlock(&host_mutex); |
|
241 if (error) |
|
242 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(host): %s", |
|
243 ERROR_STR1(error)); |
|
244 } |
|
245 |
|
246 |
|
247 |
|
248 #ifdef DCC_DEBUG_HEAP |
|
249 void |
|
250 dcc_malloc_lock(void) |
|
251 { |
|
252 int error; |
|
253 |
|
254 if (!malloc_threaded) /* no locking until locks created */ |
|
255 return; |
|
256 error = pthread_mutex_lock(&malloc_mutex); |
|
257 if (error) |
|
258 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(malloc): %s", |
|
259 ERROR_STR1(error)); |
|
260 } |
|
261 |
|
262 |
|
263 void |
|
264 dcc_malloc_unlock(void) |
|
265 { |
|
266 int error; |
|
267 |
|
268 if (!malloc_threaded) /* no locking until locks created */ |
|
269 return; |
|
270 error = pthread_mutex_unlock(&malloc_mutex); |
|
271 if (error) |
|
272 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(malloc): %s", |
|
273 ERROR_STR1(error)); |
|
274 } |
|
275 #endif /* DCC_DEBUG_HEAP */ |
|
276 |
|
277 |
|
278 |
|
279 #ifdef HAVE_LOCALTIME_R |
|
280 /* make localtime() thread safe */ |
|
281 static pthread_mutex_t localtime_mutex; |
|
282 static u_char localtime_threaded; |
|
283 |
|
284 void |
|
285 dcc_localtime_lock(void) |
|
286 { |
|
287 int error; |
|
288 |
|
289 if (!localtime_threaded) |
|
290 return; |
|
291 error = pthread_mutex_lock(&localtime_mutex); |
|
292 if (error) |
|
293 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(localtime): %s", |
|
294 ERROR_STR1(error)); |
|
295 } |
|
296 |
|
297 |
|
298 |
|
299 void |
|
300 dcc_localtime_unlock(void) |
|
301 { |
|
302 int error; |
|
303 |
|
304 if (!localtime_threaded) |
|
305 return; |
|
306 error = pthread_mutex_unlock(&localtime_mutex); |
|
307 if (error) |
|
308 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(localtime): %s", |
|
309 ERROR_STR1(error)); |
|
310 } |
|
311 #endif /* HAVE_LOCALTIME_R */ |
|
312 |
|
313 |
|
314 |
|
315 const char *main_white_nm; |
|
316 const char *mapfile_nm = DCC_MAP_NM_DEF; |
|
317 |
|
318 /* resolve things */ |
|
319 DCC_CLNT_CTXT * |
|
320 resolve_sub(DCC_CLNT_CTXT *ctxt, /* 0=allocate and initialize */ |
|
321 DCC_WF *wf, DCC_WF *tmp_wf) |
|
322 { |
|
323 DCC_EMSG emsg; |
|
324 |
|
325 if (!ctxt) { |
|
326 dcc_wf_init(wf, 0); |
|
327 if (main_white_nm) |
|
328 dcc_new_white_nm(emsg, wf, main_white_nm); |
|
329 |
|
330 emsg[0] = '\0'; |
|
331 if (!dcc_map_info(emsg, mapfile_nm, -1)) |
|
332 dcc_logbad(EX_USAGE, "%s", emsg); |
|
333 |
|
334 ctxt = dcc_alloc_ctxt(); |
|
335 } |
|
336 |
|
337 if (wf->ascii_nm[0] != '\0') { |
|
338 if (clnt_resolve_stopping) |
|
339 return ctxt; |
|
340 dcc_ctxts_unlock(); |
|
341 switch (dcc_rdy_white(emsg, wf, tmp_wf)) { |
|
342 case DCC_WHITE_OK: |
|
343 case DCC_WHITE_NOFILE: |
|
344 case DCC_WHITE_SILENT: |
|
345 break; |
|
346 case DCC_WHITE_CONTINUE: |
|
347 case DCC_WHITE_COMPLAIN: |
|
348 dcc_error_msg("%s", emsg); |
|
349 break; |
|
350 } |
|
351 dcc_ctxts_lock(); |
|
352 |
|
353 /* Tell the other threads that the hash table |
|
354 * in the disk file has changed. |
|
355 * This kludge lets this thread use its own |
|
356 * wf structure without hogging the lock |
|
357 * on cmn_wf. */ |
|
358 if (wf->closed) { |
|
359 wf->closed = 0; |
|
360 cmn_wf.need_reopen = 1; |
|
361 } |
|
362 } |
|
363 |
|
364 return ctxt; |
|
365 } |
|
366 |
|
367 |
|
368 |
|
369 static void * NRATTRIB |
|
370 clnt_resolve_thread(void *arg UATTRIB) |
|
371 { |
|
372 DCC_WF wf, tmp_wf; |
|
373 DCC_CLNT_CTXT *ctxt; |
|
374 DCC_EMSG emsg; |
|
375 int error; |
|
376 |
|
377 /* let the thread in charge of signals deal with them */ |
|
378 clnt_sigs_off(0); |
|
379 |
|
380 ctxt = 0; |
|
381 dcc_ctxts_lock(); |
|
382 for (;;) { |
|
383 if (clnt_resolve_stopping) { |
|
384 dcc_ctxts_unlock(); |
|
385 pthread_exit(0); |
|
386 } |
|
387 |
|
388 ctxt = resolve_sub(ctxt, &wf, &tmp_wf); |
|
389 |
|
390 if (clnt_resolve_stopping) { |
|
391 dcc_ctxts_unlock(); |
|
392 pthread_exit(0); |
|
393 } |
|
394 emsg[0] = '\0'; |
|
395 if (!dcc_clnt_rdy(emsg, ctxt, DCC_CLNT_FG_NO_FAIL)) |
|
396 dcc_error_msg("%s", emsg); |
|
397 else if (!dcc_info_unlock(emsg)) |
|
398 dcc_logbad(dcc_ex_code, "%s", emsg); |
|
399 |
|
400 if (grey_on) { |
|
401 if (clnt_resolve_stopping) { |
|
402 dcc_ctxts_unlock(); |
|
403 pthread_exit(0); |
|
404 } |
|
405 emsg[0] = '\0'; |
|
406 if (!dcc_clnt_rdy(emsg, ctxt, (DCC_CLNT_FG_GREY |
|
407 | DCC_CLNT_FG_NO_FAIL))) |
|
408 dcc_error_msg("%s", emsg); |
|
409 else if (!dcc_info_unlock(emsg)) |
|
410 dcc_logbad(dcc_ex_code, "%s", emsg); |
|
411 } |
|
412 |
|
413 #ifdef DCC_DEBUG_CLNT_LOCK |
|
414 ctxts_owner = 0; |
|
415 #endif |
|
416 error = pthread_cond_wait(&clnt_resolve_cond, &ctxts_mutex); |
|
417 if (error != 0) |
|
418 dcc_logbad(EX_SOFTWARE, |
|
419 "pthread_cond_wait(resolve): %s", |
|
420 ERROR_STR1(error)); |
|
421 #ifdef DCC_DEBUG_CLNT_LOCK |
|
422 ctxts_owner = pthread_self(); |
|
423 #endif |
|
424 } |
|
425 } |
|
426 |
|
427 |
|
428 |
|
429 u_char /* 1=awoke the resolver thread */ |
|
430 dcc_clnt_wake_resolve(void) |
|
431 { |
|
432 int error; |
|
433 |
|
434 /* we cannot awaken ourself or awaken the thread before it starts */ |
|
435 if (clnt_resolve_tid == 0 |
|
436 || pthread_equal(pthread_self(), clnt_resolve_tid)) |
|
437 return 0; |
|
438 |
|
439 error = pthread_cond_signal(&clnt_resolve_cond); |
|
440 if (error != 0) |
|
441 dcc_logbad(EX_SOFTWARE, "pthread_cond_signal(resolve): %s", |
|
442 ERROR_STR1(error)); |
|
443 return 1; |
|
444 } |
|
445 |
|
446 |
|
447 |
|
448 void |
|
449 dcc_clnt_stop_resolve(void) |
|
450 { |
|
451 if (clnt_resolve_stopping) |
|
452 return; |
|
453 clnt_resolve_stopping = 1; |
|
454 if (pthread_equal(pthread_self(), clnt_resolve_tid)) |
|
455 return; |
|
456 pthread_cond_signal(&clnt_resolve_cond); |
|
457 } |
|
458 |
|
459 |
|
460 |
|
461 /* some pthreads implementations (e.g. AIX) don't like static |
|
462 * initializations */ |
|
463 static void |
|
464 dcc_mutex_init(void *mutex, const char *nm) |
|
465 { |
|
466 int error = pthread_mutex_init(mutex, 0); |
|
467 if (error) |
|
468 dcc_logbad(EX_SOFTWARE, "pthread_mutex_init(%s): %s", |
|
469 nm, ERROR_STR1(error)); |
|
470 } |
|
471 |
|
472 |
|
473 |
|
474 static pthread_mutex_t work_mutex; |
|
475 static pthread_t cwf_owner; |
|
476 |
|
477 void |
|
478 lock_work(void) |
|
479 { |
|
480 int result = pthread_mutex_lock(&work_mutex); |
|
481 if (result) |
|
482 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(work_free): %s", |
|
483 ERROR_STR1(result)); |
|
484 } |
|
485 |
|
486 |
|
487 |
|
488 void |
|
489 unlock_work(void) |
|
490 { |
|
491 int result = pthread_mutex_unlock(&work_mutex); |
|
492 if (result) |
|
493 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(work_free): %s", |
|
494 ERROR_STR1(result)); |
|
495 } |
|
496 |
|
497 |
|
498 |
|
499 /* lock all CWF structures as well as the cmn_wf structure */ |
|
500 static pthread_mutex_t cwf_mutex; |
|
501 |
|
502 void |
|
503 lock_wf(void) |
|
504 { |
|
505 int error = pthread_mutex_lock(&cwf_mutex); |
|
506 if (error) |
|
507 dcc_logbad(EX_SOFTWARE, "pthread_mutex_lock(cwf): %s", |
|
508 ERROR_STR1(error)); |
|
509 cwf_owner = pthread_self(); |
|
510 } |
|
511 |
|
512 |
|
513 |
|
514 void |
|
515 unlock_wf(void) |
|
516 { |
|
517 int error; |
|
518 cwf_owner = 0; |
|
519 error = pthread_mutex_unlock(&cwf_mutex); |
|
520 if (error) |
|
521 dcc_logbad(EX_SOFTWARE, "pthread_mutex_unlock(cwf): %s", |
|
522 ERROR_STR1(error)); |
|
523 } |
|
524 |
|
525 |
|
526 |
|
527 #ifdef DCC_DEBUG_CLNT_LOCK |
|
528 void |
|
529 assert_cwf_locked(void) |
|
530 { |
|
531 if (cwf_owner != pthread_self()) |
|
532 dcc_logbad(EX_SOFTWARE, "don't have cwf lock"); |
|
533 } |
|
534 #endif |
|
535 |
|
536 |
|
537 |
|
538 void |
|
539 dcc_clnt_thread_init(void) |
|
540 { |
|
541 DCC_CLNT_CTXT *ctxt; |
|
542 int error; |
|
543 |
|
544 /* Some pthreads implementations (e.g. AIX) don't like static |
|
545 * POSIX thread initializations */ |
|
546 |
|
547 dcc_mutex_init(&ctxts_mutex, "ctxt"); |
|
548 dcc_mutex_init(&syslog_mutex, "syslog"); |
|
549 syslog_threaded = 1; |
|
550 #ifdef DCC_DEBUG_HEAP |
|
551 dcc_mutex_init(&malloc_mutex, "heap"); |
|
552 malloc_threaded = 1; |
|
553 #endif |
|
554 #ifndef HAVE_LOCALTIME_R |
|
555 dcc_mutex_init(&localtime_mutex, "localtime"); |
|
556 localtime_threaded = 1; |
|
557 #endif |
|
558 dcc_mutex_init(&host_mutex, "host"); |
|
559 dcc_host_threaded = 1; |
|
560 dcc_host_locked = 0; |
|
561 |
|
562 dcc_mutex_init(&user_log_mutex, "user_log"); |
|
563 |
|
564 dcc_mutex_init(&work_mutex, "wf_mutex"); |
|
565 dcc_mutex_init(&cwf_mutex, "cwf_mutex"); |
|
566 |
|
567 /* prevent race between resolver thread and other threads to |
|
568 * initialize things by doing it before starting the thread */ |
|
569 lock_wf(); |
|
570 dcc_ctxts_lock(); |
|
571 ctxt = resolve_sub(0, &cmn_wf, &cmn_tmp_wf); |
|
572 dcc_rel_ctxt(ctxt); |
|
573 dcc_ctxts_unlock(); |
|
574 unlock_wf(); |
|
575 |
|
576 error = pthread_cond_init(&clnt_resolve_cond, 0); |
|
577 if (error) |
|
578 dcc_logbad(EX_SOFTWARE, "phtread_cond_init(resolve): %s", |
|
579 ERROR_STR1(error)); |
|
580 error = pthread_create(&clnt_resolve_tid, 0, clnt_resolve_thread, 0); |
|
581 if (error) |
|
582 dcc_logbad(EX_SOFTWARE, "pthread_create(resolve): %s", |
|
583 ERROR_STR1(error)); |
|
584 error = pthread_detach(clnt_resolve_tid); |
|
585 if (error) |
|
586 dcc_logbad(EX_SOFTWARE, "pthread_detach(resolve): %s", |
|
587 ERROR_STR1(error)); |
|
588 } |
|
589 |
|
590 |
|
591 |
|
592 /* protect DNS blacklist helper channels */ |
|
593 u_char |
|
594 helper_lock_init(void) |
|
595 { |
|
596 dcc_mutex_init(&helper_mutex, "helper"); |
|
597 return 1; |
|
598 } |
|
599 |
|
600 |
|
601 |
|
602 void |
|
603 helper_lock(void) |
|
604 { |
|
605 int error; |
|
606 |
|
607 error = pthread_mutex_lock(&helper_mutex); |
|
608 if (error) |
|
609 dcc_logbad(EX_SOFTWARE, |
|
610 "pthread_mutex_lock(helper counter): %s", |
|
611 ERROR_STR1(error)); |
|
612 } |
|
613 |
|
614 |
|
615 |
|
616 void |
|
617 helper_unlock(void) |
|
618 { |
|
619 int error; |
|
620 |
|
621 error = pthread_mutex_unlock(&helper_mutex); |
|
622 if (error) |
|
623 dcc_logbad(EX_SOFTWARE, |
|
624 "pthread_mutex_unlock(helper counter): %s", |
|
625 ERROR_STR1(error)); |
|
626 } |