/[LeafOK_CVS]/lbbs/src/section_list_loader.c
ViewVC logotype

Contents of /lbbs/src/section_list_loader.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (show annotations)
Thu May 29 00:52:09 2025 UTC (9 months, 2 weeks ago) by sysadm
Branch: MAIN
Changes since 1.8: +74 -24 lines
Content type: text/x-csrc
Refine

1 /***************************************************************************
2 section_list_loader.c - description
3 -------------------
4 Copyright : (C) 2004-2025 by Leaflet
5 Email : leaflet@leafok.com
6 ***************************************************************************/
7
8 /***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 3 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "section_list_loader.h"
18 #include "log.h"
19 #include "database.h"
20 #include "menu.h"
21 #include <stdio.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <signal.h>
25 #include <stdlib.h>
26 #include <strings.h>
27 #include <unistd.h>
28
29 #define _POSIX_C_SOURCE 200809L
30 #include <string.h>
31
32 #define SECTION_LIST_LOAD_INTERVAL 10 // second
33
34 int section_list_loader_pid;
35 int last_article_op_log_mid;
36
37 int load_section_config_from_db(void)
38 {
39 MYSQL *db;
40 MYSQL_RES *rs, *rs2;
41 MYSQL_ROW row, row2;
42 char sql[SQL_BUFFER_LEN];
43 int32_t sid;
44 char master_list[(BBS_username_max_len + 1) * 3 + 1];
45 SECTION_LIST *p_section;
46 int ret;
47
48 db = db_open();
49 if (db == NULL)
50 {
51 log_error("db_open() error: %s\n", mysql_error(db));
52 return -2;
53 }
54
55 snprintf(sql, sizeof(sql),
56 "SELECT section_config.SID, sname, section_config.title, section_config.CID, "
57 "read_user_level, write_user_level, section_config.enable * section_class.enable AS enable "
58 "FROM section_config INNER JOIN section_class ON section_config.CID = section_class.CID "
59 "ORDER BY section_config.SID");
60
61 if (mysql_query(db, sql) != 0)
62 {
63 log_error("Query section_list error: %s\n", mysql_error(db));
64 return -3;
65 }
66 if ((rs = mysql_store_result(db)) == NULL)
67 {
68 log_error("Get section_list data failed\n");
69 return -3;
70 }
71
72 ret = 0;
73 while ((row = mysql_fetch_row(rs)))
74 {
75 sid = atoi(row[0]);
76
77 // Query section master
78 snprintf(sql, sizeof(sql),
79 "SELECT username FROM section_master "
80 "INNER JOIN user_list ON section_master.UID = user_list.UID "
81 "WHERE SID = %d AND section_master.enable AND (NOW() BETWEEN begin_dt AND end_dt) "
82 "ORDER BY major DESC, begin_dt ASC LIMIT 3",
83 sid);
84
85 if (mysql_query(db, sql) != 0)
86 {
87 log_error("Query section_master error: %s\n", mysql_error(db));
88 ret = -3;
89 break;
90 }
91 if ((rs2 = mysql_store_result(db)) == NULL)
92 {
93 log_error("Get section_master data failed\n");
94 ret = -3;
95 break;
96 }
97
98 master_list[0] = '\0';
99 while ((row2 = mysql_fetch_row(rs2)))
100 {
101 strncat(master_list, row2[0], sizeof(master_list) - 1 - strnlen(master_list, sizeof(master_list)));
102 strncat(master_list, " ", sizeof(master_list) - 1 - strnlen(master_list, sizeof(master_list)));
103 }
104 mysql_free_result(rs2);
105
106 p_section = section_list_find_by_sid(sid);
107
108 if (p_section == NULL)
109 {
110 p_section = section_list_create(sid, row[1], row[2], master_list);
111 if (p_section == NULL)
112 {
113 log_error("section_list_create() error: load new section sid = %d sname = %s\n", sid, row[1]);
114 ret = -4;
115 break;
116 }
117
118 // acquire rw lock
119 ret = section_list_rw_lock(p_section);
120 if (ret < 0)
121 {
122 break;
123 }
124 }
125 else
126 {
127 // acquire rw lock
128 ret = section_list_rw_lock(p_section);
129 if (ret < 0)
130 {
131 break;
132 }
133
134 strncpy(p_section->sname, row[1], sizeof(p_section->sname) - 1);
135 p_section->sname[sizeof(p_section->sname) - 1] = '\0';
136 strncpy(p_section->stitle, row[1], sizeof(p_section->stitle) - 1);
137 p_section->stitle[sizeof(p_section->stitle) - 1] = '\0';
138 strncpy(p_section->master_list, master_list, sizeof(p_section->master_list) - 1);
139 p_section->master_list[sizeof(p_section->master_list) - 1] = '\0';
140 }
141
142 p_section->class_id = atoi(row[3]);
143 p_section->read_user_level = atoi(row[4]);
144 p_section->write_user_level = atoi(row[5]);
145 p_section->enable = (int8_t)atoi(row[6]);
146
147 // release rw lock
148 ret = section_list_rw_unlock(p_section);
149 if (ret < 0)
150 {
151 break;
152 }
153 }
154 mysql_free_result(rs);
155
156 mysql_close(db);
157
158 return ret;
159 }
160
161 int append_articles_from_db(int32_t start_aid, int global_lock, int article_count_limit)
162 {
163 MYSQL *db;
164 MYSQL_RES *rs;
165 MYSQL_ROW row;
166 char sql[SQL_BUFFER_LEN];
167 ARTICLE article;
168 ARTICLE *p_topic;
169 SECTION_LIST *p_section = NULL;
170 int32_t last_sid = 0;
171 int article_count = 0;
172 int ret = 0;
173 int i;
174
175 db = db_open();
176 if (db == NULL)
177 {
178 log_error("db_open() error: %s\n", mysql_error(db));
179 return -2;
180 }
181
182 snprintf(sql, sizeof(sql),
183 "SELECT AID, TID, SID, CID, UID, visible, excerption, ontop, `lock`, "
184 "transship, username, nickname, title, UNIX_TIMESTAMP(sub_dt) AS sub_dt "
185 "FROM bbs WHERE AID >= %d ORDER BY AID LIMIT %d",
186 start_aid, article_count_limit);
187
188 if (mysql_query(db, sql) != 0)
189 {
190 log_error("Query article list error: %s\n", mysql_error(db));
191 return -3;
192 }
193 if ((rs = mysql_store_result(db)) == NULL)
194 {
195 log_error("Get article list data failed\n");
196 return -3;
197 }
198
199 // acquire global lock
200 if (global_lock)
201 {
202 if ((ret = section_list_rw_lock(NULL)) < 0)
203 {
204 log_error("section_list_rw_lock(sid = 0) error\n");
205 goto cleanup;
206 }
207 }
208
209 while ((row = mysql_fetch_row(rs)))
210 {
211 bzero(&article, sizeof(ARTICLE));
212
213 // copy data of article
214 i = 0;
215
216 article.aid = atoi(row[i++]);
217 article.tid = atoi(row[i++]);
218 article.sid = atoi(row[i++]);
219 article.cid = atoi(row[i++]);
220 article.uid = atoi(row[i++]);
221 article.visible = (int8_t)atoi(row[i++]);
222 article.excerption = (int8_t)atoi(row[i++]);
223 article.ontop = (int8_t)atoi(row[i++]);
224 article.lock = (int8_t)atoi(row[i++]);
225 article.transship = (int8_t)atoi(row[i++]);
226
227 strncpy(article.username, row[i++], sizeof(article.username) - 1);
228 article.username[sizeof(article.username) - 1] = '\0';
229 strncpy(article.nickname, row[i++], sizeof(article.nickname) - 1);
230 article.nickname[sizeof(article.nickname) - 1] = '\0';
231 strncpy(article.title, row[i++], sizeof(article.title) - 1);
232 article.title[sizeof(article.title) - 1] = '\0';
233
234 article.sub_dt = atol(row[i++]);
235
236 // release lock of last section if different from current one
237 if (!global_lock && article.sid != last_sid && last_sid != 0)
238 {
239 if ((ret = section_list_rw_unlock(p_section)) < 0)
240 {
241 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
242 break;
243 }
244 }
245
246 if ((p_section = section_list_find_by_sid(article.sid)) == NULL)
247 {
248 log_error("section_list_find_by_sid(%d) error: unknown section, try reloading section config\n", article.sid);
249 ret = ERR_UNKNOWN_SECTION; // Unknown section found
250 break;
251 }
252
253 if (article.visible != 0 && article.tid != 0)
254 {
255 // Check if topic article is visible
256 p_topic = article_block_find_by_aid(article.tid);
257 if (p_topic == NULL || p_topic->visible == 0)
258 {
259 // log_error("Set article (aid = %d) as invisible due to invisible or non-existing topic head\n", article.aid);
260 article.tid = 0;
261 article.visible = 0;
262 }
263 }
264
265 // acquire lock of current section if different from last one
266 if (!global_lock && article.sid != last_sid)
267 {
268 if ((ret = section_list_rw_lock(p_section)) < 0)
269 {
270 log_error("section_list_rw_lock(sid = 0) error\n");
271 break;
272 }
273 }
274
275 // append article to section list
276 last_sid = article.sid;
277
278 if (section_list_append_article(p_section, &article) < 0)
279 {
280 log_error("section_list_append_article(sid = %d, aid = %d) error\n",
281 p_section->sid, article.aid);
282 ret = -3;
283 break;
284 }
285
286 article_count++;
287
288 // TODO: generate content cache
289 }
290
291 // release lock of last section
292 if (!global_lock && last_sid != 0)
293 {
294 if ((ret = section_list_rw_unlock(p_section)) < 0)
295 {
296 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
297 }
298 }
299
300 // release global lock
301 if (global_lock)
302 {
303 if ((ret = section_list_rw_unlock(NULL)) < 0)
304 {
305 log_error("section_list_rw_unlock(sid = 0) error\n");
306 }
307 }
308
309 cleanup:
310 mysql_free_result(rs);
311
312 mysql_close(db);
313
314 return (ret < 0 ? ret : article_count);
315 }
316
317 int set_last_article_op_log_from_db(void)
318 {
319 MYSQL *db;
320 MYSQL_RES *rs;
321 MYSQL_ROW row;
322 char sql[SQL_BUFFER_LEN];
323
324 db = db_open();
325 if (db == NULL)
326 {
327 log_error("db_open() error: %s\n", mysql_error(db));
328 return -1;
329 }
330
331 snprintf(sql, sizeof(sql),
332 "SELECT MID FROM bbs_article_op ORDER BY MID DESC LIMIT 1");
333
334 if (mysql_query(db, sql) != 0)
335 {
336 log_error("Query article op error: %s\n", mysql_error(db));
337 return -2;
338 }
339 if ((rs = mysql_store_result(db)) == NULL)
340 {
341 log_error("Get article op data failed\n");
342 return -2;
343 }
344
345 if ((row = mysql_fetch_row(rs)))
346 {
347 last_article_op_log_mid = atoi(row[0]);
348 }
349
350 mysql_free_result(rs);
351
352 mysql_close(db);
353
354 return last_article_op_log_mid;
355 }
356
357 int apply_article_op_log_from_db(int op_count_limit)
358 {
359 MYSQL *db;
360 MYSQL_RES *rs, *rs2;
361 MYSQL_ROW row, row2;
362 char sql[SQL_BUFFER_LEN];
363 ARTICLE *p_article;
364 SECTION_LIST *p_section = NULL;
365 SECTION_LIST *p_section_dest;
366 int32_t last_sid = 0;
367 int32_t sid_dest;
368 int op_count = 0;
369 int ret = 0;
370
371 db = db_open();
372 if (db == NULL)
373 {
374 log_error("db_open() error: %s\n", mysql_error(db));
375 return -3;
376 }
377
378 snprintf(sql, sizeof(sql),
379 "SELECT MID, AID, type FROM bbs_article_op "
380 "WHERE MID > %d AND type NOT IN ('A') "
381 "ORDER BY MID LIMIT %d",
382 last_article_op_log_mid, op_count_limit);
383
384 if (mysql_query(db, sql) != 0)
385 {
386 log_error("Query article log error: %s\n", mysql_error(db));
387 return -3;
388 }
389 if ((rs = mysql_store_result(db)) == NULL)
390 {
391 log_error("Get article log data failed\n");
392 return -3;
393 }
394
395 while ((row = mysql_fetch_row(rs)))
396 {
397 p_article = article_block_find_by_aid(atoi(row[1]));
398 if (p_article == NULL) // related article has not been appended yet
399 {
400 ret = -2;
401 break;
402 }
403
404 // release lock of last section if different from current one
405 if (p_article->sid != last_sid && last_sid != 0)
406 {
407 if ((ret = section_list_rw_unlock(p_section)) < 0)
408 {
409 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
410 break;
411 }
412 }
413
414 if ((p_section = section_list_find_by_sid(p_article->sid)) == NULL)
415 {
416 log_error("section_list_find_by_sid(%d) error: unknown section, try reloading section config\n", p_article->sid);
417 ret = ERR_UNKNOWN_SECTION; // Unknown section found
418 break;
419 }
420
421 // acquire lock of current section if different from last one
422 if (p_article->sid != last_sid)
423 {
424 if ((ret = section_list_rw_lock(p_section)) < 0)
425 {
426 log_error("section_list_rw_lock(sid = 0) error\n");
427 break;
428 }
429 }
430
431 last_sid = p_article->sid;
432
433 switch (row[2][0])
434 {
435 case 'A': // Add article
436 log_error("Operation type=A should not be found\n");
437 break;
438 case 'D': // Delete article
439 case 'X': // Delete article by Admin
440 p_article->visible = 0;
441 if (p_article->tid == 0)
442 {
443 // Set articles in the topic to be invisible
444 do
445 {
446 p_article = p_article->p_topic_next;
447 p_article->visible = 0;
448 } while (p_article->tid != 0);
449 }
450 break;
451 case 'S': // Restore article
452 p_article->visible = 1;
453 break;
454 case 'L': // Lock article
455 p_article->lock = 1;
456 break;
457 case 'U': // Unlock article
458 p_article->lock = 0;
459 break;
460 case 'M': // Modify article
461 snprintf(sql, sizeof(sql),
462 "SELECT CID FROM bbs WHERE AID = %d",
463 p_article->aid);
464
465 if (mysql_query(db, sql) != 0)
466 {
467 log_error("Query article error: %s\n", mysql_error(db));
468 ret = -3;
469 break;
470 }
471 if ((rs2 = mysql_store_result(db)) == NULL)
472 {
473 log_error("Get article data failed\n");
474 ret = -3;
475 break;
476 }
477 if ((row2 = mysql_fetch_row(rs2)))
478 {
479 p_article->cid = atoi(row2[0]);
480 }
481 else
482 {
483 p_article->cid = 0;
484 ret = -4;
485 }
486 mysql_free_result(rs2);
487
488 if (p_article->cid > 0)
489 {
490 // TODO: generate content cache
491 }
492
493 break;
494 case 'T': // Move article
495 snprintf(sql, sizeof(sql),
496 "SELECT SID FROM bbs WHERE AID = %d",
497 p_article->aid);
498
499 if (mysql_query(db, sql) != 0)
500 {
501 log_error("Query article error: %s\n", mysql_error(db));
502 ret = -3;
503 break;
504 }
505 if ((rs2 = mysql_store_result(db)) == NULL)
506 {
507 log_error("Get article data failed\n");
508 ret = -3;
509 break;
510 }
511 if ((row2 = mysql_fetch_row(rs2)))
512 {
513 sid_dest = atoi(row2[0]);
514 }
515 else
516 {
517 sid_dest = 0;
518 ret = -4;
519 }
520 mysql_free_result(rs2);
521
522 if (sid_dest > 0 && sid_dest != p_article->sid)
523 {
524 p_section_dest = section_list_find_by_sid(sid_dest);
525 if (p_section_dest == NULL)
526 {
527 ret = ERR_UNKNOWN_SECTION;
528 break;
529 }
530 // acquire lock of dest section
531 if ((ret = section_list_rw_lock(p_section_dest)) < 0)
532 {
533 log_error("section_list_rw_lock(sid = %d) error\n", p_section_dest);
534 break;
535 }
536 // Move topic
537 if ((ret = section_list_move_topic(p_section, p_section_dest, p_article->aid)) < 0)
538 {
539 log_error("section_list_move_topic(src_sid=%d, dest_sid=%d, aid=%d) error (%d), retry in the next loop\n",
540 p_section->sid, p_section_dest->sid, p_article->aid, ret);
541 }
542 // release lock of dest section
543 if (section_list_rw_unlock(p_section_dest) < 0)
544 {
545 log_error("section_list_rw_unlock(sid = %d) error\n", p_section_dest);
546 ret = -1;
547 }
548 }
549 break;
550 case 'E': // Set article as excerption
551 p_article->excerption = 1;
552 break;
553 case 'O': // Unset article as excerption
554 p_article->excerption = 0;
555 break;
556 case 'F': // Set article on top
557 p_article->ontop = 1;
558 break;
559 case 'V': // Unset article on top
560 p_article->ontop = 0;
561 break;
562 case 'Z': // Set article as trnasship
563 p_article->transship = 1;
564 break;
565 default:
566 // log_error("Operation type=%s unknown, mid=%s\n", row[2], row[0]);
567 break;
568 }
569
570 if (ret < 0)
571 {
572 break;
573 }
574
575 // Update MID with last successfully proceeded article_op_log
576 last_article_op_log_mid = atoi(row[0]);
577
578 op_count++;
579 }
580
581 // release lock of last section
582 if (last_sid != 0)
583 {
584 if ((ret = section_list_rw_unlock(p_section)) < 0)
585 {
586 log_error("section_list_rw_unlock(sid = %d) error\n", p_section->sid);
587 }
588 }
589
590 mysql_free_result(rs);
591
592 mysql_close(db);
593
594 return (ret < 0 ? ret : op_count);
595 }
596
597 int section_list_loader_launch(void)
598 {
599 int pid;
600 int ret;
601 int32_t last_aid;
602 int article_count;
603 int load_count;
604 int last_mid;
605 int i;
606
607 if (section_list_loader_pid != 0)
608 {
609 log_error("section_list_loader already running, pid = %d\n", section_list_loader_pid);
610 return -2;
611 }
612
613 pid = fork();
614
615 if (pid > 0) // Parent process
616 {
617 SYS_child_process_count++;
618 section_list_loader_pid = pid;
619 log_std("Section list loader process (%d) start\n", pid);
620 return 0;
621 }
622 else if (pid < 0) // Error
623 {
624 log_error("fork() error (%d)\n", errno);
625 return -1;
626 }
627
628 // Child process
629 SYS_child_process_count = 0;
630
631 // Detach menu in shared memory
632 detach_menu_shm(p_bbs_menu);
633 free(p_bbs_menu);
634 p_bbs_menu = NULL;
635
636 // Do section data loader periodically
637 while (!SYS_server_exit)
638 {
639 if (SYS_section_list_reload)
640 {
641 SYS_section_list_reload = 0;
642
643 // Load section config
644 if (load_section_config_from_db() < 0)
645 {
646 log_error("load_section_config_from_db() error\n");
647 }
648 else
649 {
650 log_error("Reload section config successfully\n");
651 }
652 }
653
654 // Load section articles
655 article_count = article_block_article_count();
656
657 do
658 {
659 last_aid = article_block_last_aid();
660
661 if ((ret = append_articles_from_db(last_aid + 1, 0, LOAD_ARTICLE_COUNT_LIMIT)) < 0)
662 {
663 log_error("append_articles_from_db(%d, 0, %d) error\n", last_aid + 1, LOAD_ARTICLE_COUNT_LIMIT);
664
665 if (ret == ERR_UNKNOWN_SECTION)
666 {
667 SYS_section_list_reload = 1; // Force reload section_list
668 }
669 }
670 } while (ret == LOAD_ARTICLE_COUNT_LIMIT);
671
672 load_count = article_block_article_count() - article_count;
673 if (load_count > 0)
674 {
675 log_std("Incrementally load %d articles, last_aid = %d\n", load_count, article_block_last_aid());
676 }
677
678 if (SYS_section_list_reload)
679 {
680 continue;
681 }
682
683 // Load article_op log
684 last_mid = last_article_op_log_mid;
685
686 do
687 {
688 if ((ret = apply_article_op_log_from_db(LOAD_ARTICLE_COUNT_LIMIT)) < 0)
689 {
690 log_error("apply_article_op_log_from_db() error\n");
691
692 if (ret == ERR_UNKNOWN_SECTION)
693 {
694 SYS_section_list_reload = 1; // Force reload section_list
695 }
696 }
697 } while (ret == LOAD_ARTICLE_COUNT_LIMIT);
698
699 if (last_article_op_log_mid > last_mid)
700 {
701 log_std("Proceeded %d article logs, last_mid = %d\n", last_article_op_log_mid - last_mid, last_article_op_log_mid);
702 }
703
704 if (SYS_section_list_reload)
705 {
706 continue;
707 }
708
709 for (i = 0; i < SECTION_LIST_LOAD_INTERVAL && !SYS_server_exit && !SYS_section_list_reload; i++)
710 {
711 sleep(1);
712 }
713 }
714
715 // Child process exit
716
717 // Detach data pools shm
718 detach_section_list_shm();
719 detach_article_block_shm();
720 detach_trie_dict_shm();
721
722 log_std("Section list loader process exit normally\n");
723 log_end();
724
725 section_list_loader_pid = 0;
726
727 _exit(0);
728
729 return 0;
730 }
731
732 int section_list_loader_reload(void)
733 {
734 if (section_list_loader_pid == 0)
735 {
736 log_error("section_list_loader not running\n");
737 return -2;
738 }
739
740 if (kill(section_list_loader_pid, SIGHUP) < 0)
741 {
742 log_error("Send SIGTERM signal failed (%d)\n", errno);
743 return -1;
744 }
745
746 return 0;
747 }
748
749 int query_section_articles(SECTION_LIST *p_section, int32_t page_id, ARTICLE *p_articles[], int32_t *p_article_count)
750 {
751 ARTICLE *p_article;
752 ARTICLE *p_next_page_first_article;
753 int ret = 0;
754
755 if (p_section == NULL || p_articles == NULL || p_article_count == NULL)
756 {
757 log_error("query_section_articles() NULL pointer error\n");
758 return -1;
759 }
760
761 // acquire lock of section
762 if ((ret = section_list_rd_lock(p_section)) < 0)
763 {
764 log_error("section_list_rd_lock(sid = %d) error\n", p_section->sid);
765 return -2;
766 }
767
768 if (p_section->visible_article_count == 0)
769 {
770 *p_article_count = 0;
771 }
772 else if (page_id < 0 || page_id >= p_section->page_count)
773 {
774 log_error("Invalid page_id=%d, not in range [0, %d)\n", page_id, p_section->page_count);
775 ret = -3;
776 }
777 else
778 {
779 ret = page_id;
780 p_article = p_section->p_page_first_article[page_id];
781 p_next_page_first_article =
782 (page_id == p_section->page_count - 1 ? p_section->p_article_head : p_section->p_page_first_article[page_id + 1]);
783 *p_article_count = 0;
784
785 do
786 {
787 if (p_article->visible)
788 {
789 p_articles[*p_article_count] = p_article;
790 (*p_article_count)++;
791 }
792 p_article = p_article->p_next;
793 } while (p_article != p_next_page_first_article && (*p_article_count) <= BBS_article_limit_per_page);
794
795 if (*p_article_count != (page_id < p_section->page_count - 1 ? BBS_article_limit_per_page : p_section->last_page_visible_article_count))
796 {
797 log_error("Inconsistent visible article count %d detected in section %d page %d\n", *p_article_count, p_section->sid, page_id);
798 }
799 }
800
801 // release lock of section
802 if (section_list_rd_unlock(p_section) < 0)
803 {
804 log_error("section_list_rd_unlock(sid = %d) error\n", p_section->sid);
805 ret = -2;
806 }
807
808 return ret;
809 }

webmaster@leafok.com
ViewVC Help
Powered by ViewVC 1.3.0-beta1