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(&current_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