1 /*
2  * This file Copyright (C) 2010-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #include <errno.h>
10 #include <stdio.h>
11 
12 #include <event2/buffer.h>
13 
14 #include "transmission.h"
15 #include "file.h"
16 #include "log.h"
17 #include "platform.h" /* tr_lock */
18 #include "tr-assert.h"
19 #include "utils.h"
20 
21 tr_log_level __tr_message_level = TR_LOG_ERROR;
22 
23 static bool myQueueEnabled = false;
24 static tr_log_message* myQueue = NULL;
25 static tr_log_message** myQueueTail = &myQueue;
26 static int myQueueLength = 0;
27 
28 #ifndef _WIN32
29 
30 /* make null versions of these win32 functions */
IsDebuggerPresent(void)31 static inline bool IsDebuggerPresent(void)
32 {
33     return false;
34 }
35 
OutputDebugStringA(void const * unused UNUSED)36 static inline void OutputDebugStringA(void const* unused UNUSED)
37 {
38 }
39 
40 #endif
41 
42 /***
43 ****
44 ***/
45 
tr_logGetLevel(void)46 tr_log_level tr_logGetLevel(void)
47 {
48     return __tr_message_level;
49 }
50 
51 /***
52 ****
53 ***/
54 
getMessageLock(void)55 static tr_lock* getMessageLock(void)
56 {
57     static tr_lock* l = NULL;
58 
59     if (l == NULL)
60     {
61         l = tr_lockNew();
62     }
63 
64     return l;
65 }
66 
tr_logGetFile(void)67 tr_sys_file_t tr_logGetFile(void)
68 {
69     static bool initialized = false;
70     static tr_sys_file_t file = TR_BAD_SYS_FILE;
71 
72     if (!initialized)
73     {
74         int const fd = tr_env_get_int("TR_DEBUG_FD", 0);
75 
76         switch (fd)
77         {
78         case 1:
79             file = tr_sys_file_get_std(TR_STD_SYS_FILE_OUT, NULL);
80             break;
81 
82         case 2:
83             file = tr_sys_file_get_std(TR_STD_SYS_FILE_ERR, NULL);
84             break;
85         }
86 
87         initialized = true;
88     }
89 
90     return file;
91 }
92 
tr_logSetLevel(tr_log_level level)93 void tr_logSetLevel(tr_log_level level)
94 {
95     __tr_message_level = level;
96 }
97 
tr_logSetQueueEnabled(bool isEnabled)98 void tr_logSetQueueEnabled(bool isEnabled)
99 {
100     myQueueEnabled = isEnabled;
101 }
102 
tr_logGetQueueEnabled(void)103 bool tr_logGetQueueEnabled(void)
104 {
105     return myQueueEnabled;
106 }
107 
tr_logGetQueue(void)108 tr_log_message* tr_logGetQueue(void)
109 {
110     tr_log_message* ret;
111     tr_lockLock(getMessageLock());
112 
113     ret = myQueue;
114     myQueue = NULL;
115     myQueueTail = &myQueue;
116     myQueueLength = 0;
117 
118     tr_lockUnlock(getMessageLock());
119     return ret;
120 }
121 
tr_logFreeQueue(tr_log_message * list)122 void tr_logFreeQueue(tr_log_message* list)
123 {
124     tr_log_message* next;
125 
126     while (list != NULL)
127     {
128         next = list->next;
129         tr_free(list->message);
130         tr_free(list->name);
131         tr_free(list);
132         list = next;
133     }
134 }
135 
136 /**
137 ***
138 **/
139 
tr_logGetTimeStr(char * buf,size_t buflen)140 char* tr_logGetTimeStr(char* buf, size_t buflen)
141 {
142     char tmp[64];
143     struct tm now_tm;
144     struct timeval tv;
145     time_t seconds;
146     int milliseconds;
147 
148     tr_gettimeofday(&tv);
149 
150     seconds = tv.tv_sec;
151     tr_localtime_r(&seconds, &now_tm);
152     strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S.%%03d", &now_tm);
153     milliseconds = tv.tv_usec / 1000;
154     tr_snprintf(buf, buflen, tmp, milliseconds);
155 
156     return buf;
157 }
158 
tr_logGetDeepEnabled(void)159 bool tr_logGetDeepEnabled(void)
160 {
161     static int8_t deepLoggingIsActive = -1;
162 
163     if (deepLoggingIsActive < 0)
164     {
165         deepLoggingIsActive = (int8_t)(IsDebuggerPresent() || tr_logGetFile() != TR_BAD_SYS_FILE);
166     }
167 
168     return deepLoggingIsActive != 0;
169 }
170 
tr_logAddDeep(char const * file,int line,char const * name,char const * fmt,...)171 void tr_logAddDeep(char const* file, int line, char const* name, char const* fmt, ...)
172 {
173     tr_sys_file_t const fp = tr_logGetFile();
174 
175     if (fp != TR_BAD_SYS_FILE || IsDebuggerPresent())
176     {
177         va_list args;
178         char timestr[64];
179         char* message;
180         size_t message_len;
181         struct evbuffer* buf = evbuffer_new();
182         char* base = tr_sys_path_basename(file, NULL);
183 
184         evbuffer_add_printf(buf, "[%s] ", tr_logGetTimeStr(timestr, sizeof(timestr)));
185 
186         if (name != NULL)
187         {
188             evbuffer_add_printf(buf, "%s ", name);
189         }
190 
191         va_start(args, fmt);
192         evbuffer_add_vprintf(buf, fmt, args);
193         va_end(args);
194         evbuffer_add_printf(buf, " (%s:%d)" TR_NATIVE_EOL_STR, base, line);
195         /* FIXME (libevent2) ifdef this out for nonwindows platforms */
196         message = evbuffer_free_to_str(buf, &message_len);
197         OutputDebugStringA(message);
198 
199         if (fp != TR_BAD_SYS_FILE)
200         {
201             tr_sys_file_write(fp, message, message_len, NULL, NULL);
202         }
203 
204         tr_free(message);
205         tr_free(base);
206     }
207 }
208 
209 /***
210 ****
211 ***/
212 
tr_logAddMessage(char const * file,int line,tr_log_level level,char const * name,char const * fmt,...)213 void tr_logAddMessage(char const* file, int line, tr_log_level level, char const* name, char const* fmt, ...)
214 {
215     int const err = errno; /* message logging shouldn't affect errno */
216     char buf[1024];
217     int buf_len;
218     va_list ap;
219     tr_lockLock(getMessageLock());
220 
221     /* build the text message */
222     *buf = '\0';
223     va_start(ap, fmt);
224     buf_len = evutil_vsnprintf(buf, sizeof(buf), fmt, ap);
225     va_end(ap);
226 
227     if (buf_len < 0)
228     {
229         goto finish;
230     }
231 
232 #ifdef _WIN32
233 
234     if ((size_t)buf_len < sizeof(buf) - 3)
235     {
236         buf[buf_len + 0] = '\r';
237         buf[buf_len + 1] = '\n';
238         buf[buf_len + 2] = '\0';
239         OutputDebugStringA(buf);
240         buf[buf_len + 0] = '\0';
241     }
242     else
243     {
244         OutputDebugStringA(buf);
245     }
246 
247 #endif
248 
249     if (!tr_str_is_empty(buf))
250     {
251         if (tr_logGetQueueEnabled())
252         {
253             tr_log_message* newmsg;
254             newmsg = tr_new0(tr_log_message, 1);
255             newmsg->level = level;
256             newmsg->when = tr_time();
257             newmsg->message = tr_strdup(buf);
258             newmsg->file = file;
259             newmsg->line = line;
260             newmsg->name = tr_strdup(name);
261 
262             *myQueueTail = newmsg;
263             myQueueTail = &newmsg->next;
264             ++myQueueLength;
265 
266             if (myQueueLength > TR_LOG_MAX_QUEUE_LENGTH)
267             {
268                 tr_log_message* old = myQueue;
269                 myQueue = old->next;
270                 old->next = NULL;
271                 tr_logFreeQueue(old);
272                 --myQueueLength;
273                 TR_ASSERT(myQueueLength == TR_LOG_MAX_QUEUE_LENGTH);
274             }
275         }
276         else
277         {
278             tr_sys_file_t fp;
279             char timestr[64];
280 
281             fp = tr_logGetFile();
282 
283             if (fp == TR_BAD_SYS_FILE)
284             {
285                 fp = tr_sys_file_get_std(TR_STD_SYS_FILE_ERR, NULL);
286             }
287 
288             tr_logGetTimeStr(timestr, sizeof(timestr));
289 
290             if (name != NULL)
291             {
292                 tr_sys_file_write_fmt(fp, "[%s] %s: %s" TR_NATIVE_EOL_STR, NULL, timestr, name, buf);
293             }
294             else
295             {
296                 tr_sys_file_write_fmt(fp, "[%s] %s" TR_NATIVE_EOL_STR, NULL, timestr, buf);
297             }
298 
299             tr_sys_file_flush(fp, NULL);
300         }
301     }
302 
303 finish:
304     tr_lockUnlock(getMessageLock());
305     errno = err;
306 }
307