1 /*
2 * hdhomerun_debug.c
3 *
4 * Copyright © 2007-2016 Silicondust USA Inc. <www.silicondust.com>.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /*
22 * The debug logging includes optional support for connecting to the
23 * Silicondust support server. This option should not be used without
24 * being explicitly enabled by the user. Debug information should be
25 * limited to information useful to diagnosing a problem.
26 * - Silicondust.
27 */
28
29 #include "hdhomerun.h"
30
31 #if !defined(HDHOMERUN_DEBUG_HOST)
32 #define HDHOMERUN_DEBUG_HOST "debug.silicondust.com"
33 #endif
34 #if !defined(HDHOMERUN_DEBUG_PORT)
35 #define HDHOMERUN_DEBUG_PORT 8002
36 #endif
37
38 #define HDHOMERUN_DEBUG_CONNECT_RETRY_TIME 30000
39 #define HDHOMERUN_DEBUG_CONNECT_TIMEOUT 10000
40 #define HDHOMERUN_DEBUG_SEND_TIMEOUT 10000
41
42 struct hdhomerun_debug_message_t
43 {
44 struct hdhomerun_debug_message_t *next;
45 char buffer[2048];
46 };
47
48 struct hdhomerun_debug_t
49 {
50 thread_task_t thread;
51 volatile bool enabled;
52 volatile bool terminate;
53 char *prefix;
54
55 thread_mutex_t print_lock;
56 thread_mutex_t queue_lock;
57 thread_mutex_t send_lock;
58
59 thread_cond_t queue_cond;
60 struct hdhomerun_debug_message_t *queue_head;
61 struct hdhomerun_debug_message_t *queue_tail;
62 uint32_t queue_depth;
63
64 uint64_t connect_delay;
65
66 char *file_name;
67 FILE *file_fp;
68 struct hdhomerun_sock_t *sock;
69 };
70
71 static void hdhomerun_debug_thread_execute(void *arg);
72
hdhomerun_debug_create(void)73 struct hdhomerun_debug_t *hdhomerun_debug_create(void)
74 {
75 struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)calloc(1, sizeof(struct hdhomerun_debug_t));
76 if (!dbg) {
77 return NULL;
78 }
79
80 thread_mutex_init(&dbg->print_lock);
81 thread_mutex_init(&dbg->queue_lock);
82 thread_mutex_init(&dbg->send_lock);
83 thread_cond_init(&dbg->queue_cond);
84
85 if (!thread_task_create(&dbg->thread, &hdhomerun_debug_thread_execute, dbg)) {
86 free(dbg);
87 return NULL;
88 }
89
90 return dbg;
91 }
92
hdhomerun_debug_destroy(struct hdhomerun_debug_t * dbg)93 void hdhomerun_debug_destroy(struct hdhomerun_debug_t *dbg)
94 {
95 if (!dbg) {
96 return;
97 }
98
99 dbg->terminate = true;
100 thread_cond_signal(&dbg->queue_cond);
101 thread_task_join(dbg->thread);
102
103 if (dbg->prefix) {
104 free(dbg->prefix);
105 }
106 if (dbg->file_name) {
107 free(dbg->file_name);
108 }
109 if (dbg->file_fp) {
110 fclose(dbg->file_fp);
111 }
112 if (dbg->sock) {
113 hdhomerun_sock_destroy(dbg->sock);
114 }
115
116 thread_cond_dispose(&dbg->queue_cond);
117 thread_mutex_dispose(&dbg->print_lock);
118 thread_mutex_dispose(&dbg->queue_lock);
119 thread_mutex_dispose(&dbg->send_lock);
120 free(dbg);
121 }
122
123 /* Send lock held by caller */
hdhomerun_debug_close_internal(struct hdhomerun_debug_t * dbg)124 static void hdhomerun_debug_close_internal(struct hdhomerun_debug_t *dbg)
125 {
126 if (dbg->file_fp) {
127 fclose(dbg->file_fp);
128 dbg->file_fp = NULL;
129 }
130
131 if (dbg->sock) {
132 hdhomerun_sock_destroy(dbg->sock);
133 dbg->sock = NULL;
134 }
135 }
136
hdhomerun_debug_close(struct hdhomerun_debug_t * dbg,uint64_t timeout)137 void hdhomerun_debug_close(struct hdhomerun_debug_t *dbg, uint64_t timeout)
138 {
139 if (!dbg) {
140 return;
141 }
142
143 if (timeout > 0) {
144 hdhomerun_debug_flush(dbg, timeout);
145 }
146
147 thread_mutex_lock(&dbg->send_lock);
148 hdhomerun_debug_close_internal(dbg);
149 dbg->connect_delay = 0;
150 thread_mutex_unlock(&dbg->send_lock);
151 }
152
hdhomerun_debug_set_filename(struct hdhomerun_debug_t * dbg,const char * filename)153 void hdhomerun_debug_set_filename(struct hdhomerun_debug_t *dbg, const char *filename)
154 {
155 if (!dbg) {
156 return;
157 }
158
159 thread_mutex_lock(&dbg->send_lock);
160
161 if (!filename && !dbg->file_name) {
162 thread_mutex_unlock(&dbg->send_lock);
163 return;
164 }
165 if (filename && dbg->file_name) {
166 if (strcmp(filename, dbg->file_name) == 0) {
167 thread_mutex_unlock(&dbg->send_lock);
168 return;
169 }
170 }
171
172 hdhomerun_debug_close_internal(dbg);
173 dbg->connect_delay = 0;
174
175 if (dbg->file_name) {
176 free(dbg->file_name);
177 dbg->file_name = NULL;
178 }
179 if (filename) {
180 dbg->file_name = strdup(filename);
181 }
182
183 thread_mutex_unlock(&dbg->send_lock);
184 }
185
hdhomerun_debug_set_prefix(struct hdhomerun_debug_t * dbg,const char * prefix)186 void hdhomerun_debug_set_prefix(struct hdhomerun_debug_t *dbg, const char *prefix)
187 {
188 if (!dbg) {
189 return;
190 }
191
192 thread_mutex_lock(&dbg->print_lock);
193
194 if (dbg->prefix) {
195 free(dbg->prefix);
196 dbg->prefix = NULL;
197 }
198
199 if (prefix) {
200 dbg->prefix = strdup(prefix);
201 }
202
203 thread_mutex_unlock(&dbg->print_lock);
204 }
205
hdhomerun_debug_enable(struct hdhomerun_debug_t * dbg)206 void hdhomerun_debug_enable(struct hdhomerun_debug_t *dbg)
207 {
208 if (!dbg) {
209 return;
210 }
211 if (dbg->enabled) {
212 return;
213 }
214
215 dbg->enabled = true;
216 thread_cond_signal(&dbg->queue_cond);
217 }
218
hdhomerun_debug_disable(struct hdhomerun_debug_t * dbg)219 void hdhomerun_debug_disable(struct hdhomerun_debug_t *dbg)
220 {
221 if (!dbg) {
222 return;
223 }
224
225 dbg->enabled = false;
226 }
227
hdhomerun_debug_enabled(struct hdhomerun_debug_t * dbg)228 bool hdhomerun_debug_enabled(struct hdhomerun_debug_t *dbg)
229 {
230 if (!dbg) {
231 return false;
232 }
233
234 return dbg->enabled;
235 }
236
hdhomerun_debug_flush(struct hdhomerun_debug_t * dbg,uint64_t timeout)237 void hdhomerun_debug_flush(struct hdhomerun_debug_t *dbg, uint64_t timeout)
238 {
239 if (!dbg) {
240 return;
241 }
242
243 timeout = getcurrenttime() + timeout;
244
245 while (getcurrenttime() < timeout) {
246 thread_mutex_lock(&dbg->queue_lock);
247 struct hdhomerun_debug_message_t *message = dbg->queue_head;
248 thread_mutex_unlock(&dbg->queue_lock);
249
250 if (!message) {
251 return;
252 }
253
254 msleep_approx(16);
255 }
256 }
257
hdhomerun_debug_printf(struct hdhomerun_debug_t * dbg,const char * fmt,...)258 void hdhomerun_debug_printf(struct hdhomerun_debug_t *dbg, const char *fmt, ...)
259 {
260 va_list args;
261 va_start(args, fmt);
262 hdhomerun_debug_vprintf(dbg, fmt, args);
263 va_end(args);
264 }
265
hdhomerun_debug_vprintf(struct hdhomerun_debug_t * dbg,const char * fmt,va_list args)266 void hdhomerun_debug_vprintf(struct hdhomerun_debug_t *dbg, const char *fmt, va_list args)
267 {
268 if (!dbg) {
269 return;
270 }
271
272 struct hdhomerun_debug_message_t *message = (struct hdhomerun_debug_message_t *)malloc(sizeof(struct hdhomerun_debug_message_t));
273 if (!message) {
274 return;
275 }
276
277 message->next = NULL;
278
279 char *ptr = message->buffer;
280 char *end = message->buffer + sizeof(message->buffer) - 2;
281 *end = 0;
282
283 /*
284 * Timestamp.
285 */
286 time_t current_time = time(NULL);
287 ptr += strftime(ptr, end - ptr, "%Y%m%d-%H:%M:%S ", localtime(¤t_time));
288 if (ptr > end) {
289 ptr = end;
290 }
291
292 /*
293 * Debug prefix.
294 */
295 thread_mutex_lock(&dbg->print_lock);
296
297 if (dbg->prefix) {
298 hdhomerun_sprintf(ptr, end, "%s ", dbg->prefix);
299 ptr = strchr(ptr, 0);
300 }
301
302 thread_mutex_unlock(&dbg->print_lock);
303
304 /*
305 * Message text.
306 */
307 hdhomerun_vsprintf(ptr, end, fmt, args);
308 ptr = strchr(ptr, 0);
309
310 /*
311 * Force newline.
312 */
313 if (ptr[-1] != '\n') {
314 hdhomerun_sprintf(ptr, end, "\n");
315 }
316
317 /*
318 * Enqueue.
319 */
320 thread_mutex_lock(&dbg->queue_lock);
321
322 if (dbg->queue_tail) {
323 dbg->queue_tail->next = message;
324 } else {
325 dbg->queue_head = message;
326 }
327 dbg->queue_tail = message;
328 dbg->queue_depth++;
329
330 bool signal_thread = dbg->enabled || (dbg->queue_depth > 1024 + 100);
331
332 thread_mutex_unlock(&dbg->queue_lock);
333
334 if (signal_thread) {
335 thread_cond_signal(&dbg->queue_cond);
336 }
337 }
338
339 /* Send lock held by caller */
hdhomerun_debug_output_message_file(struct hdhomerun_debug_t * dbg,struct hdhomerun_debug_message_t * message)340 static bool hdhomerun_debug_output_message_file(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message)
341 {
342 if (!dbg->file_fp) {
343 uint64_t current_time = getcurrenttime();
344 if (current_time < dbg->connect_delay) {
345 return false;
346 }
347 dbg->connect_delay = current_time + 30*1000;
348
349 dbg->file_fp = fopen(dbg->file_name, "a");
350 if (!dbg->file_fp) {
351 return false;
352 }
353 }
354
355 fprintf(dbg->file_fp, "%s", message->buffer);
356 fflush(dbg->file_fp);
357
358 return true;
359 }
360
361 /* Send lock held by caller */
hdhomerun_debug_output_message_sock(struct hdhomerun_debug_t * dbg,struct hdhomerun_debug_message_t * message)362 static bool hdhomerun_debug_output_message_sock(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message)
363 {
364 if (!dbg->sock) {
365 uint64_t current_time = getcurrenttime();
366 if (current_time < dbg->connect_delay) {
367 return false;
368 }
369 dbg->connect_delay = current_time + HDHOMERUN_DEBUG_CONNECT_RETRY_TIME;
370
371 dbg->sock = hdhomerun_sock_create_tcp();
372 if (!dbg->sock) {
373 return false;
374 }
375
376 uint32_t remote_addr = hdhomerun_sock_getaddrinfo_addr(dbg->sock, HDHOMERUN_DEBUG_HOST);
377 if (remote_addr == 0) {
378 hdhomerun_debug_close_internal(dbg);
379 return false;
380 }
381
382 if (!hdhomerun_sock_connect(dbg->sock, remote_addr, HDHOMERUN_DEBUG_PORT, HDHOMERUN_DEBUG_CONNECT_TIMEOUT)) {
383 hdhomerun_debug_close_internal(dbg);
384 return false;
385 }
386 }
387
388 size_t length = strlen(message->buffer);
389 if (!hdhomerun_sock_send(dbg->sock, message->buffer, length, HDHOMERUN_DEBUG_SEND_TIMEOUT)) {
390 hdhomerun_debug_close_internal(dbg);
391 return false;
392 }
393
394 return true;
395 }
396
hdhomerun_debug_output_message(struct hdhomerun_debug_t * dbg,struct hdhomerun_debug_message_t * message)397 static bool hdhomerun_debug_output_message(struct hdhomerun_debug_t *dbg, struct hdhomerun_debug_message_t *message)
398 {
399 thread_mutex_lock(&dbg->send_lock);
400
401 bool ret;
402 if (dbg->file_name) {
403 ret = hdhomerun_debug_output_message_file(dbg, message);
404 } else {
405 ret = hdhomerun_debug_output_message_sock(dbg, message);
406 }
407
408 thread_mutex_unlock(&dbg->send_lock);
409 return ret;
410 }
411
hdhomerun_debug_pop_and_free_message(struct hdhomerun_debug_t * dbg)412 static void hdhomerun_debug_pop_and_free_message(struct hdhomerun_debug_t *dbg)
413 {
414 thread_mutex_lock(&dbg->queue_lock);
415
416 struct hdhomerun_debug_message_t *message = dbg->queue_head;
417 dbg->queue_head = message->next;
418 if (!dbg->queue_head) {
419 dbg->queue_tail = NULL;
420 }
421 dbg->queue_depth--;
422
423 thread_mutex_unlock(&dbg->queue_lock);
424
425 free(message);
426 }
427
hdhomerun_debug_thread_execute(void * arg)428 static void hdhomerun_debug_thread_execute(void *arg)
429 {
430 struct hdhomerun_debug_t *dbg = (struct hdhomerun_debug_t *)arg;
431
432 while (!dbg->terminate) {
433 thread_mutex_lock(&dbg->queue_lock);
434 struct hdhomerun_debug_message_t *message = dbg->queue_head;
435 uint32_t queue_depth = dbg->queue_depth;
436 thread_mutex_unlock(&dbg->queue_lock);
437
438 if (!message) {
439 thread_cond_wait(&dbg->queue_cond);
440 continue;
441 }
442
443 if (queue_depth > 1024) {
444 hdhomerun_debug_pop_and_free_message(dbg);
445 continue;
446 }
447
448 if (!dbg->enabled) {
449 thread_cond_wait(&dbg->queue_cond);
450 continue;
451 }
452
453 if (!hdhomerun_debug_output_message(dbg, message)) {
454 msleep_approx(1000);
455 continue;
456 }
457
458 hdhomerun_debug_pop_and_free_message(dbg);
459 }
460 }
461