/* * Copyright (c) 2021-2022 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "focusable.h" #include "logger.h" #include "subtext.h" #include "tcp.h" #include "user.h" #include "util.h" struct logger *logger = NULL; static UDPiopb syslog_pb; static StreamPtr syslog_stream = 0; static char *syslog_rcv_buf = NULL; #define SYSLOG_RCV_BUF_SIZE 2048 static wdsEntry syslog_wds[2]; static char *syslog_send_buf = NULL; #define SYSLOG_SEND_BUF_SIZE 1024 void logger_layout(Rect *init_bounds); void logger_key_down(struct focusable *focusable, EventRecord *event); void logger_mouse_down(struct focusable *focusable, EventRecord *event); bool logger_menu(struct focusable *focusable, short menu, short item); void logger_resize(struct focusable *focusable, EventRecord *event); void logger_update(struct focusable *focusable, EventRecord *event); void logger_resume(struct focusable *focusable, EventRecord *event); bool logger_quit(struct focusable *focusable); void logger_flush_buffer(bool force); void syslog_vprintf(const char *format, va_list ap); void logger_init(void) { struct focusable *focusable; Rect bounds = { 0 }; short padding = 5; logger = xmalloczero(sizeof(struct logger)); if (!logger) panic("Can't allocate logger"); logger->buffered_logs = xmalloc(LOGGER_BUFFERED_LOG_SIZE); if (!logger->buffered_logs) panic("Can't allocate logger buffer"); logger->buffered_logs[0] = '\0'; logger->autoflush = true; bounds.left = padding; bounds.top = ((screenBits.bounds.bottom - screenBits.bounds.top) / 3); bounds.right = screenBits.bounds.right - padding - 1; bounds.bottom = screenBits.bounds.bottom - padding - 1; logger->win = NewWindow(0L, &bounds, "\p", false, documentProc, (WindowPtr)-1L, false, 0); if (!logger->win) panic("Can't create logger window"); logger_update_title(); SetPort(logger->win); TextFont(LOGGER_FONT); TextSize(LOGGER_FONT_SIZE); bounds.right -= bounds.left; bounds.bottom -= bounds.top; bounds.top = bounds.left = 0; logger_layout(&bounds); focusable = xmalloczero(sizeof(struct focusable)); if (!focusable) panic("Can't create focusable"); focusable->win = logger->win; focusable->cookie = logger; focusable->mouse_down = logger_mouse_down; focusable->menu = logger_menu; focusable->resize = logger_resize; focusable->update = logger_update; focusable->quit = logger_quit; focusable->resume = logger_resume; if (!add_focusable(focusable)) panic("Can't add focusable"); logger->focusable = focusable; DrawControls(logger->win); DrawGrowIconOnly(logger->win); } void logger_layout(Rect *init_bounds) { Rect bounds, inset_bounds, win_bounds; bool init = (init_bounds != NULL); if (init) win_bounds = *init_bounds; else win_bounds = logger->win->portRect; /* messages scrollbar */ bounds.top = -1; bounds.right = win_bounds.right - win_bounds.left + 1; bounds.bottom = win_bounds.bottom - win_bounds.top - 14; bounds.left = bounds.right - SCROLLBAR_WIDTH; if (init) { logger->messages_scroller = NewControl(logger->win, &bounds, "\p", true, 1, 1, 1, scrollBarProc, 0L); if (!logger->messages_scroller) panic("Can't create messages scroller"); } else (*(logger->messages_scroller))->contrlRect = bounds; /* messages */ bounds.right = (*(logger->messages_scroller))->contrlRect.left; bounds.left = 0; bounds.top = 0; bounds.bottom = win_bounds.bottom - win_bounds.top; if (init) { inset_bounds = bounds; InsetRect(&inset_bounds, 4, 4); logger->messages_te = TEStylNew(&inset_bounds, &bounds); if (logger->messages_te == NULL) panic("Can't create logger TE"); (*(logger->messages_te))->caretHook = NullCaretHook; TEActivate(logger->messages_te); } else { (*(logger->messages_te))->viewRect = bounds; InsetRect(&bounds, 4, 4); (*(logger->messages_te))->destRect = bounds; TECalText(logger->messages_te); TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te); } DrawControls(logger->win); DrawGrowIconOnly(logger->win); if (!init) logger_flush_buffer(true); } void logger_resume(struct focusable *focusable, EventRecord *event) { show_focusable(focusable); InvalRect(logger->win->visRgn); } bool logger_quit(struct focusable *focusable) { destroy_focusable(focusable); xfree(&logger->buffered_logs); xfree(&logger); return true; } void logger_update(struct focusable *focusable, EventRecord *event) { GrafPtr old_port; Rect r; short what = -1; GetPort(&old_port); SetPort(logger->win); if (event != NULL) what = event->what; switch (what) { case -1: case updateEvt: TextFont(applFont); TextSize(10); EraseRect(&logger->win->portRect); r = (*(logger->messages_te))->viewRect; InsetRect(&r, -1, -1); FrameRect(&r); TEUpdate(&(*(logger->messages_te))->viewRect, logger->messages_te); DrawControls(logger->win); DrawGrowIconOnly(logger->win); break; case activateEvt: if (event->modifiers & activeFlag) { TEActivate(logger->messages_te); } else { TEDeactivate(logger->messages_te); } break; } SetPort(old_port); } void logger_update_title(void) { static char logger_title[64]; short n, uprinted = 0; snprintf(logger_title, sizeof(logger_title), "%s | %luKB Free | %lu Call%s | ", db->config.name, (FreeMem() / 1024), session_today_tally.calls, session_today_tally.calls == 1 ? "" : "s"); for (n = 0; n < MAX_SESSIONS; n++) { if (sessions[n] == NULL || !sessions[n]->logged_in) continue; if (uprinted++) strlcat(logger_title, ", ", sizeof(logger_title)); else strlcat(logger_title, "Logged in: ", sizeof(logger_title)); strlcat(logger_title, sessions[n]->user ? sessions[n]->user->username : GUEST_USERNAME, sizeof(logger_title)); } if (uprinted == 0) strlcat(logger_title, "0 connected", sizeof(logger_title)); SetWTitle(logger->win, CtoPstr(logger_title)); } void logger_mouse_down(struct focusable *focusable, EventRecord *event) { Point p; ControlHandle control; Rect r; int val, adj; p = event->where; GlobalToLocal(&p); r = (*(logger->messages_te))->viewRect; if (PtInRect(p, &r)) { TEClick(p, ((event->modifiers & shiftKey) != 0), logger->messages_te); return; } switch (FindControl(p, logger->win, &control)) { case inUpButton: case inDownButton: case inPageUp: case inPageDown: if (control != logger->messages_scroller) break; SetTrackControlTE(logger->messages_te); TrackControl(control, p, TrackMouseDownInControl); break; case inThumb: val = GetCtlValue(control); if (TrackControl(control, p, 0L) == 0) break; adj = val - GetCtlValue(control); if (adj != 0) { val -= adj; if (control == logger->messages_scroller) TEScroll(0, adj * TEGetHeight(0, 0, logger->messages_te), logger->messages_te); SetCtlValue(control, val); } break; } } bool logger_menu(struct focusable *focusable, short menu, short item) { switch (menu) { case EDIT_MENU_ID: switch (item) { case EDIT_MENU_COPY_ID: HLock(logger->messages_te); if ((*(logger->messages_te))->selStart == (*(logger->messages_te))->selEnd) SysBeep(10); else TECopy(logger->messages_te); HUnlock(logger->messages_te); break; default: SysBeep(10); break; } return true; } return false; } void logger_resize(struct focusable *focusable, EventRecord *event) { Rect bounds; long newsize, width, height; bounds.left = 100; bounds.top = 100; bounds.right = screenBits.bounds.right; bounds.bottom = screenBits.bounds.bottom; newsize = GrowWindow(focusable->win, event->where, &bounds); height = HiWord(newsize); width = LoWord(newsize); SizeWindow(focusable->win, width, height, true); logger_layout(NULL); } size_t logger_printf(const char *format, ...) { va_list va; size_t len; if (!logger) return 0; va_start(va, format); len = logger_vprintf(format, va); va_end(va); if (db->config.syslog_ip) { va_start(va, format); syslog_vprintf(format, va); va_end(va); } return len; } size_t logger_vprintf(const char *format, va_list ap) { static char buf[600]; ssize_t len; time_t now = Time; if (!logger) return 0; blanker_unblank(); len = strftime(buf, sizeof(buf), "\r[%H:%M:%S] ", localtime(&now)); len += vsnprintf(buf + len, sizeof(buf) - len, format, ap); if (len > sizeof(buf)) { buf[sizeof(buf) - 1] = ']'; buf[sizeof(buf) - 2] = 'É'; buf[sizeof(buf) - 3] = '['; len = sizeof(buf); } while (buf[len - 1] == '\r') { buf[len - 1] = '\0'; len--; } if (logger->buffered_logs_len + len > LOGGER_BUFFERED_LOG_SIZE) logger_flush_buffer(false); len = strlcat(logger->buffered_logs, buf, LOGGER_BUFFERED_LOG_SIZE); logger->buffered_logs_len = MIN(len, LOGGER_BUFFERED_LOG_SIZE); if (logger->autoflush) logger_flush_buffer(false); return len; } void logger_flush_buffer(bool force) { RgnHandle savergn; Rect zerorect = { 0, 0, 0, 0 }; GrafPtr old_port; short line_height = 0, new_lines = 1, n; char *buf; if (!logger || (logger->buffered_logs_len == 0 && !force)) return; for (n = 0; n < logger->buffered_logs_len; n++) { if (logger->buffered_logs[n] == '\r') new_lines++; } line_height = LOGGER_FONT_SIZE + 3; GetPort(&old_port); SetPort(logger->win); /* check for TE overflow */ HLock(logger->messages_te); /* too many lines */ if ((unsigned long)(*(logger->messages_te))->nLines >= (nitems((*(logger->messages_te))->lineStarts) - new_lines)) goto te_overflow; /* too many characters */ if ((unsigned long)(*(logger->messages_te))->teLength >= (SHRT_MAX - logger->buffered_logs_len)) goto te_overflow; /* destRect of all lines is too tall */ if ((unsigned long)(*(logger->messages_te))->nLines * line_height >= (SHRT_MAX - new_lines)) goto te_overflow; goto no_overflow; te_overflow: savergn = NewRgn(); GetClip(savergn); /* create an empty clip region so all TE updates are hidden */ ClipRect(&zerorect); /* select some lines at the start, delete them */ TESetSelect(0, (*(logger->messages_te))->lineStarts[new_lines * 2], logger->messages_te); TEDelete(logger->messages_te); /* scroll up, causing a repaint */ TEPinScroll(0, SHRT_MAX, logger->messages_te); /* then scroll back down to what it looked like before we did anything */ TEPinScroll(0, -SHRT_MAX, logger->messages_te); /* resume normal drawing */ SetClip(savergn); DisposeRgn(savergn); no_overflow: TESetSelect(SHRT_MAX, SHRT_MAX, logger->messages_te); buf = logger->buffered_logs; if ((*(logger->messages_te))->teLength == 0) { /* skip leading \r */ logger->buffered_logs_len--; buf++; } TEInsert(buf, logger->buffered_logs_len, logger->messages_te); if ((unsigned long)(*(logger->messages_te))->nLines >= nitems((*(logger->messages_te))->lineStarts)) warn("te overflow!"); TEPinScroll(0, -SHRT_MAX, logger->messages_te); SetCtlValue(logger->messages_scroller, GetCtlMax(logger->messages_scroller)); UpdateScrollbarForTE(logger->win, logger->messages_scroller, logger->messages_te, false); HUnlock(logger->messages_te); SetPort(old_port); logger->buffered_logs_len = 0; logger->buffered_logs[0] = '\0'; } void syslog_init(void) { short error; if (!db->config.syslog_ip) return; if (_TCPInit() != noErr) panic("Failed initializing MacTCP"); syslog_rcv_buf = xmalloc(SYSLOG_RCV_BUF_SIZE); if (syslog_rcv_buf == NULL) panic("Failed allocating syslog buf"); error = _UDPCreate(&syslog_pb, &syslog_stream, (Ptr)syslog_rcv_buf, SYSLOG_RCV_BUF_SIZE, NULL, NULL, NULL, false); if (error) panic("UDPCreate failed: %d", error); syslog_send_buf = xmalloc(SYSLOG_SEND_BUF_SIZE); if (syslog_send_buf == NULL) panic("Failed allocating syslog send buf"); } void syslog_deinit(void) { if (syslog_stream) { _UDPRelease(&syslog_pb, syslog_stream, NULL, NULL, false); syslog_stream = 0; } if (syslog_rcv_buf) xfree(&syslog_rcv_buf); if (syslog_send_buf) xfree(&syslog_send_buf); } bool syslog_reinit(void) { syslog_deinit(); syslog_init(); } /* RFC3164 */ void syslog_vprintf(const char *format, va_list ap) { size_t len; time_t now = Time; if (!db->config.syslog_ip || syslog_send_buf == NULL) return; /* system (3) priority, notice (5) severity */ len = strftime(syslog_send_buf, SYSLOG_SEND_BUF_SIZE, "<29>%b %d %H:%M:%S subtext: ", localtime(&now)); len += vsnprintf(syslog_send_buf + len, SYSLOG_SEND_BUF_SIZE - len, format, ap); syslog_wds[0].ptr = (Ptr)syslog_send_buf; syslog_wds[0].length = len; syslog_wds[1].ptr = 0; syslog_wds[1].length = 0; _UDPSend(&syslog_pb, syslog_stream, syslog_wds, db->config.syslog_ip, 514, NULL, NULL, false); }