1 /*-
2  * SSLsplit - transparent SSL/TLS interception
3  * https://www.roe.ch/SSLsplit
4  *
5  * Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  * 1. Redistributions of source code must retain the above copyright notice,
11  *    this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "thrqueue.h"
30 
31 #include <stdlib.h>
32 #include <string.h>
33 #include <pthread.h>
34 
35 /*
36  * Thread-safe, bounded-size queue based on pthreads mutex and conds.
37  * Both enqueue and dequeue are available in a blocking and non-blocking
38  * version.
39  */
40 
41 struct thrqueue {
42 	void **data;
43 	size_t sz, n;
44 	size_t in, out;
45 	unsigned int block_enqueue : 1;
46 	unsigned int block_dequeue : 1;
47 	pthread_mutex_t mutex;
48 	pthread_cond_t notempty;
49 	pthread_cond_t notfull;
50 };
51 
52 /*
53  * Create a new thread-safe queue of size sz.
54  */
55 thrqueue_t *
thrqueue_new(size_t sz)56 thrqueue_new(size_t sz)
57 {
58 	thrqueue_t *queue;
59 
60 	if (!(queue = malloc(sizeof(thrqueue_t))))
61 		goto out0;
62 	if (!(queue->data = malloc(sz * sizeof(void*))))
63 		goto out1;
64 	if (pthread_mutex_init(&queue->mutex, NULL))
65 		goto out2;
66 	if (pthread_cond_init(&queue->notempty, NULL))
67 		goto out3;
68 	if (pthread_cond_init(&queue->notfull, NULL))
69 		goto out4;
70 	queue->sz = sz;
71 	queue->n = 0;
72 	queue->in = 0;
73 	queue->out = 0;
74 	queue->block_enqueue = 1;
75 	queue->block_dequeue = 1;
76 	return queue;
77 
78 out4:
79 	pthread_cond_destroy(&queue->notempty);
80 out3:
81 	pthread_mutex_destroy(&queue->mutex);
82 out2:
83 	free(queue->data);
84 out1:
85 	free(queue);
86 out0:
87 	return NULL;
88 }
89 
90 /*
91  * Free all resources associated with queue.
92  * The caller must ensure that there are no threads still
93  * using the queue when it is free'd.
94  */
95 void
thrqueue_free(thrqueue_t * queue)96 thrqueue_free(thrqueue_t *queue)
97 {
98 	free(queue->data);
99 	pthread_mutex_destroy(&queue->mutex);
100 	pthread_cond_destroy(&queue->notempty);
101 	pthread_cond_destroy(&queue->notfull);
102 	free(queue);
103 }
104 
105 /*
106  * Enqueue an item into the queue.  Will block if the queue is full.
107  * If enqueue has been switched to non-blocking mode, never blocks
108  * but instead returns NULL if queue is full.
109  * Returns enqueued item on success.
110  */
111 void *
thrqueue_enqueue(thrqueue_t * queue,void * item)112 thrqueue_enqueue(thrqueue_t *queue, void *item)
113 {
114 	pthread_mutex_lock(&queue->mutex);
115 	while (queue->n == queue->sz) {
116 		if (!queue->block_enqueue) {
117 			pthread_mutex_unlock(&queue->mutex);
118 			return NULL;
119 		}
120 		pthread_cond_wait(&queue->notfull, &queue->mutex);
121 	}
122 	queue->data[queue->in++] = item;
123 	queue->in %= queue->sz;
124 	queue->n++;
125 	pthread_mutex_unlock(&queue->mutex);
126 	pthread_cond_broadcast(&queue->notempty);
127 	return item;
128 }
129 
130 /*
131  * Non-blocking enqueue.  Never blocks.
132  * Returns NULL if the queue is full.
133  * Returns the enqueued item on success.
134  */
135 void *
thrqueue_enqueue_nb(thrqueue_t * queue,void * item)136 thrqueue_enqueue_nb(thrqueue_t *queue, void *item)
137 {
138 	pthread_mutex_lock(&queue->mutex);
139 	if (queue->n == queue->sz) {
140 		pthread_mutex_unlock(&queue->mutex);
141 		return NULL;
142 	}
143 	queue->data[queue->in++] = item;
144 	queue->in %= queue->sz;
145 	queue->n++;
146 	pthread_mutex_unlock(&queue->mutex);
147 	pthread_cond_signal(&queue->notempty);
148 	return item;
149 }
150 
151 /*
152  * Dequeue an item from the queue.  Will block if the queue is empty.
153  * If dequeue has been switched to non-blocking mode, never blocks
154  * but instead returns NULL if queue is empty.
155  * Returns dequeued item on success.
156  */
157 void *
thrqueue_dequeue(thrqueue_t * queue)158 thrqueue_dequeue(thrqueue_t *queue)
159 {
160 	void *item;
161 
162 	pthread_mutex_lock(&queue->mutex);
163 	while (queue->n == 0) {
164 		if (!queue->block_dequeue) {
165 			pthread_mutex_unlock(&queue->mutex);
166 			return NULL;
167 		}
168 		pthread_cond_wait(&queue->notempty, &queue->mutex);
169 	}
170 	item = queue->data[queue->out++];
171 	queue->out %= queue->sz;
172 	queue->n--;
173 	pthread_mutex_unlock(&queue->mutex);
174 	pthread_cond_signal(&queue->notfull);
175 	return item;
176 }
177 
178 /*
179  * Non-blocking dequeue.  Never blocks.
180  * Returns NULL if the queue is empty.
181  * Returns the dequeued item on success.
182  */
183 void *
thrqueue_dequeue_nb(thrqueue_t * queue)184 thrqueue_dequeue_nb(thrqueue_t *queue)
185 {
186 	void *item;
187 
188 	pthread_mutex_lock(&queue->mutex);
189 	if (queue->n == 0) {
190 		pthread_mutex_unlock(&queue->mutex);
191 		return NULL;
192 	}
193 	item = queue->data[queue->out++];
194 	queue->out %= queue->sz;
195 	queue->n--;
196 	pthread_mutex_unlock(&queue->mutex);
197 	pthread_cond_signal(&queue->notfull);
198 	return item;
199 }
200 
201 /*
202  * Permanently make all enqueue operations on queue non-blocking and wake
203  * up all threads currently waiting for the queue to become not full.
204  * This is to allow threads to finish their work on the queue on application
205  * shutdown, but not be blocked forever.
206  */
207 void
thrqueue_unblock_enqueue(thrqueue_t * queue)208 thrqueue_unblock_enqueue(thrqueue_t *queue)
209 {
210 	queue->block_enqueue = 0;
211 	pthread_cond_broadcast(&queue->notfull);
212 	sched_yield();
213 }
214 
215 /*
216  * Permanently make all dequeue operations on queue non-blocking and wake
217  * up all threads currently waiting for the queue to become not empty.
218  * This is to allow threads to finish their work on the queue on application
219  * shutdown, but not be blocked forever.
220  */
221 void
thrqueue_unblock_dequeue(thrqueue_t * queue)222 thrqueue_unblock_dequeue(thrqueue_t *queue)
223 {
224 	queue->block_dequeue = 0;
225 	pthread_cond_broadcast(&queue->notempty);
226 	sched_yield();
227 }
228 
229 /* vim: set noet ft=c: */
230