2 * Copyright (c) 2017, De Rais <derais@cock.li>
4 * Permission to use, copy, modify, and/or distribute this software for
5 * any purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 * PERFORMANCE OF THIS SOFTWARE.
30 #define TRY_BIND_T(s, db, i, v) \
32 if ((sqlite3_bind_text((s), \
33 sqlite3_bind_parameter_index((s), (i)), \
34 (v), -1, SQLITE_STATIC)) \
36 ERROR_MESSAGE("sqlite3_bind_text(): cannot bind " \
39 sqlite3_errmsg(db)); \
44 #define TRY_BIND_I(s, db, i, v) \
46 if ((sret = sqlite3_bind_int64((s), \
47 sqlite3_bind_parameter_index((s), \
49 (sqlite3_int64) (v))) != \
51 ERROR_MESSAGE("sqlite3_bind_int64(): cannot bind " \
53 (i), ((long long int) (v)), \
54 sqlite3_errmsg(db)); \
59 #define TRY_MAKE_STATEMENT_ARRAY(name) \
61 if (!(board_ ## name ## _stmt = \
62 calloc(conf->boards_num, \
63 sizeof *board_ ## name ## _stmt))) \
65 PERROR_MESSAGE("calloc"); \
70 #define TRY_PREPARE_FOR_BOARD(j, name) \
72 if (sqlite3_prepare_v2(board_dbs[j], \
73 board_ ## name ## _txt, -1, \
74 &board_ ## name ## _stmt[j], \
76 ERROR_MESSAGE("Preparing statement failed: %s", \
77 sqlite3_errmsg(board_dbs[j])); \
82 #define FINALIZE_FOR_BOARD(j, name) \
84 if (board_ ## name ## _stmt) { \
85 sqlite3_finalize(board_ ## name ## _stmt[j]); \
86 board_ ## name ## _stmt[j] = 0; \
90 #define CLEAN_UP_STATEMENT_ARRAY(name) \
92 free(board_ ## name ## _stmt); \
93 board_ ## name ## _stmt = 0; \
96 #define EXFILTRATE_TEXT(s, n, str, len) \
98 char *tmp = (char *) sqlite3_column_text(s, n); \
101 len = sqlite3_column_bytes(s, n); \
110 static const char *make_comment_table
=
111 "create table if not exists comments \n" /* */
112 " (id integer primary key autoincrement,\n" /* */
113 " thread_closed integer default 0, \n" /* */
114 " thread_full integer default 0, \n" /* */
115 " thread_stickied integer default 0, \n" /* */
116 " thread_last_reply integer default 0, \n" /* */
117 " thread_bumpable integer default 1, \n" /* */
118 " in_thread integer default 0, \n" /* */
119 " ip text default '0.0.0.0', \n" /* */
120 " date integer default 0, \n" /* */
121 " name text default 'Anonymous', \n" /* */
122 " tripcode text default '', \n" /* */
123 " email text default '', \n" /* */
124 " comment text default '', \n" /* */
125 " subject text default '', \n" /* */
126 " users_filename text default '', \n" /* */
127 " system_full_path text default '', \n" /* */
128 " system_thumb_path text default '', \n" /* */
129 " file_info text default '' \n" /* */
131 static const char *make_ban_table
=
132 "create table if not exists bans \n" /* */
133 " (id integer primary key autoincrement,\n" /* */
134 " ip_start text default '', \n" /* */
135 " ip_end text default '', \n" /* */
136 " date_start integer default 0, \n" /* */
137 " date_end integer default 0, \n" /* */
138 " reason text default 'No reason given' \n" /* */
140 static const char *make_cooldown_table
=
141 "create table if not exists cooldowns \n" /* */
142 " (ip text primary key, \n" /* */
143 " cooldown_expiry integer default 0 \n" /* */
145 /* Are you banned globally? */
146 static const char *global_check_ban_txt
=
147 "select date_end, reason from bans \n" /* */
148 " where @ip between ip_start and ip_end \n" /* */
149 " and date_end > @now; \n"; /* */
150 static sqlite3_stmt
*global_check_ban_stmt
;
152 /* Create a global ban */
153 static const char *global_insert_ban_txt
=
154 "insert into bans ( ip_start, \n" /* */
156 " date_start, \n" /* */
157 " date_end, \n" /* */
159 " values (@ip_start, \n" /* */
161 " @date_start, \n" /* */
162 " @date_end, \n" /* */
163 " @reason); \n"; /* */
164 static sqlite3_stmt
*global_insert_ban_stmt
;
166 /* Are you banned? */
167 static const char *board_check_ban_txt
=
168 "select date_end, reason from bans \n" /* */
169 " where @ip between ip_start and ip_end \n" /* */
170 " and date_end > @now; \n"; /* */
171 static sqlite3_stmt
**board_check_ban_stmt
;
173 /* When was your last post? */
174 static const char *board_check_cooldown_txt
= "select cooldown_expiry\n" /* */
175 "from cooldowns\n" /* */
177 static sqlite3_stmt
**board_check_cooldown_stmt
;
179 /* That thread, uhh, exists, right? */
180 static const char *board_check_thread_exists_txt
=
181 "select thread_closed, thread_full from comments \n" /* */
182 "where id is @thread; \n";
183 static sqlite3_stmt
**board_check_thread_exists_stmt
;
185 /* How many replies are in this thread? */
186 static const char *board_count_posts_txt
=
187 "select count(*) from comments \n" /* */
188 "where coalesce(in_thread, id) is @thread; \n";
189 static sqlite3_stmt
**board_count_posts_stmt
;
191 /* Delete a thread */
192 static const char *board_delete_thread_txt
=
193 "delete from comments where coalesce(in_thread, id) is @thread;";
194 static sqlite3_stmt
**board_delete_thread_stmt
;
197 static const char *board_delete_post_txt
=
198 "delete from comments where id is @id;";
199 static sqlite3_stmt
**board_delete_post_stmt
;
201 /* Get the thread for a post */
202 static const char *board_find_containing_thread_txt
=
204 select coalesce(in_thread, id) from comments where id is @id \
207 static sqlite3_stmt
**board_find_containing_thread_stmt
;
209 /* Get the subject of a thread */
210 static const char *board_get_subject_txt
=
211 "select subject from comments where id is @thread;";
212 static sqlite3_stmt
**board_get_subject_stmt
;
214 /* Get the contents of a thread */
215 static const char *board_get_thread_contents_txt
=
216 "select id, date, name, subject, email, \n" /* */
217 " tripcode, comment, users_filename, \n" /* */
218 " system_full_path, system_thumb_path, \n" /* */
219 " file_info, ip, thread_closed, \n" /* */
220 " thread_stickied from comments \n" /* */
221 "where coalesce(in_thread, id) is @thread;\n"; /* */
222 static sqlite3_stmt
**board_get_thread_contents_stmt
;
224 /* Get enough of a thread to write a summary on a board page */
225 static const char *board_get_thread_summary_txt
=
226 "select id, date, name, subject, email, \n" /* */
227 " tripcode, comment, users_filename, \n" /* */
228 " system_full_path, system_thumb_path, \n" /* */
229 " file_info, ip, thread_closed, \n" /* */
230 " thread_stickied from comments where \n" /* */
231 " coalesce(in_thread, id) is @thread \n" /* */
232 " and (id in ( \n" /* */
233 " select id from comments where \n" /* */
234 " id is @thread or \n" /* */
235 " in_thread is @thread \n" /* */
236 " order by id desc limit 3 \n" /* */
237 " ) or id is @thread) order by id asc;\n"; /* */
238 static sqlite3_stmt
**board_get_thread_summary_stmt
;
240 /* Get the contents of a post */
241 static const char *board_get_post_contents_txt
=
242 "select id, date, name, subject, email, \n" /* */
243 " tripcode, comment, users_filename, \n" /* */
244 " system_full_path, system_thumb_path, \n" /* */
245 " file_info, ip, in_thread, \n" /* */
246 " thread_closed, thread_stickied \n" /* */
247 " from comments \n" /* */
248 "where id is @post; \n"; /* */
249 static sqlite3_stmt
**board_get_post_contents_stmt
;
251 /* Create a global ban */
252 static const char *board_insert_ban_txt
=
253 "insert into bans ( ip_start, \n" /* */
255 " date_start, \n" /* */
256 " date_end, \n" /* */
258 " values (@ip_start, \n" /* */
260 " @date_start, \n" /* */
261 " @date_end, \n" /* */
262 " @reason); \n"; /* */
263 static sqlite3_stmt
**board_insert_ban_stmt
;
265 /* Make a post/thread/whatever */
266 static const char *board_insert_comment_txt
=
267 "insert into comments ( ip, \n" /* */
269 " thread_last_reply,\n" /* */
270 " in_thread, \n" /* */
272 " tripcode, \n" /* */
276 " users_filename, \n" /* */
277 " system_full_path, \n" /* */
278 " system_thumb_path \n" /* */
280 " values (@ip, \n" /* */
283 " @in_thread, \n" /* */
285 " @tripcode, \n" /* */
287 " @subject, \n" /* */
288 " @comment, \n" /* */
289 " @users_filename, \n" /* */
290 " @system_full_path, \n" /* */
291 " @system_thumb_path \n" /* */
293 static sqlite3_stmt
**board_insert_comment_stmt
;
295 /* Insert comment part II: adjust the thread */
296 static const char *board_insert_comment_II_txt
=
297 "update comments set \n" /* */
298 " thread_last_reply = \n" /* */
299 " (case when @should_bump is 1 and \n" /* */
300 " thread_bumpable is 1 then \n" /* */
303 " thread_last_reply \n" /* */
306 " thread_bumpable = \n" /* */
307 " (case when (select count(*) \n" /* */
308 " from comments \n" /* */
309 " where in_thread is @in_thread) \n" /* */
310 " >= 300 then \n" /* */
313 " thread_bumpable \n" /* */
316 " thread_full = \n" /* */
317 " (case when (select count(*) \n" /* */
318 " from comments \n" /* */
319 " where in_thread is @in_thread) \n" /* */
320 " >= 500 then \n" /* */
323 " thread_full \n" /* */
325 " where id is @in_thread; \n"; /* */
326 static sqlite3_stmt
**board_insert_comment_II_stmt
;
328 /* Find all threads on this board */
329 static const char *board_list_threads_txt
=
330 "select id from comments where in_thread is NULL\n" /* */
331 "order by thread_stickied desc, \n" /* */
332 " thread_last_reply desc; \n"; /* */
333 static sqlite3_stmt
**board_list_threads_stmt
;
336 static const char *board_set_cooldown_txt
=
339 "insert or replace into cooldowns (ip, \n" /* */
340 " cooldown_expiry) \n" /* */
341 "values (@ip, @cooldown_expiry); "; /* */
342 static sqlite3_stmt
**board_set_cooldown_stmt
;
344 /* Change the sorts of things that moderation needs */
345 static const char *board_update_by_moderation_txt
=
346 "update comments set \n" /* */
347 " comment = @comment, \n" /* */
348 " thread_stickied = @thread_stickied, \n" /* */
349 " thread_closed = @thread_closed \n" /* */
350 " where id is @id; \n"; /* */
351 static sqlite3_stmt
**board_update_by_moderation_stmt
;
353 /* Update the file_info field for a post (after creation) */
354 static const char *board_update_file_info_txt
=
355 "update comments set \n" /* */
356 " file_info = @file_info, \n" /* */
357 " system_full_path = @system_full_path, \n" /* */
358 " system_thumb_path = @system_thumb_path \n" /* */
359 " where id is @id; \n"; /* */
360 static sqlite3_stmt
**board_update_file_info_stmt
;
362 /* Our connections */
363 static sqlite3
**board_dbs
= 0;
364 static size_t num_connected_db
= 0;
365 static sqlite3
*global_db
= 0;
367 /* Global configuration */
368 const struct configuration
*conf
;
371 * Make sure we can connect to the DBs and that they're in working order
375 * - setup_dbs() was not invoked more recently than clean_dbs().
377 * Postconditions (success):
379 * - Any other function in this file may be safely called.
381 int setup_dbs(const struct configuration
*in_conf
)
387 char *error_message
= 0;
391 /* Memory for all our board-specific things */
392 if (!(board_dbs
= calloc(conf
->boards_num
, sizeof *board_dbs
))) {
393 PERROR_MESSAGE("calloc");
397 TRY_MAKE_STATEMENT_ARRAY(check_ban
);
398 TRY_MAKE_STATEMENT_ARRAY(check_cooldown
);
399 TRY_MAKE_STATEMENT_ARRAY(check_thread_exists
);
400 TRY_MAKE_STATEMENT_ARRAY(count_posts
);
401 TRY_MAKE_STATEMENT_ARRAY(delete_thread
);
402 TRY_MAKE_STATEMENT_ARRAY(delete_post
);
403 TRY_MAKE_STATEMENT_ARRAY(find_containing_thread
);
404 TRY_MAKE_STATEMENT_ARRAY(get_subject
);
405 TRY_MAKE_STATEMENT_ARRAY(get_thread_contents
);
406 TRY_MAKE_STATEMENT_ARRAY(get_thread_summary
);
407 TRY_MAKE_STATEMENT_ARRAY(get_post_contents
);
408 TRY_MAKE_STATEMENT_ARRAY(insert_ban
);
409 TRY_MAKE_STATEMENT_ARRAY(insert_comment
);
410 TRY_MAKE_STATEMENT_ARRAY(insert_comment_II
);
411 TRY_MAKE_STATEMENT_ARRAY(list_threads
);
412 TRY_MAKE_STATEMENT_ARRAY(set_cooldown
);
413 TRY_MAKE_STATEMENT_ARRAY(update_by_moderation
);
414 TRY_MAKE_STATEMENT_ARRAY(update_file_info
);
416 /* Turn on global connection */
417 len
= snprintf(0, 0, "%s/global.db", conf
->work_path
);
419 if (!(path
= malloc(len
+ 1))) {
420 PERROR_MESSAGE("malloc");
424 sprintf(path
, "%s/global.db", conf
->work_path
);
426 if ((sret
= sqlite3_open(path
, &global_db
)) != SQLITE_OK
) {
427 ERROR_MESSAGE("Cannot open or create database %s: %s", path
,
428 sqlite3_errstr(sret
));
432 /* Set up global table (only bans) */
433 if (sqlite3_exec(global_db
, make_ban_table
, 0, 0, &error_message
) !=
435 ERROR_MESSAGE("Cannot set up ban table in database %s: %s",
436 path
, error_message
);
440 /* Global statments (only ban creation/checking) */
441 if (sqlite3_prepare_v2(global_db
, global_check_ban_txt
, -1,
442 &global_check_ban_stmt
, 0) != SQLITE_OK
) {
443 ERROR_MESSAGE("Preparing statement failed: %s", sqlite3_errmsg(
448 if (sqlite3_prepare_v2(global_db
, global_insert_ban_txt
, -1,
449 &global_insert_ban_stmt
, 0) != SQLITE_OK
) {
450 ERROR_MESSAGE("Preparing statement failed: %s", sqlite3_errmsg(
455 /* Board specific stuff */
456 for (size_t j
= 0; j
< conf
->boards_num
; ++j
) {
459 len
= snprintf(0, 0, "%s/board_%s.db", conf
->work_path
,
460 conf
->boards
[j
].name
);
462 if (!(path
= malloc(len
+ 1))) {
463 PERROR_MESSAGE("malloc");
467 sprintf(path
, "%s/board_%s.db", conf
->work_path
,
468 conf
->boards
[j
].name
);
471 if ((sret
= sqlite3_open(path
, &board_dbs
[j
])) != SQLITE_OK
) {
472 ERROR_MESSAGE("Cannot open or create database %s: %s",
473 path
, sqlite3_errstr(sret
));
480 if (sqlite3_exec(board_dbs
[j
], make_comment_table
, 0, 0,
481 &error_message
) != SQLITE_OK
) {
483 "Cannot set up comment table in database %s: %s",
489 if (sqlite3_exec(board_dbs
[j
], make_ban_table
, 0, 0,
490 &error_message
) != SQLITE_OK
) {
492 "Cannot set up ban table in database %s: %s",
498 if (sqlite3_exec(board_dbs
[j
], make_cooldown_table
, 0, 0,
499 &error_message
) != SQLITE_OK
) {
501 "Cannot set up cooldown table in database %s: %s",
510 /* Set up statements */
511 TRY_PREPARE_FOR_BOARD(j
, check_ban
);
512 TRY_PREPARE_FOR_BOARD(j
, check_cooldown
);
513 TRY_PREPARE_FOR_BOARD(j
, check_thread_exists
);
514 TRY_PREPARE_FOR_BOARD(j
, count_posts
);
515 TRY_PREPARE_FOR_BOARD(j
, delete_thread
);
516 TRY_PREPARE_FOR_BOARD(j
, delete_post
);
517 TRY_PREPARE_FOR_BOARD(j
, find_containing_thread
);
518 TRY_PREPARE_FOR_BOARD(j
, get_subject
);
519 TRY_PREPARE_FOR_BOARD(j
, get_thread_contents
);
520 TRY_PREPARE_FOR_BOARD(j
, get_thread_summary
);
521 TRY_PREPARE_FOR_BOARD(j
, get_post_contents
);
522 TRY_PREPARE_FOR_BOARD(j
, insert_ban
);
523 TRY_PREPARE_FOR_BOARD(j
, insert_comment
);
524 TRY_PREPARE_FOR_BOARD(j
, insert_comment_II
);
525 TRY_PREPARE_FOR_BOARD(j
, list_threads
);
526 TRY_PREPARE_FOR_BOARD(j
, set_cooldown
);
527 TRY_PREPARE_FOR_BOARD(j
, update_by_moderation
);
528 TRY_PREPARE_FOR_BOARD(j
, update_file_info
);
535 sqlite3_free(error_message
);
544 * Construct something suitable for use in <a>
548 * - setup_dbs() has been invoked more recently than clean_dbs().
550 * - board is a sequence of ASCII characters of length board_len
551 * that represents a board.
553 * - Board directories are located at "/", so that "/".board."/res/"
554 * is where thread pages live.
556 * - post is a sequence of ASCII digits of length post_len.
558 * - out, out_len, and found are not 0.
560 * - Overwriting *out shall not cause a memory leak.
564 * - If the post doesn't exist, *found = 0.
566 * - Otherwise, *found is 1, and *out is a string like "/a/res/1235"
567 * of length *out_len, which can be used in a <a> element.
569 int db_construct_post_link(const char *board
, size_t board_len
, const
570 char *post
, size_t post_len
, int *found
, char **out
,
575 size_t board_idx
= (size_t) -1;
576 size_t in_thread
= 0;
577 uintmax_t post_num
= 0;
583 if (board_len
> INT_MAX
/ 2) {
584 ERROR_MESSAGE("The board name \"%.*s...\" is way too long", 10,
590 * We can't call strtoll(post, 0, 0) because board might
591 * not be 0-terminated, it may point into internal PCRE2
592 * memory for example. It's simpler to recreate base 10
593 * strtoll than to malloc/copy/free a temp buffer.
595 for (size_t j
= 0; j
< post_len
; ++j
) {
596 post_num
= 10 * post_num
+ (post
[j
] - '0');
599 for (size_t j
= 0; j
< num_connected_db
; ++j
) {
600 const struct board
*b
= &conf
->boards
[j
];
602 if (strlen(b
->name
) == board_len
&&
603 !strcmp(board
, b
->name
)) {
609 if (board_idx
== (size_t) -1) {
610 ERROR_MESSAGE("Board \"%.*s\" doesn't exist", (int) board_len
,
615 s
= board_find_containing_thread_stmt
[board_idx
];
616 db
= board_dbs
[board_idx
];
617 TRY_BIND_I(s
, db
, "@id", post_num
);
618 sret
= sqlite3_step(s
);
626 in_thread
= sqlite3_column_int64(s
, 0);
629 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
634 len
= snprintf(0, 0, "/%.*s/res/%zu#post%zu", (int) board_len
, board
,
635 in_thread
, post_num
);
637 if (!(tmp
= malloc(len
+ 1))) {
638 PERROR_MESSAGE("malloc");
642 sprintf(tmp
, "/%.*s/res/%zu#post%zu", (int) board_len
, board
, in_thread
,
650 sqlite3_clear_bindings(s
);
656 * Ensure that there are not more than the proper number of threads
657 * lying around; report how many pages we need.
661 * - setup_dbs() has been invoked more recently than clean_dbs().
663 * - board_idx represents a board, AND THE LOCK IS HELD.
665 * - out_thread_ids, out_thread_id_num, and out_num_pages are not 0.
667 * - Overwriting *out_thread_ids shall not cause a memory leak.
669 * Postconditions (success):
671 * - There are num_pages * threads_per_page threads (rows with
672 * in_thread = 0) in the board's database.
674 * - If rows had to be deleted, all relevant reply rows were also
677 * - If rows had to be deleted, all files related to those rows
678 * (the thread page, the stored files for replies, etc.) have
681 int db_cull_and_report_threads(size_t board_idx
, uintmax_t **out_thread_ids
,
682 size_t *out_thread_ids_num
,
683 size_t *out_num_pages
)
685 uintmax_t *to_delete
= 0;
686 size_t to_delete_num
= 0;
687 size_t to_delete_sz
= 0;
688 uintmax_t total_threads_seen
= 0;
689 uintmax_t threads_to_keep
= 0;
692 sqlite3_stmt
*s
= board_list_threads_stmt
[board_idx
];
693 sqlite3
*db
= board_dbs
[board_idx
];
694 uint_fast8_t exhausted
= 0;
696 uintmax_t *thread_ids
= 0;
697 const struct board
*b
= &conf
->boards
[board_idx
];
699 threads_to_keep
= b
->num_pages
* b
->threads_per_page
;
701 if (!(thread_ids
= calloc(threads_to_keep
, sizeof *thread_ids
))) {
702 PERROR_MESSAGE("calloc");
706 if (!(to_delete
= malloc(sizeof *to_delete
))) {
707 PERROR_MESSAGE("malloc");
715 sret
= sqlite3_step(s
);
722 total_threads_seen
++;
724 if (total_threads_seen
> threads_to_keep
) {
725 to_delete
[to_delete_num
] = sqlite3_column_int64(
728 if (to_delete_num
+ 1 >= to_delete_sz
) {
729 if (!(newmem
= realloc(to_delete
,
734 PERROR_MESSAGE("calloc");
744 thread_ids
[total_threads_seen
- 1] =
745 sqlite3_column_int64(s
, 0);
750 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
755 for (size_t j
= 0; j
< to_delete_num
; ++j
) {
756 db_remove_thread_and_files(board_idx
, to_delete
[j
]);
759 *out_thread_ids
= thread_ids
;
760 *out_thread_ids_num
= (total_threads_seen
> threads_to_keep
) ?
761 threads_to_keep
: total_threads_seen
;
764 if (*out_thread_ids_num
) {
765 *out_num_pages
= 1 + ((*out_thread_ids_num
- 1) /
766 b
->threads_per_page
);
774 sqlite3_clear_bindings(s
);
780 * Check whether a specific type of ban is active.
784 * - setup_dbs() has been invoked more recently than clean_dbs().
786 * - s is one of global_check_ban_stmt or a board_check_ban_stmt[j].
788 * - db corresponds to s.
790 * - ip is a string like "127.0.0.1"
792 * - out_is_banned, out_ban_until, out_ban_reason are not 0.
794 * - Overwriting *out_ban_until and *out_ban_reason shall not cause
797 * Postconditions (success):
799 * - *out_is_banned represents whether s returned a row for ip and
802 * - If *out_banned != 0, then *out_ban_until and *out_ban_reason
803 * are informative text strings (*out_ban_until is something
804 * like "2020-01-01T12:34:56" and *out_ban_reason is something
805 * like "having wrong opinions"). They are not 0.
807 static int check_ban_h(sqlite3_stmt
*s
, sqlite3
* db
, const char *ip
, time_t
808 now
, int *out_is_banned
, char **out_ban_until
,
809 char **out_ban_reason
)
814 TRY_BIND_T(s
, db
, "@ip", ip
);
815 TRY_BIND_I(s
, db
, "@now", now
);
816 sret
= sqlite3_step(s
);
825 UNUSED(sqlite3_column_text(s
, 1));
827 if (!(*out_ban_reason
= malloc(1 + sqlite3_column_bytes(s
,
829 ERROR_MESSAGE("malloc");
833 strcpy(*out_ban_reason
, (const char *) sqlite3_column_text(s
,
836 if (!(*out_ban_until
= util_iso8601_from_time_t(
837 (time_t) sqlite3_column_int64(s
, 0)))) {
844 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
851 sqlite3_clear_bindings(s
);
857 * Check whether any ban is active.
861 * - setup_dbs() has been invoked more recently than clean_dbs().
863 * - ip is a string like "127.0.0.1".
865 * - out_is_banned, out_ban_until, out_ban_reason are not 0.
867 * - Overwriting *out_ban_until and *out_ban_reason shall not cause
870 * - board_idx corresponds to a board.
872 * Postconditions (success):
874 * - *out_is_banned represents whether a row was found in the bans
875 * db, either globally or for board_idx, matching ip and now.
877 * - If *out_banned != 0, then *out_ban_until and *out_ban_reason
878 * are informative text strings (*out_ban_until is something
879 * like "2020-01-01T12:34:56" and *out_ban_reason is something
880 * like "having wrong opinions"). They are not 0.
882 int db_check_bans(const char *ip
, size_t board_idx
, time_t now
,
883 int *out_is_banned
, char **out_ban_until
,
884 char **out_ban_reason
)
888 /* First check global bans */
889 if (check_ban_h(global_check_ban_stmt
, global_db
, ip
, now
,
890 out_is_banned
, out_ban_until
, out_ban_reason
) < 0) {
894 if (*out_is_banned
) {
899 /* Now board-specific */
900 if (check_ban_h(board_check_ban_stmt
[board_idx
], board_dbs
[board_idx
],
901 ip
, now
, out_is_banned
, out_ban_until
, out_ban_reason
) <
913 * Check whether a cooldown is active.
917 * - setup_dbs() has been invoked more recently than clean_dbs().
919 * - ip is a string like "127.0.0.1".
921 * - out_is_cooled, out_cooldown_length are not 0.
923 * - Overwriting *out_cooldown_length shall not cause a memory
926 * - board_idx corresponds to a board.
928 * Postconditions (success):
930 * - *out_is_cooled represents whether a row was found in the
931 * cooldowns table corresponding to board_idx.
933 * - If *out_is_cooled != 0, then *out_cooldown_length is a string
934 * like "20 seconds", corresponding to the cooldown row.
936 int db_check_cooldowns(const char *ip
, size_t board_idx
, time_t now
,
937 int *out_is_cooled
, char **out_cooldown_length
)
944 sqlite3_stmt
*s
= board_check_cooldown_stmt
[board_idx
];
945 sqlite3
*db
= board_dbs
[board_idx
];
947 TRY_BIND_T(s
, db
, "@ip", ip
);
948 sret
= sqlite3_step(s
);
955 expiry
= (time_t) sqlite3_column_int64(s
, 0);
956 diff
= (expiry
> now
) ? expiry
- now
: -1;
960 len
= snprintf(0, 0, "%ld seconds", diff
);
962 if (!(*out_cooldown_length
= malloc(len
+ 1))) {
963 PERROR_MESSAGE("malloc");
967 sprintf(*out_cooldown_length
, "%ld seconds", diff
);
973 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
980 sqlite3_clear_bindings(s
);
986 * Check whether a thread exists, is full, is closed.
990 * - board_idx corresponds to a board.
992 * - thread_dne, thread_closed, thread_full are not 0.
994 static int check_thread(uintmax_t id
, size_t board_idx
, int *thread_dne
,
995 int *thread_closed
, int *thread_full
)
999 sqlite3
*db
= board_dbs
[board_idx
];
1000 sqlite3_stmt
*s
= board_check_thread_exists_stmt
[board_idx
];
1002 TRY_BIND_I(s
, db
, "@thread", id
);
1003 sret
= sqlite3_step(s
);
1011 *thread_closed
= sqlite3_column_int(s
, 0);
1012 *thread_full
= sqlite3_column_int(s
, 1);
1016 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1023 sqlite3_clear_bindings(s
);
1028 /* Get the subject of a thread.
1032 * - setup_dbs() has been invoked more recently than clean_dbs().
1034 * - board_idx represents a board.
1036 * - thread is the id of a thread.
1038 * - out_subject and out_subject_len are not 0.
1040 * - overwriting *out_subject shall not cause a memory leak.
1042 * Postconditions (success):
1044 * - *out_subject is a string of length *out_subject_len, which
1045 * is the subject of the thread given by thread.
1047 * - The memory of *out_subject should be freed by the caller.
1049 int db_extract_subject(size_t board_idx
, uintmax_t thread
, char **out_subject
,
1050 size_t *out_subject_len
)
1054 sqlite3
*db
= board_dbs
[board_idx
];
1055 sqlite3_stmt
*s
= board_get_subject_stmt
[board_idx
];
1057 TRY_BIND_I(s
, db
, "@thread", thread
);
1058 sret
= sqlite3_step(s
);
1064 EXFILTRATE_TEXT(s
, 0, *out_subject
, *out_subject_len
);
1067 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1074 sqlite3_clear_bindings(s
);
1080 * Insert a ban, which may be either global or board-specific.
1084 * - setup_dbs() has been invoked more recently than clean_dbs().
1086 * - Either global_ban is non-zero, or board_idx represents a board.
1088 * - first_ip and last_ip are "normalized" ip addresses, in the
1089 * sense of the output of util_normalize_ip(), not e.g. RFC 2373.
1091 * - message is a string.
1093 * Postconditions (success):
1095 * - Depending on global_ban and board_idx, a row in the bans table
1096 * of an appropriate database has been created, depending on the
1097 * input parameters in an obvious way.
1099 int db_insert_ban(uint_fast8_t global_ban
, size_t board_idx
, const
1100 char *first_ip
, const char *last_ip
, const char *message
,
1106 sqlite3_stmt
*s
= 0;
1110 s
= global_insert_ban_stmt
;
1113 s
= board_insert_ban_stmt
[board_idx
];
1114 db
= board_dbs
[board_idx
];
1117 TRY_BIND_T(s
, db
, "@ip_start", first_ip
);
1118 TRY_BIND_T(s
, db
, "@ip_end", last_ip
);
1119 TRY_BIND_I(s
, db
, "@date_start", ban_start
);
1120 TRY_BIND_I(s
, db
, "@date_end", ban_expiry
);
1121 TRY_BIND_T(s
, db
, "@reason", message
);
1122 sret
= sqlite3_step(s
);
1128 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1135 sqlite3_clear_bindings(s
);
1141 * Insert a post, which may be a reply or a new thread. Some fields
1142 * (file_info) are not filled out - they are updated later, after
1143 * filesystem work has been completed.
1147 * - setup_dbs() has been invoked more recently than clean_dbs().
1149 * - ip is a string like "127.0.0.1".
1151 * - f is not 0 if pc contains a file.
1153 * - The prepared_XYZ fields of pc are filled out.
1155 * - If this post is a reply, in_thread is the id of the thread's
1158 * - thread_dne, thread_closed, thread_full, post_id are not 0.
1160 * Postconditions (success):
1162 * - If the post couldn't be made because the thread doesn't exist,
1165 * - Otherwise, if the post couldn't be made because the thread
1166 * is closed, *thread_closed = 1.
1168 * - Otherwise, if the post couldn't be made because the thread
1169 * is full, *thread_full = 1.
1171 * - Otherwise, the post was made, and the surrounding thread's
1172 * reply date, fullness, etc. have been updated (no actual HTML
1173 * regeneration, though).
1175 * - Furthermore, *post_id is the number of the inserted post.
1177 int db_insert_post(const char *ip
, size_t in_thread
, int cooldown
, struct
1178 post_cmd
*pc
, int *thread_dne
, int *thread_closed
,
1184 sqlite3_stmt
*s
= board_insert_comment_stmt
[pc
->board_idx
];
1185 sqlite3_stmt
*s2
= board_insert_comment_II_stmt
[pc
->board_idx
];
1186 sqlite3_stmt
*s3
= board_set_cooldown_stmt
[pc
->board_idx
];
1187 sqlite3
*db
= board_dbs
[pc
->board_idx
];
1189 TRY_BIND_T(s
, db
, "@ip", ip
);
1190 TRY_BIND_I(s
, db
, "@date", pc
->prepared
.now
);
1193 if (check_thread(in_thread
, pc
->board_idx
, thread_dne
,
1194 thread_closed
, thread_full
) < 0) {
1205 TRY_BIND_I(s
, db
, "@in_thread", in_thread
);
1206 TRY_BIND_I(s2
, db
, "@in_thread", in_thread
);
1207 TRY_BIND_I(s2
, db
, "@date", pc
->prepared
.now
);
1208 TRY_BIND_I(s2
, db
, "@should_bump", (!pc
->prepared
.email
||
1209 strcmp(pc
->prepared
.email
,
1213 TRY_BIND_T(s
, db
, "@name", pc
->prepared
.name
);
1214 TRY_BIND_T(s
, db
, "@tripcode", pc
->prepared
.tripcode
);
1215 TRY_BIND_T(s
, db
, "@email", pc
->prepared
.email
);
1216 TRY_BIND_T(s
, db
, "@subject", pc
->prepared
.subject
);
1217 TRY_BIND_T(s
, db
, "@comment", pc
->prepared
.comment
);
1218 TRY_BIND_T(s
, db
, "@users_filename", pc
->prepared
.file_name
);
1221 * It's highly probable that these are blank. At the current
1222 * time of writing, db_insert_post() is called before
1223 * install_files(), and the resulting row is fixed up
1224 * afterwards in db_update_file_info(). These are currently
1225 * left in for the hack-ish hooks for writing posts
1228 TRY_BIND_T(s
, db
, "@system_full_path", pc
->prepared
.system_full_path
);
1229 TRY_BIND_T(s
, db
, "@system_thumb_path", pc
->prepared
.system_thumb_path
);
1230 sret
= sqlite3_step(s
);
1236 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1240 *post_id
= sqlite3_last_insert_rowid(db
);
1243 sret
= sqlite3_step(s2
);
1250 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1255 TRY_BIND_T(s3
, db
, "@ip", ip
);
1256 TRY_BIND_I(s3
, db
, "@cooldown_expiry", pc
->prepared
.now
+ cooldown
);
1257 sret
= sqlite3_step(s3
);
1264 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1271 sqlite3_clear_bindings(s
);
1273 sqlite3_clear_bindings(s2
);
1275 sqlite3_clear_bindings(s3
);
1281 * Check if a post is actually the OP of a thread.
1285 * - setup_dbs() has been invoked more recently than clean_dbs().
1287 * - board_idx represents a board.
1289 * - post_id represents a post.
1291 * - out_is_op is not 0.
1293 * Postconditions (success):
1295 * - *out_is_op is either 1 (if the row with id = post_id has
1296 * in_thread NULL), or 0 (otherwise).
1298 int db_is_op(size_t board_idx
, uintmax_t post_id
, uint_fast8_t *out_is_op
)
1302 sqlite3_stmt
*s
= board_get_post_contents_stmt
[board_idx
];
1303 sqlite3
*db
= board_dbs
[board_idx
];
1305 TRY_BIND_I(s
, db
, "@post", post_id
);
1306 sret
= sqlite3_step(s
);
1313 *out_is_op
= !sqlite3_column_int64(s
, 12);
1316 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1323 sqlite3_clear_bindings(s
);
1329 * Perform minor adjustments to a post
1333 * - setup_dbs() has been invoked more recently than clean_dbs().
1335 * - board_idx represents a board.
1337 * - post_id represents a post.
1339 * - moderator_comment is either 0 or a string.
1341 * - If change_sticky or change_close are not 0, then post_id
1342 * represents the OP of a thread.
1344 * Postconditions (success):
1346 * - If change_sticky, then the thread_stickied will be adjusted
1349 * - If change_close, then the thread_closed will be adjusted to
1352 int db_moderate_post(size_t board_idx
, uintmax_t post_id
, const
1353 char *moderator_comment
, uint_fast8_t change_sticky
,
1354 uint_fast8_t sticky_status
,
1355 uint_fast8_t change_close
, uint_fast8_t close_status
)
1359 sqlite3_stmt
*s
= board_get_post_contents_stmt
[board_idx
];
1360 sqlite3_stmt
*s2
= board_update_by_moderation_stmt
[board_idx
];
1361 sqlite3
*db
= board_dbs
[board_idx
];
1362 uint_fast8_t thread_stickied
= 0;
1363 uint_fast8_t thread_closed
= 0;
1365 size_t comment_len
= 0;
1366 char *new_comment
= 0;
1367 size_t new_comment_len
= 0;
1369 TRY_BIND_I(s
, db
, "@post", post_id
);
1370 sret
= sqlite3_step(s
);
1374 LOG("Board /%s/, post %ju does not exist",
1375 conf
->boards
[board_idx
].name
, post_id
);
1378 EXFILTRATE_TEXT(s
, 6, comment
, comment_len
);
1379 thread_closed
= !!sqlite3_column_int64(s
, 13);
1380 thread_stickied
= !!sqlite3_column_int64(s
, 14);
1383 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1387 if (!moderator_comment
) {
1388 TRY_BIND_T(s2
, db
, "@comment", comment
);
1389 } else if (comment_len
) {
1390 new_comment_len
= snprintf(0, 0, "%s<br /><br />"
1391 "<span class=\"mod-text\">"
1392 "(%s)</span>", comment
,
1395 if (!(new_comment
= malloc(new_comment_len
+ 1))) {
1396 PERROR_MESSAGE("malloc");
1400 sprintf(new_comment
, "%s<br /><br />"
1401 "<span class=\"mod-text\">(%s)</span>",
1404 TRY_BIND_T(s2
, db
, "@comment", new_comment
);
1406 new_comment_len
= snprintf(0, 0, "<span class=\"mod-text\">"
1410 if (!(new_comment
= malloc(new_comment_len
+ 1))) {
1411 PERROR_MESSAGE("malloc");
1415 sprintf(new_comment
, "<span class=\"mod-text\">(%s)</span>",
1417 TRY_BIND_T(s2
, db
, "@comment", new_comment
);
1420 if (change_sticky
) {
1421 TRY_BIND_I(s2
, db
, "@thread_stickied", sticky_status
);
1423 TRY_BIND_I(s2
, db
, "@thread_stickied", thread_stickied
);
1427 TRY_BIND_I(s2
, db
, "@thread_closed", close_status
);
1429 TRY_BIND_I(s2
, db
, "@thread_closed", thread_closed
);
1432 TRY_BIND_I(s2
, db
, "@id", post_id
);
1433 sret
= sqlite3_step(s2
);
1439 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1448 sqlite3_clear_bindings(s
);
1454 * Delete all files related to a post, remove row from the
1459 * - setup_dbs() has been invoked more recently than clean_dbs().
1461 * - board_idx represents a board, AND THE LOCK IS HELD.
1463 * - post_id represents a post (a row with in_thread != NULL).
1465 * Postconditions (success):
1467 * - wt_remove_files() has been called on the relevant paths.
1469 * - The row for which id is thread_id has been removed
1470 * from the database.
1472 int db_remove_post_and_files(size_t board_idx
, uintmax_t post_id
)
1476 sqlite3_stmt
*s
= board_get_post_contents_stmt
[board_idx
];
1477 sqlite3_stmt
*s2
= board_delete_post_stmt
[board_idx
];
1478 sqlite3
*db
= board_dbs
[board_idx
];
1479 char *system_full_path
= 0;
1480 size_t system_full_path_len
= 0;
1481 char *system_thumb_path
= 0;
1482 size_t system_thumb_path_len
= 0;
1484 TRY_BIND_I(s
, db
, "@post", post_id
);
1485 sret
= sqlite3_step(s
);
1489 LOG("Board /%s/, post %ju does not exist",
1490 conf
->boards
[board_idx
].name
, post_id
);
1493 EXFILTRATE_TEXT(s
, 8, system_full_path
, system_full_path_len
);
1494 EXFILTRATE_TEXT(s
, 9, system_thumb_path
, system_thumb_path_len
);
1495 wt_remove_files(system_full_path
, system_full_path_len
,
1496 system_thumb_path
, system_thumb_path_len
);
1499 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1503 TRY_BIND_I(s2
, db
, "@id", post_id
);
1504 sret
= sqlite3_step(s2
);
1510 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1516 free(system_full_path
);
1517 free(system_thumb_path
);
1520 sqlite3_clear_bindings(s
);
1521 sqlite3_clear_bindings(s2
);
1527 * Delete all files related to a thread, remove rows from the
1532 * - setup_dbs() has been invoked more recently than clean_dbs().
1534 * - board_idx represents a board, AND THE LOCK IS HELD.
1536 * - thread_id represents a thread (a row with in_thread = NULL).
1538 * Postconditions (success):
1540 * - For every post in the thread, wt_remove_files() has been
1541 * called on the relevant paths.
1543 * - wt_remove_thread_page() has been called on the relevant thread.
1545 * - Any row for which in_thread is thread_id has been removed
1546 * from the database.
1548 int db_remove_thread_and_files(size_t board_idx
, uintmax_t thread_id
)
1552 sqlite3_stmt
*s
= board_get_thread_contents_stmt
[board_idx
];
1553 sqlite3_stmt
*s2
= board_delete_thread_stmt
[board_idx
];
1554 sqlite3
*db
= board_dbs
[board_idx
];
1555 char *system_full_path
= 0;
1556 size_t system_full_path_len
= 0;
1557 char *system_thumb_path
= 0;
1558 size_t system_thumb_path_len
= 0;
1560 TRY_BIND_I(s
, db
, "@thread", thread_id
);
1562 sret
= sqlite3_step(s
);
1566 LOG("Board /%s/, post %ju does not exist",
1567 conf
->boards
[board_idx
].name
, thread_id
);
1570 EXFILTRATE_TEXT(s
, 8, system_full_path
, system_full_path_len
);
1571 EXFILTRATE_TEXT(s
, 9, system_thumb_path
, system_thumb_path_len
);
1572 wt_remove_files(system_full_path
, system_full_path_len
,
1573 system_thumb_path
, system_thumb_path_len
);
1576 free(system_full_path
);
1577 free(system_thumb_path
);
1578 system_full_path
= 0;
1579 system_thumb_path
= 0;
1582 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1586 if (wt_remove_thread_page(board_idx
, thread_id
) < 0) {
1590 TRY_BIND_I(s2
, db
, "@thread", thread_id
);
1591 sret
= sqlite3_step(s2
);
1597 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1605 sqlite3_clear_bindings(s
);
1606 sqlite3_clear_bindings(s2
);
1612 * Update the file_info field for a comment (as it isn't known at insert time)
1616 * - setup_dbs() has been invoked more recently than clean_dbs().
1618 * - board_idx represents a board.
1620 * - post_id is the id of a row that exists in that board's comments.
1622 * - info is a string of length info_len.
1624 * Postconditions (success):
1626 * - `select file_info from comments where id is @post_id', on the
1627 * correct board, would return info.
1629 int db_update_file_info(size_t board_idx
, uintmax_t post_id
, const char *info
,
1630 size_t info_len
, const char *system_full_path
, size_t
1631 system_full_path_len
,
1632 const char *system_thumb_path
, size_t
1633 system_thumb_path_len
)
1637 sqlite3
*db
= board_dbs
[board_idx
];
1638 sqlite3_stmt
*s
= board_update_file_info_stmt
[board_idx
];
1640 /* XXX: use this in TRY_BIND_T */
1642 UNUSED(system_full_path_len
);
1643 UNUSED(system_thumb_path_len
);
1644 TRY_BIND_I(s
, db
, "@id", post_id
);
1645 TRY_BIND_T(s
, db
, "@file_info", info
);
1646 TRY_BIND_T(s
, db
, "@system_full_path", system_full_path
);
1647 TRY_BIND_T(s
, db
, "@system_thumb_path", system_thumb_path
);
1648 sret
= sqlite3_step(s
);
1655 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1662 sqlite3_clear_bindings(s
);
1668 * The db side of write_thread(): pull all posts from a thread, and
1669 * call pw_function as appropriate.
1671 * *pw_function had better be one of wt_write_post(),
1675 * - setup_dbs() has been invoked more recently than clean_dbs().
1677 * - board_idx represents a board.
1679 * - thread is the id of a thread.
1681 * - f is an open filehandle that can be written to.
1683 * - *pw_function is something like wt_write_post().
1685 * Postconditions (success):
1687 * - The thread has, somehow, been written out to f. In practice,
1688 * this means wt_write_post() has been called on the rows that
1689 * correspond to thread.
1691 int db_writeback_posts_in_thread(size_t board_idx
, uintmax_t thread
, FILE *f
,
1692 post_writeback pw_function
)
1696 uint_fast8_t first_post
= 1;
1697 struct prepared_post p
= { 0 };
1698 sqlite3_stmt
*s
= board_get_thread_contents_stmt
[board_idx
];
1699 sqlite3
*db
= board_dbs
[board_idx
];
1701 TRY_BIND_I(s
, db
, "@thread", thread
);
1703 sret
= sqlite3_step(s
);
1711 p
= (struct prepared_post
) { 0 };
1712 p
.id
= sqlite3_column_int64(s
, 0);
1713 p
.now
= sqlite3_column_int64(s
, 1);
1714 EXFILTRATE_TEXT(s
, 2, p
.name
, p
.name_len
);
1715 EXFILTRATE_TEXT(s
, 3, p
.subject
, p
.subject_len
);
1716 EXFILTRATE_TEXT(s
, 4, p
.email
, p
.email_len
);
1717 EXFILTRATE_TEXT(s
, 5, p
.tripcode
, p
.tripcode_len
);
1718 EXFILTRATE_TEXT(s
, 6, p
.comment
, p
.comment_len
);
1719 EXFILTRATE_TEXT(s
, 7, p
.file_name
, p
.file_name_len
);
1720 EXFILTRATE_TEXT(s
, 8, p
.system_full_path
,
1721 p
.system_full_path_len
);
1722 EXFILTRATE_TEXT(s
, 9, p
.system_thumb_path
,
1723 p
.system_thumb_path_len
);
1724 EXFILTRATE_TEXT(s
, 10, p
.file_info
, p
.file_info_len
);
1725 EXFILTRATE_TEXT(s
, 11, p
.ip
, p
.ip_len
);
1726 p
.thread_closed
= !!sqlite3_column_int64(s
, 12);
1727 p
.thread_stickied
= !!sqlite3_column_int64(s
, 13);
1729 if ((*pw_function
)(&p
, f
, first_post
, 0, 0, 0, 0) < 0) {
1735 /* XXX: refactor into a prepared_post cleaner */
1742 free(p
.system_full_path
);
1743 free(p
.system_thumb_path
);
1748 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1755 sqlite3_clear_bindings(s
);
1761 * The db side of write_board(): pull enough posts from a thread
1762 * to create a summary for the board page.
1766 * - setup_dbs() has been invoked more recently than clean_dbs().
1768 * - board_idx represents a board.
1770 * - thread_ids is an array of size (at least) thread_ids_num.
1772 * - each element of thread_ids represents a currently-active
1773 * thread, and the list is sorted by bump order (most recent
1776 * - f is an open filehandle that can be written to.
1778 * Postconditions (success):
1780 * - Each of the thread_ids_num threads represented in thread_ids
1781 * has, somehow, been written out to f. In practice, this means
1782 * write_op_in_board() and write_post_in_board() have been called
1783 * on the rows that correspond to the OP and the last few posts
1786 int db_writeback_thread_summaries(size_t board_idx
, uintmax_t *thread_ids
,
1787 size_t thread_ids_num
, FILE *f
)
1791 uint_fast8_t first_post
= 1;
1792 sqlite3_stmt
*s
= board_count_posts_stmt
[board_idx
];
1793 sqlite3_stmt
*s2
= board_get_thread_summary_stmt
[board_idx
];
1794 sqlite3
*db
= board_dbs
[board_idx
];
1795 struct prepared_post p
= { 0 };
1797 for (size_t j
= 0; j
< thread_ids_num
; ++j
) {
1798 uintmax_t total_post_num
= 0;
1799 uintmax_t thread_id
= thread_ids
[j
];
1803 sqlite3_clear_bindings(s
);
1804 TRY_BIND_I(s
, db
, "@thread", thread_id
);
1805 sret
= sqlite3_step(s
);
1811 total_post_num
= sqlite3_column_int64(s
, 0);
1814 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1819 sqlite3_clear_bindings(s2
);
1820 TRY_BIND_I(s2
, db
, "@thread", thread_id
);
1822 sret
= sqlite3_step(s2
);
1828 p
= (struct prepared_post
) { 0 };
1829 p
.id
= sqlite3_column_int64(s2
, 0);
1830 p
.now
= sqlite3_column_int64(s2
, 1);
1831 EXFILTRATE_TEXT(s2
, 2, p
.name
, p
.name_len
);
1832 EXFILTRATE_TEXT(s2
, 3, p
.subject
, p
.subject_len
);
1833 EXFILTRATE_TEXT(s2
, 4, p
.email
, p
.email_len
);
1834 EXFILTRATE_TEXT(s2
, 5, p
.tripcode
, p
.tripcode_len
);
1835 EXFILTRATE_TEXT(s2
, 6, p
.comment
, p
.comment_len
);
1836 EXFILTRATE_TEXT(s2
, 7, p
.file_name
, p
.file_name_len
);
1837 EXFILTRATE_TEXT(s2
, 8, p
.system_full_path
,
1838 p
.system_full_path_len
);
1839 EXFILTRATE_TEXT(s2
, 9, p
.system_thumb_path
,
1840 p
.system_thumb_path_len
);
1841 EXFILTRATE_TEXT(s2
, 10, p
.file_info
, p
.file_info_len
);
1842 EXFILTRATE_TEXT(s2
, 11, p
.ip
, p
.ip_len
);
1843 p
.thread_closed
= !!sqlite3_column_int64(s2
, 12);
1844 p
.thread_stickied
= !!sqlite3_column_int64(s2
, 13);
1845 wt_write_post(&p
, f
, first_post
, 1,
1846 conf
->boards
[board_idx
].name
,
1851 /* XXX: refactor into a prepared_post cleaner */
1858 free(p
.system_full_path
);
1859 free(p
.system_thumb_path
);
1864 ERROR_MESSAGE("sqlite3_step(): %s", sqlite3_errmsg(db
));
1872 sqlite3_clear_bindings(s
);
1878 * Clean up any memory from this file
1880 * Postconditions (success):
1882 * - Valgrind won't report any memory leaks from this file.
1884 * - setup_dbs() can be safely called again.
1888 /* Close board connections */
1889 for (size_t j
= 0; j
< num_connected_db
; ++j
) {
1890 FINALIZE_FOR_BOARD(j
, check_ban
);
1891 FINALIZE_FOR_BOARD(j
, check_cooldown
);
1892 FINALIZE_FOR_BOARD(j
, check_thread_exists
);
1893 FINALIZE_FOR_BOARD(j
, count_posts
);
1894 FINALIZE_FOR_BOARD(j
, delete_thread
);
1895 FINALIZE_FOR_BOARD(j
, delete_post
);
1896 FINALIZE_FOR_BOARD(j
, find_containing_thread
);
1897 FINALIZE_FOR_BOARD(j
, get_subject
);
1898 FINALIZE_FOR_BOARD(j
, get_thread_contents
);
1899 FINALIZE_FOR_BOARD(j
, get_thread_summary
);
1900 FINALIZE_FOR_BOARD(j
, get_post_contents
);
1901 FINALIZE_FOR_BOARD(j
, insert_ban
);
1902 FINALIZE_FOR_BOARD(j
, insert_comment
);
1903 FINALIZE_FOR_BOARD(j
, insert_comment_II
);
1904 FINALIZE_FOR_BOARD(j
, list_threads
);
1905 FINALIZE_FOR_BOARD(j
, set_cooldown
);
1906 FINALIZE_FOR_BOARD(j
, update_by_moderation
);
1907 FINALIZE_FOR_BOARD(j
, update_file_info
);
1910 sqlite3_close(board_dbs
[j
]);
1920 /* Clean up prepared board statements */
1921 CLEAN_UP_STATEMENT_ARRAY(check_ban
);
1922 CLEAN_UP_STATEMENT_ARRAY(check_cooldown
);
1923 CLEAN_UP_STATEMENT_ARRAY(check_thread_exists
);
1924 CLEAN_UP_STATEMENT_ARRAY(count_posts
);
1925 CLEAN_UP_STATEMENT_ARRAY(delete_thread
);
1926 CLEAN_UP_STATEMENT_ARRAY(delete_post
);
1927 CLEAN_UP_STATEMENT_ARRAY(find_containing_thread
);
1928 CLEAN_UP_STATEMENT_ARRAY(get_subject
);
1929 CLEAN_UP_STATEMENT_ARRAY(get_thread_contents
);
1930 CLEAN_UP_STATEMENT_ARRAY(get_thread_summary
);
1931 CLEAN_UP_STATEMENT_ARRAY(get_post_contents
);
1932 CLEAN_UP_STATEMENT_ARRAY(insert_ban
);
1933 CLEAN_UP_STATEMENT_ARRAY(insert_comment
);
1934 CLEAN_UP_STATEMENT_ARRAY(insert_comment_II
);
1935 CLEAN_UP_STATEMENT_ARRAY(list_threads
);
1936 CLEAN_UP_STATEMENT_ARRAY(set_cooldown
);
1937 CLEAN_UP_STATEMENT_ARRAY(update_by_moderation
);
1938 CLEAN_UP_STATEMENT_ARRAY(update_file_info
);
1940 /* Close global connection */
1941 sqlite3_finalize(global_check_ban_stmt
);
1942 global_check_ban_stmt
= 0;
1943 sqlite3_finalize(global_insert_ban_stmt
);
1944 global_insert_ban_stmt
= 0;
1947 sqlite3_close(global_db
);