Download
jcs
/subtext
/logger.c
(View History)
jcs logger: syslog doesn't need the hostname on the wire after all | Latest amendment: 586 on 2024-02-13 |
1 | /* |
2 | * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdio.h> |
18 | #include <stdarg.h> |
19 | #include <string.h> |
20 | #include "focusable.h" |
21 | #include "logger.h" |
22 | #include "subtext.h" |
23 | #include "tcp.h" |
24 | #include "user.h" |
25 | #include "util.h" |
26 | |
27 | struct logger *logger = NULL; |
28 | |
29 | static UDPiopb syslog_pb; |
30 | static StreamPtr syslog_stream = 0; |
31 | static char *syslog_rcv_buf = NULL; |
32 | #define SYSLOG_RCV_BUF_SIZE 2048 |
33 | static wdsEntry syslog_wds[2]; |
34 | static char *syslog_send_buf = NULL; |
35 | #define SYSLOG_SEND_BUF_SIZE 1024 |
36 | |
37 | void logger_layout(Rect *init_bounds); |
38 | void logger_key_down(struct focusable *focusable, EventRecord *event); |
39 | void logger_mouse_down(struct focusable *focusable, EventRecord *event); |
40 | bool logger_menu(struct focusable *focusable, short menu, short item); |
41 | void logger_resize(struct focusable *focusable, EventRecord *event); |
42 | void logger_update(struct focusable *focusable, EventRecord *event); |
43 | void logger_resume(struct focusable *focusable, EventRecord *event); |
44 | bool logger_quit(struct focusable *focusable); |
45 | void logger_flush_buffer(bool force); |
46 | |
47 | void syslog_vprintf(const char *format, va_list ap); |
48 | |
49 | void |
50 | logger_init(void) |
51 | { |
52 | struct focusable *focusable; |
53 | Rect bounds = { 0 }; |
54 | short padding = 5; |
55 | |
56 | logger = xmalloczero(sizeof(struct logger)); |
57 | if (!logger) |
58 | panic("Can't allocate logger"); |
59 | logger->buffered_logs = xmalloc(LOGGER_BUFFERED_LOG_SIZE); |
60 | if (!logger->buffered_logs) |
61 | panic("Can't allocate logger buffer"); |
62 | logger->buffered_logs[0] = '\0'; |
63 | logger->autoflush = true; |
64 | |
65 | bounds.left = padding; |
66 | bounds.top = ((screenBits.bounds.bottom - |
67 | screenBits.bounds.top) / 3); |
68 | bounds.right = screenBits.bounds.right - padding - 1; |
69 | bounds.bottom = screenBits.bounds.bottom - padding - 1; |
70 | |
71 | logger->win = NewWindow(0L, &bounds, "\p", false, |
72 | documentProc, (WindowPtr)-1L, false, 0); |
73 | if (!logger->win) |
74 | panic("Can't create logger window"); |
75 | logger_update_title(); |
76 | |
77 | SetPort(logger->win); |
78 | |
79 | TextFont(LOGGER_FONT); |
80 | TextSize(LOGGER_FONT_SIZE); |
81 | |
82 | bounds.right -= bounds.left; |
83 | bounds.bottom -= bounds.top; |
84 | bounds.top = bounds.left = 0; |
85 | logger_layout(&bounds); |
86 | |
87 | focusable = xmalloczero(sizeof(struct focusable)); |
88 | if (!focusable) |
89 | panic("Can't create focusable"); |
90 | focusable->win = logger->win; |
91 | focusable->cookie = logger; |
92 | focusable->mouse_down = logger_mouse_down; |
93 | focusable->menu = logger_menu; |
94 | focusable->resize = logger_resize; |
95 | focusable->update = logger_update; |
96 | focusable->quit = logger_quit; |
97 | focusable->resume = logger_resume; |
98 | if (!add_focusable(focusable)) |
99 | panic("Can't add focusable"); |
100 | logger->focusable = focusable; |
101 | |
102 | DrawControls(logger->win); |
103 | DrawGrowIconOnly(logger->win); |
104 | } |
105 | |
106 | void |
107 | logger_layout(Rect *init_bounds) |
108 | { |
109 | Rect bounds, inset_bounds, win_bounds; |
110 | bool init = (init_bounds != NULL); |
111 | |
112 | if (init) |
113 | win_bounds = *init_bounds; |
114 | else |
115 | win_bounds = logger->win->portRect; |
116 | |
117 | /* messages scrollbar */ |
118 | bounds.top = -1; |
119 | bounds.right = win_bounds.right - win_bounds.left + 1; |
120 | bounds.bottom = win_bounds.bottom - win_bounds.top - 14; |
121 | bounds.left = bounds.right - SCROLLBAR_WIDTH; |
122 | if (init) { |
123 | logger->messages_scroller = NewControl(logger->win, &bounds, |
124 | "\p", true, 1, 1, 1, scrollBarProc, 0L); |
125 | if (!logger->messages_scroller) |
126 | panic("Can't create messages scroller"); |
127 | } else |
128 | (*(logger->messages_scroller))->contrlRect = bounds; |
129 | |
130 | /* messages */ |
131 | bounds.right = (*(logger->messages_scroller))->contrlRect.left; |
132 | bounds.left = 0; |
133 | bounds.top = 0; |
134 | bounds.bottom = win_bounds.bottom - win_bounds.top; |
135 | if (init) { |
136 | inset_bounds = bounds; |
137 | InsetRect(&inset_bounds, 4, 4); |
138 | logger->messages_te = TEStylNew(&inset_bounds, &bounds); |
139 | if (logger->messages_te == NULL) |
140 | panic("Can't create logger TE"); |
141 | (*(logger->messages_te))->caretHook = NullCaretHook; |
142 | TEActivate(logger->messages_te); |
143 | } else { |
144 | (*(logger->messages_te))->viewRect = bounds; |
145 | InsetRect(&bounds, 4, 4); |
146 | (*(logger->messages_te))->destRect = bounds; |
147 | TECalText(logger->messages_te); |
148 | TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te); |
149 | } |
150 | |
151 | DrawControls(logger->win); |
152 | DrawGrowIconOnly(logger->win); |
153 | |
154 | if (!init) |
155 | logger_flush_buffer(true); |
156 | } |
157 | |
158 | void |
159 | logger_resume(struct focusable *focusable, EventRecord *event) |
160 | { |
161 | show_focusable(focusable); |
162 | InvalRect(logger->win->visRgn); |
163 | } |
164 | |
165 | bool |
166 | logger_quit(struct focusable *focusable) |
167 | { |
168 | destroy_focusable(focusable); |
169 | xfree(&logger->buffered_logs); |
170 | xfree(&logger); |
171 | |
172 | return true; |
173 | } |
174 | |
175 | void |
176 | logger_update(struct focusable *focusable, EventRecord *event) |
177 | { |
178 | GrafPtr old_port; |
179 | Rect r; |
180 | short what = -1; |
181 | |
182 | GetPort(&old_port); |
183 | SetPort(logger->win); |
184 | |
185 | if (event != NULL) |
186 | what = event->what; |
187 | |
188 | switch (what) { |
189 | case -1: |
190 | case updateEvt: |
191 | TextFont(applFont); |
192 | TextSize(10); |
193 | |
194 | EraseRect(&logger->win->portRect); |
195 | |
196 | r = (*(logger->messages_te))->viewRect; |
197 | InsetRect(&r, -1, -1); |
198 | FrameRect(&r); |
199 | |
200 | TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te); |
201 | DrawControls(logger->win); |
202 | DrawGrowIconOnly(logger->win); |
203 | break; |
204 | case activateEvt: |
205 | if (event->modifiers & activeFlag) { |
206 | TEActivate(logger->messages_te); |
207 | } else { |
208 | TEDeactivate(logger->messages_te); |
209 | } |
210 | break; |
211 | } |
212 | |
213 | SetPort(old_port); |
214 | } |
215 | |
216 | void |
217 | logger_update_title(void) |
218 | { |
219 | static char logger_title[64]; |
220 | short n, uprinted = 0; |
221 | |
222 | snprintf(logger_title, sizeof(logger_title), |
223 | "%s | %luKB Free | %lu Call%s | ", db->config.name, |
224 | (FreeMem() / 1024), |
225 | session_today_tally.calls, session_today_tally.calls == 1 ? "" : "s"); |
226 | |
227 | for (n = 0; n < MAX_SESSIONS; n++) { |
228 | if (sessions[n] == NULL || !sessions[n]->logged_in) |
229 | continue; |
230 | |
231 | if (uprinted++) |
232 | strlcat(logger_title, ", ", sizeof(logger_title)); |
233 | else |
234 | strlcat(logger_title, "Logged in: ", sizeof(logger_title)); |
235 | |
236 | strlcat(logger_title, |
237 | sessions[n]->user ? sessions[n]->user->username : GUEST_USERNAME, |
238 | sizeof(logger_title)); |
239 | } |
240 | |
241 | if (uprinted == 0) |
242 | strlcat(logger_title, "0 connected", sizeof(logger_title)); |
243 | |
244 | SetWTitle(logger->win, CtoPstr(logger_title)); |
245 | } |
246 | |
247 | void |
248 | logger_mouse_down(struct focusable *focusable, EventRecord *event) |
249 | { |
250 | Point p; |
251 | ControlHandle control; |
252 | Rect r; |
253 | int val, adj; |
254 | |
255 | p = event->where; |
256 | GlobalToLocal(&p); |
257 | |
258 | r = (*(logger->messages_te))->viewRect; |
259 | if (PtInRect(p, &r)) { |
260 | TEClick(p, ((event->modifiers & shiftKey) != 0), |
261 | logger->messages_te); |
262 | return; |
263 | } |
264 | |
265 | switch (FindControl(p, logger->win, &control)) { |
266 | case inUpButton: |
267 | case inDownButton: |
268 | case inPageUp: |
269 | case inPageDown: |
270 | if (control != logger->messages_scroller) |
271 | break; |
272 | SetTrackControlTE(logger->messages_te); |
273 | TrackControl(control, p, TrackMouseDownInControl); |
274 | break; |
275 | case inThumb: |
276 | val = GetCtlValue(control); |
277 | if (TrackControl(control, p, 0L) == 0) |
278 | break; |
279 | adj = val - GetCtlValue(control); |
280 | if (adj != 0) { |
281 | val -= adj; |
282 | if (control == logger->messages_scroller) |
283 | TEScroll(0, adj * TEGetHeight(0, 0, logger->messages_te), |
284 | logger->messages_te); |
285 | SetCtlValue(control, val); |
286 | } |
287 | break; |
288 | } |
289 | } |
290 | |
291 | bool |
292 | logger_menu(struct focusable *focusable, short menu, short item) |
293 | { |
294 | switch (menu) { |
295 | case EDIT_MENU_ID: |
296 | switch (item) { |
297 | case EDIT_MENU_COPY_ID: |
298 | HLock(logger->messages_te); |
299 | if ((*(logger->messages_te))->selStart == |
300 | (*(logger->messages_te))->selEnd) |
301 | SysBeep(10); |
302 | else |
303 | TECopy(logger->messages_te); |
304 | HUnlock(logger->messages_te); |
305 | break; |
306 | default: |
307 | SysBeep(10); |
308 | break; |
309 | } |
310 | return true; |
311 | } |
312 | |
313 | return false; |
314 | } |
315 | |
316 | void |
317 | logger_resize(struct focusable *focusable, EventRecord *event) |
318 | { |
319 | Rect bounds; |
320 | long newsize, width, height; |
321 | |
322 | bounds.left = 100; |
323 | bounds.top = 100; |
324 | bounds.right = screenBits.bounds.right; |
325 | bounds.bottom = screenBits.bounds.bottom; |
326 | |
327 | newsize = GrowWindow(focusable->win, event->where, &bounds); |
328 | |
329 | height = HiWord(newsize); |
330 | width = LoWord(newsize); |
331 | SizeWindow(focusable->win, width, height, true); |
332 | logger_layout(NULL); |
333 | } |
334 | |
335 | size_t |
336 | logger_printf(const char *format, ...) |
337 | { |
338 | va_list va; |
339 | size_t len; |
340 | |
341 | if (!logger) |
342 | return 0; |
343 | |
344 | va_start(va, format); |
345 | len = logger_vprintf(format, va); |
346 | va_end(va); |
347 | |
348 | if (db->config.syslog_ip) { |
349 | va_start(va, format); |
350 | syslog_vprintf(format, va); |
351 | va_end(va); |
352 | } |
353 | |
354 | return len; |
355 | } |
356 | |
357 | size_t |
358 | logger_vprintf(const char *format, va_list ap) |
359 | { |
360 | static char buf[600]; |
361 | ssize_t len; |
362 | time_t now = Time; |
363 | |
364 | if (!logger) |
365 | return 0; |
366 | |
367 | blanker_unblank(); |
368 | |
369 | len = strftime(buf, sizeof(buf), "\r[%H:%M:%S] ", localtime(&now)); |
370 | len += vsnprintf(buf + len, sizeof(buf) - len, format, ap); |
371 | if (len > sizeof(buf)) { |
372 | buf[sizeof(buf) - 1] = ']'; |
373 | buf[sizeof(buf) - 2] = '…'; |
374 | buf[sizeof(buf) - 3] = '['; |
375 | len = sizeof(buf); |
376 | } |
377 | |
378 | while (buf[len - 1] == '\r') { |
379 | buf[len - 1] = '\0'; |
380 | len--; |
381 | } |
382 | |
383 | if (logger->buffered_logs_len + len > LOGGER_BUFFERED_LOG_SIZE) |
384 | logger_flush_buffer(false); |
385 | |
386 | len = strlcat(logger->buffered_logs, buf, LOGGER_BUFFERED_LOG_SIZE); |
387 | logger->buffered_logs_len = MIN(len, LOGGER_BUFFERED_LOG_SIZE); |
388 | |
389 | if (logger->autoflush) |
390 | logger_flush_buffer(false); |
391 | |
392 | return len; |
393 | } |
394 | |
395 | void |
396 | logger_flush_buffer(bool force) |
397 | { |
398 | RgnHandle savergn; |
399 | Rect zerorect = { 0, 0, 0, 0 }; |
400 | GrafPtr old_port; |
401 | short line_height = 0, new_lines = 1, n; |
402 | char *buf; |
403 | |
404 | if (!logger || (logger->buffered_logs_len == 0 && !force)) |
405 | return; |
406 | |
407 | for (n = 0; n < logger->buffered_logs_len; n++) { |
408 | if (logger->buffered_logs[n] == '\r') |
409 | new_lines++; |
410 | } |
411 | |
412 | line_height = LOGGER_FONT_SIZE + 3; |
413 | |
414 | GetPort(&old_port); |
415 | SetPort(logger->win); |
416 | |
417 | /* check for TE overflow */ |
418 | |
419 | HLock(logger->messages_te); |
420 | |
421 | /* too many lines */ |
422 | if ((unsigned long)(*(logger->messages_te))->nLines >= |
423 | (nitems((*(logger->messages_te))->lineStarts) - new_lines)) |
424 | goto te_overflow; |
425 | |
426 | /* too many characters */ |
427 | if ((unsigned long)(*(logger->messages_te))->teLength >= |
428 | (SHRT_MAX - logger->buffered_logs_len)) |
429 | goto te_overflow; |
430 | |
431 | /* destRect of all lines is too tall */ |
432 | if ((unsigned long)(*(logger->messages_te))->nLines * line_height >= |
433 | (SHRT_MAX - new_lines)) |
434 | goto te_overflow; |
435 | |
436 | goto no_overflow; |
437 | |
438 | te_overflow: |
439 | savergn = NewRgn(); |
440 | GetClip(savergn); |
441 | /* create an empty clip region so all TE updates are hidden */ |
442 | ClipRect(&zerorect); |
443 | |
444 | /* select some lines at the start, delete them */ |
445 | TESetSelect(0, (*(logger->messages_te))->lineStarts[new_lines * 2], |
446 | logger->messages_te); |
447 | TEDelete(logger->messages_te); |
448 | |
449 | /* scroll up, causing a repaint */ |
450 | TEPinScroll(0, SHRT_MAX, logger->messages_te); |
451 | |
452 | /* then scroll back down to what it looked like before we did anything */ |
453 | TEPinScroll(0, -SHRT_MAX, logger->messages_te); |
454 | |
455 | /* resume normal drawing */ |
456 | SetClip(savergn); |
457 | DisposeRgn(savergn); |
458 | |
459 | no_overflow: |
460 | TESetSelect(SHRT_MAX, SHRT_MAX, logger->messages_te); |
461 | |
462 | buf = logger->buffered_logs; |
463 | if ((*(logger->messages_te))->teLength == 0) { |
464 | /* skip leading \r */ |
465 | logger->buffered_logs_len--; |
466 | buf++; |
467 | } |
468 | |
469 | TEInsert(buf, logger->buffered_logs_len, logger->messages_te); |
470 | |
471 | if ((unsigned long)(*(logger->messages_te))->nLines >= |
472 | nitems((*(logger->messages_te))->lineStarts)) |
473 | warn("te overflow!"); |
474 | |
475 | TEPinScroll(0, -SHRT_MAX, logger->messages_te); |
476 | SetCtlValue(logger->messages_scroller, |
477 | GetCtlMax(logger->messages_scroller)); |
478 | UpdateScrollbarForTE(logger->win, logger->messages_scroller, |
479 | logger->messages_te, false); |
480 | HUnlock(logger->messages_te); |
481 | |
482 | SetPort(old_port); |
483 | |
484 | logger->buffered_logs_len = 0; |
485 | logger->buffered_logs[0] = '\0'; |
486 | } |
487 | |
488 | void |
489 | syslog_init(void) |
490 | { |
491 | short error; |
492 | |
493 | if (!db->config.syslog_ip) |
494 | return; |
495 | |
496 | if (_TCPInit() != noErr) |
497 | panic("Failed initializing MacTCP"); |
498 | |
499 | syslog_rcv_buf = xmalloc(SYSLOG_RCV_BUF_SIZE); |
500 | if (syslog_rcv_buf == NULL) |
501 | panic("Failed allocating syslog buf"); |
502 | error = _UDPCreate(&syslog_pb, &syslog_stream, |
503 | (Ptr)syslog_rcv_buf, SYSLOG_RCV_BUF_SIZE, NULL, NULL, |
504 | NULL, false); |
505 | if (error) |
506 | panic("UDPCreate failed: %d", error); |
507 | |
508 | syslog_send_buf = xmalloc(SYSLOG_SEND_BUF_SIZE); |
509 | if (syslog_send_buf == NULL) |
510 | panic("Failed allocating syslog send buf"); |
511 | } |
512 | |
513 | void |
514 | syslog_deinit(void) |
515 | { |
516 | if (syslog_stream) { |
517 | _UDPRelease(&syslog_pb, syslog_stream, NULL, NULL, false); |
518 | syslog_stream = 0; |
519 | } |
520 | |
521 | if (syslog_rcv_buf) |
522 | xfree(&syslog_rcv_buf); |
523 | if (syslog_send_buf) |
524 | xfree(&syslog_send_buf); |
525 | } |
526 | |
527 | bool |
528 | syslog_reinit(void) |
529 | { |
530 | syslog_deinit(); |
531 | syslog_init(); |
532 | } |
533 | |
534 | /* RFC3164 */ |
535 | void |
536 | syslog_vprintf(const char *format, va_list ap) |
537 | { |
538 | size_t len; |
539 | time_t now = Time; |
540 | |
541 | if (!db->config.syslog_ip || syslog_send_buf == NULL) |
542 | return; |
543 | |
544 | /* system (3) priority, notice (5) severity */ |
545 | len = strftime(syslog_send_buf, SYSLOG_SEND_BUF_SIZE, |
546 | "<29>%b %d %H:%M:%S subtext: ", localtime(&now)); |
547 | len += vsnprintf(syslog_send_buf + len, SYSLOG_SEND_BUF_SIZE - len, |
548 | format, ap); |
549 | |
550 | syslog_wds[0].ptr = (Ptr)syslog_send_buf; |
551 | syslog_wds[0].length = len; |
552 | syslog_wds[1].ptr = 0; |
553 | syslog_wds[1].length = 0; |
554 | |
555 | _UDPSend(&syslog_pb, syslog_stream, syslog_wds, |
556 | db->config.syslog_ip, 514, NULL, NULL, false); |
557 | } |