IRC client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

log.c 3.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /* Copyright (C) 2018 Curtis McEnroe <june@causal.agency>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU Affero General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include <err.h>
  17. #include <errno.h>
  18. #include <fcntl.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <sys/stat.h>
  23. #include <sysexits.h>
  24. #include <time.h>
  25. #include "chat.h"
  26. static int logRoot = -1;
  27. static struct Log {
  28. int dir;
  29. int year;
  30. int month;
  31. int day;
  32. FILE *file;
  33. } logs[TagsLen];
  34. void logOpen(const char *path) {
  35. logRoot = open(path, O_RDONLY | O_CLOEXEC);
  36. if (logRoot < 0) err(EX_CANTCREAT, "%s", path);
  37. }
  38. static void sanitize(char *name) {
  39. for (; name[0]; ++name) {
  40. if (name[0] == '/') name[0] = '_';
  41. }
  42. }
  43. static FILE *logFile(struct Tag tag, const struct tm *time) {
  44. struct Log *log = &logs[tag.id];
  45. if (
  46. log->file
  47. && log->year == time->tm_year
  48. && log->month == time->tm_mon
  49. && log->day == time->tm_mday
  50. ) return log->file;
  51. if (log->file) {
  52. fclose(log->file);
  53. } else {
  54. char *name = strdup(tag.name);
  55. if (!name) err(EX_OSERR, "strdup");
  56. sanitize(name);
  57. int error = mkdirat(logRoot, name, 0700);
  58. if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", name);
  59. log->dir = openat(logRoot, name, O_RDONLY | O_CLOEXEC);
  60. if (log->dir < 0) err(EX_CANTCREAT, "%s", name);
  61. free(name);
  62. }
  63. log->year = time->tm_year;
  64. log->month = time->tm_mon;
  65. log->day = time->tm_mday;
  66. char path[sizeof("YYYY-MM-DD.log")];
  67. strftime(path, sizeof(path), "%F.log", time);
  68. int fd = openat(
  69. log->dir, path, O_RDWR | O_APPEND | O_CREAT | O_CLOEXEC, 0600
  70. );
  71. if (fd < 0) err(EX_CANTCREAT, "%s/%s", tag.name, path);
  72. log->file = fdopen(fd, "a+");
  73. if (!log->file) err(EX_CANTCREAT, "%s/%s", tag.name, path);
  74. setlinebuf(log->file);
  75. return log->file;
  76. }
  77. enum { StampLen = sizeof("YYYY-MM-DDThh:mm:ss+hhmm") - 1 };
  78. void logFmt(struct Tag tag, const time_t *ts, const char *format, ...) {
  79. if (logRoot < 0) return;
  80. time_t t;
  81. if (!ts) {
  82. t = time(NULL);
  83. ts = &t;
  84. }
  85. struct tm *time = localtime(ts);
  86. if (!time) err(EX_SOFTWARE, "localtime");
  87. FILE *file = logFile(tag, time);
  88. char stamp[StampLen + 1];
  89. strftime(stamp, sizeof(stamp), "%FT%T%z", time);
  90. fprintf(file, "[%s] ", stamp);
  91. if (ferror(file)) err(EX_IOERR, "%s", tag.name);
  92. va_list ap;
  93. va_start(ap, format);
  94. vfprintf(file, format, ap);
  95. va_end(ap);
  96. if (ferror(file)) err(EX_IOERR, "%s", tag.name);
  97. fprintf(file, "\n");
  98. if (ferror(file)) err(EX_IOERR, "%s", tag.name);
  99. }
  100. void logReplay(struct Tag tag) {
  101. if (logRoot < 0) return;
  102. time_t t = time(NULL);
  103. struct tm *time = localtime(&t);
  104. if (!time) err(EX_SOFTWARE, "localtime");
  105. FILE *file = logFile(tag, time);
  106. rewind(file);
  107. size_t len;
  108. char *line;
  109. while (NULL != (line = fgetln(file, &len))) {
  110. line[len - 1] = '\0';
  111. if (len > 1 + StampLen + 2) line = &line[1 + StampLen + 2];
  112. uiFmt(tag, UICold, "\3%d%s", IRCGray, line);
  113. }
  114. if (ferror(file)) err(EX_IOERR, "%s", tag.name);
  115. }