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

Contents of /lbbs/src/section_list.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.25 - (show annotations)
Mon May 26 23:38:11 2025 UTC (9 months, 3 weeks ago) by sysadm
Branch: MAIN
Changes since 1.24: +8 -0 lines
Content type: text/x-csrc
Add article_block_last_aid()

1 /***************************************************************************
2 section_list.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 #define _GNU_SOURCE
18
19 #include "section_list.h"
20 #include "log.h"
21 #include "trie_dict.h"
22 #include <stdio.h>
23 #include <string.h>
24 #include <signal.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <errno.h>
28 #include <sys/param.h>
29 #include <sys/sem.h>
30 #include <sys/shm.h>
31 #include <sys/ipc.h>
32
33 #ifdef _SEM_SEMUN_UNDEFINED
34 union semun
35 {
36 int val; /* Value for SETVAL */
37 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
38 unsigned short *array; /* Array for GETALL, SETALL */
39 struct seminfo *__buf; /* Buffer for IPC_INFO
40 (Linux-specific) */
41 };
42 #endif // #ifdef _SEM_SEMUN_UNDEFINED
43
44 #define SECTION_TRY_LOCK_WAIT_TIME 1 // second
45 #define SECTION_TRY_LOCK_TIMES 10
46
47 #define ARTICLE_BLOCK_PER_SHM 400 // sizeof(ARTICLE_BLOCK) * ARTICLE_BLOCK_PER_SHM is the size of each shm segment to allocate
48 #define ARTICLE_BLOCK_SHM_COUNT_LIMIT 200 // limited by length (8-bit) of proj_id in ftok(path, proj_id)
49 #define ARTICLE_BLOCK_PER_POOL (ARTICLE_BLOCK_PER_SHM * ARTICLE_BLOCK_SHM_COUNT_LIMIT)
50
51 #define CALCULATE_PAGE_THRESHOLD 100 // Adjust to tune performance of move topic
52
53 #define SID_STR_LEN 5 // 32-bit + NULL
54
55 struct article_block_t
56 {
57 ARTICLE articles[ARTICLE_PER_BLOCK];
58 int article_count;
59 struct article_block_t *p_next_block;
60 };
61 typedef struct article_block_t ARTICLE_BLOCK;
62
63 struct article_block_pool_t
64 {
65 int shmid;
66 struct
67 {
68 int shmid;
69 void *p_shm;
70 } shm_pool[ARTICLE_BLOCK_SHM_COUNT_LIMIT];
71 int shm_count;
72 ARTICLE_BLOCK *p_block_free_list;
73 ARTICLE_BLOCK *p_block[ARTICLE_BLOCK_PER_POOL];
74 int block_count;
75 };
76 typedef struct article_block_pool_t ARTICLE_BLOCK_POOL;
77
78 static ARTICLE_BLOCK_POOL *p_article_block_pool = NULL;
79
80 struct section_list_pool_t
81 {
82 int shmid;
83 SECTION_LIST sections[BBS_max_section];
84 int section_count;
85 int semid;
86 TRIE_NODE *p_trie_dict_section_by_name;
87 TRIE_NODE *p_trie_dict_section_by_sid;
88 };
89 typedef struct section_list_pool_t SECTION_LIST_POOL;
90
91 static SECTION_LIST_POOL *p_section_list_pool = NULL;
92
93 int article_block_init(const char *filename, int block_count)
94 {
95 int shmid;
96 int proj_id;
97 key_t key;
98 size_t size;
99 void *p_shm;
100 int i;
101 int block_count_in_shm;
102 ARTICLE_BLOCK *p_block_in_shm;
103 ARTICLE_BLOCK **pp_block_next;
104
105 if (p_article_block_pool != NULL)
106 {
107 log_error("article_block_pool already initialized\n");
108 return -1;
109 }
110
111 if (block_count > ARTICLE_BLOCK_PER_POOL)
112 {
113 log_error("article_block_count exceed limit %d\n", ARTICLE_BLOCK_PER_POOL);
114 return -2;
115 }
116
117 // Allocate shared memory
118 proj_id = ARTICLE_BLOCK_SHM_COUNT_LIMIT; // keep different from proj_id used to create block shm
119 key = ftok(filename, proj_id);
120 if (key == -1)
121 {
122 log_error("ftok(%s, %d) error (%d)\n", filename, proj_id, errno);
123 return -3;
124 }
125
126 size = sizeof(ARTICLE_BLOCK_POOL);
127 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
128 if (shmid == -1)
129 {
130 log_error("shmget(article_block_pool_shm, size = %d) error (%d)\n", size, errno);
131 return -3;
132 }
133 p_shm = shmat(shmid, NULL, 0);
134 if (p_shm == (void *)-1)
135 {
136 log_error("shmat(shmid = %d) error (%d)\n", shmid, errno);
137 return -3;
138 }
139
140 p_article_block_pool = p_shm;
141 p_article_block_pool->shmid = shmid;
142
143 p_article_block_pool->shm_count = 0;
144 pp_block_next = &(p_article_block_pool->p_block_free_list);
145
146 while (block_count > 0)
147 {
148 block_count_in_shm = MIN(block_count, ARTICLE_BLOCK_PER_SHM);
149 block_count -= block_count_in_shm;
150
151 proj_id = p_article_block_pool->shm_count;
152 key = ftok(filename, proj_id);
153 if (key == -1)
154 {
155 log_error("ftok(%s, %d) error (%d)\n", filename, proj_id, errno);
156 article_block_cleanup();
157 return -3;
158 }
159
160 size = sizeof(ARTICLE_BLOCK) * (size_t)block_count_in_shm;
161 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
162 if (shmid == -1)
163 {
164 log_error("shmget(shm_index = %d, size = %d) error (%d)\n", p_article_block_pool->shm_count, size, errno);
165 article_block_cleanup();
166 return -3;
167 }
168 p_shm = shmat(shmid, NULL, 0);
169 if (p_shm == (void *)-1)
170 {
171 log_error("shmat(shmid = %d) error (%d)\n", shmid, errno);
172 article_block_cleanup();
173 return -3;
174 }
175
176 (p_article_block_pool->shm_pool + p_article_block_pool->shm_count)->shmid = shmid;
177 (p_article_block_pool->shm_pool + p_article_block_pool->shm_count)->p_shm = p_shm;
178 p_article_block_pool->shm_count++;
179
180 p_block_in_shm = p_shm;
181 *pp_block_next = p_block_in_shm;
182
183 for (i = 0; i < block_count_in_shm; i++)
184 {
185 if (i < block_count_in_shm - 1)
186 {
187 (p_block_in_shm + i)->p_next_block = (p_block_in_shm + i + 1);
188 }
189 else
190 {
191 (p_block_in_shm + i)->p_next_block = NULL;
192 pp_block_next = &((p_block_in_shm + i)->p_next_block);
193 }
194 }
195 }
196
197 p_article_block_pool->block_count = 0;
198
199 return 0;
200 }
201
202 void article_block_cleanup(void)
203 {
204 int shmid;
205
206 if (p_article_block_pool == NULL)
207 {
208 return;
209 }
210
211 for (int i = 0; i < p_article_block_pool->shm_count; i++)
212 {
213 if (shmdt((p_article_block_pool->shm_pool + i)->p_shm) == -1)
214 {
215 log_error("shmdt(shmid = %d) error (%d)\n", (p_article_block_pool->shm_pool + i)->shmid, errno);
216 }
217
218 if (shmctl((p_article_block_pool->shm_pool + i)->shmid, IPC_RMID, NULL) == -1)
219 {
220 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", (p_article_block_pool->shm_pool + i)->shmid, errno);
221 }
222 }
223
224 shmid = p_article_block_pool->shmid;
225
226 if (shmdt(p_article_block_pool) == -1)
227 {
228 log_error("shmdt(shmid = %d) error (%d)\n", shmid, errno);
229 }
230
231 if (shmctl(shmid, IPC_RMID, NULL) == -1)
232 {
233 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", shmid, errno);
234 }
235
236 p_article_block_pool = NULL;
237 }
238
239 inline static ARTICLE_BLOCK *pop_free_article_block(void)
240 {
241 ARTICLE_BLOCK *p_block = NULL;
242
243 if (p_article_block_pool->p_block_free_list != NULL)
244 {
245 p_block = p_article_block_pool->p_block_free_list;
246 p_article_block_pool->p_block_free_list = p_block->p_next_block;
247 p_block->p_next_block = NULL;
248 p_block->article_count = 0;
249 }
250
251 return p_block;
252 }
253
254 inline static void push_free_article_block(ARTICLE_BLOCK *p_block)
255 {
256 p_block->p_next_block = p_article_block_pool->p_block_free_list;
257 p_article_block_pool->p_block_free_list = p_block;
258 }
259
260 int article_block_reset(void)
261 {
262 ARTICLE_BLOCK *p_block;
263
264 if (p_article_block_pool == NULL)
265 {
266 log_error("article_block_pool not initialized\n");
267 return -1;
268 }
269
270 while (p_article_block_pool->block_count > 0)
271 {
272 p_article_block_pool->block_count--;
273 p_block = p_article_block_pool->p_block[p_article_block_pool->block_count];
274 push_free_article_block(p_block);
275 }
276
277 return 0;
278 }
279
280 ARTICLE *article_block_find_by_aid(int32_t aid)
281 {
282 ARTICLE_BLOCK *p_block;
283 int left;
284 int right;
285 int mid;
286
287 if (p_article_block_pool == NULL)
288 {
289 log_error("article_block_pool not initialized\n");
290 return NULL;
291 }
292
293 if (p_article_block_pool->block_count == 0) // empty
294 {
295 return NULL;
296 }
297
298 left = 0;
299 right = p_article_block_pool->block_count;
300
301 // aid in the range [ head aid of blocks[left], tail aid of blocks[right - 1] ]
302 while (left < right - 1)
303 {
304 // get block offset no less than mid value of left and right block offsets
305 mid = (left + right) / 2 + (right - left) % 2;
306
307 if (mid >= p_article_block_pool->block_count)
308 {
309 log_error("block(mid = %d) is out of boundary\n", mid);
310 return NULL;
311 }
312
313 if (aid < p_article_block_pool->p_block[mid]->articles[0].aid)
314 {
315 right = mid;
316 }
317 else
318 {
319 left = mid;
320 }
321 }
322
323 p_block = p_article_block_pool->p_block[left];
324
325 left = 0;
326 right = p_block->article_count - 1;
327
328 // aid in the range [ aid of articles[left], aid of articles[right] ]
329 while (left < right)
330 {
331 mid = (left + right) / 2;
332
333 if (aid <= p_block->articles[mid].aid)
334 {
335 right = mid;
336 }
337 else
338 {
339 left = mid + 1;
340 }
341 }
342
343 return (p_block->articles + left);
344 }
345
346 ARTICLE *article_block_find_by_index(int index)
347 {
348 ARTICLE_BLOCK *p_block;
349
350 if (p_article_block_pool == NULL)
351 {
352 log_error("article_block_pool not initialized\n");
353 return NULL;
354 }
355
356 if (index < 0 || index / ARTICLE_PER_BLOCK >= p_article_block_pool->block_count)
357 {
358 log_error("article_block_find_by_index(%d) is out of boundary of block [0, %d)\n", index, p_article_block_pool->block_count);
359 return NULL;
360 }
361
362 p_block = p_article_block_pool->p_block[index / ARTICLE_PER_BLOCK];
363
364 if (index % ARTICLE_PER_BLOCK >= p_block->article_count)
365 {
366 log_error("article_block_find_by_index(%d) is out of boundary of article [0, %d)\n", index, p_block->article_count);
367 return NULL;
368 }
369
370 return (p_block->articles + (index % ARTICLE_PER_BLOCK));
371 }
372
373 extern int section_list_init(const char *filename)
374 {
375 int semid;
376 int shmid;
377 int proj_id;
378 key_t key;
379 size_t size;
380 void *p_shm;
381 union semun arg;
382 int i;
383
384 if (p_section_list_pool != NULL)
385 {
386 section_list_cleanup();
387 }
388
389 proj_id = (int)(time(NULL) % getpid());
390 key = ftok(filename, proj_id);
391 if (key == -1)
392 {
393 log_error("ftok(%s, %d) error (%d)\n", filename, proj_id, errno);
394 return -3;
395 }
396
397 // Allocate shared memory
398 size = sizeof(SECTION_LIST_POOL);
399 shmid = shmget(key, size, IPC_CREAT | IPC_EXCL | 0600);
400 if (shmid == -1)
401 {
402 log_error("shmget(section_list_pool_shm, size = %d) error (%d)\n", size, errno);
403 return -3;
404 }
405 p_shm = shmat(shmid, NULL, 0);
406 if (p_shm == (void *)-1)
407 {
408 log_error("shmat(shmid = %d) error (%d)\n", shmid, errno);
409 return -3;
410 }
411
412 p_section_list_pool = p_shm;
413 p_section_list_pool->shmid = shmid;
414 p_section_list_pool->section_count = 0;
415
416 // Allocate semaphore as section locks
417 size = 2 * (BBS_max_section + 1); // r_sem and w_sem per section, the last pair for all sections
418 semid = semget(key, (int)size, IPC_CREAT | IPC_EXCL | 0600);
419 if (semid == -1)
420 {
421 log_error("semget(section_list_pool_sem, size = %d) error (%d)\n", size, errno);
422 return -3;
423 }
424
425 // Initialize sem value to 0
426 arg.val = 0;
427 for (i = 0; i < size; i++)
428 {
429 if (semctl(semid, i, SETVAL, arg) == -1)
430 {
431 log_error("semctl(section_list_pool_sem, SETVAL) error (%d)\n", errno);
432 return -3;
433 }
434 }
435
436 p_section_list_pool->semid = semid;
437
438 p_section_list_pool->p_trie_dict_section_by_name = trie_dict_create();
439 if (p_section_list_pool->p_trie_dict_section_by_name == NULL)
440 {
441 log_error("trie_dict_create() OOM\n", BBS_max_section);
442 return -2;
443 }
444
445 p_section_list_pool->p_trie_dict_section_by_sid = trie_dict_create();
446 if (p_section_list_pool->p_trie_dict_section_by_sid == NULL)
447 {
448 log_error("trie_dict_create() OOM\n", BBS_max_section);
449 return -2;
450 }
451
452 return 0;
453 }
454
455 inline static void sid_to_str(int32_t sid, char *p_sid_str)
456 {
457 uint32_t u_sid;
458 int i;
459
460 u_sid = (uint32_t)sid;
461 for (i = 0; i < SID_STR_LEN - 1; i++)
462 {
463 p_sid_str[i] = (char)(u_sid % 255 + 1);
464 u_sid /= 255;
465 }
466 p_sid_str[i] = '\0';
467 }
468
469 SECTION_LIST *section_list_create(int32_t sid, const char *sname, const char *stitle, const char *master_name)
470 {
471 SECTION_LIST *p_section;
472 char sid_str[SID_STR_LEN];
473
474 if (p_section_list_pool == NULL)
475 {
476 log_error("session_list_pool not initialized\n");
477 return NULL;
478 }
479
480 if (p_section_list_pool->section_count >= BBS_max_section)
481 {
482 log_error("section_count reach limit %d >= %d\n", p_section_list_pool->section_count, BBS_max_section);
483 return NULL;
484 }
485
486 sid_to_str(sid, sid_str);
487
488 p_section = p_section_list_pool->sections + p_section_list_pool->section_count;
489
490 p_section->sid = sid;
491
492 strncpy(p_section->sname, sname, sizeof(p_section->sname - 1));
493 p_section->sname[sizeof(p_section->sname - 1)] = '\0';
494
495 strncpy(p_section->stitle, stitle, sizeof(p_section->stitle - 1));
496 p_section->stitle[sizeof(p_section->stitle - 1)] = '\0';
497
498 strncpy(p_section->master_name, master_name, sizeof(p_section->master_name - 1));
499 p_section->master_name[sizeof(p_section->master_name - 1)] = '\0';
500
501 if (trie_dict_set(p_section_list_pool->p_trie_dict_section_by_name, sname, p_section_list_pool->section_count) != 1)
502 {
503 log_error("trie_dict_set(section, %s, %d) error\n", sname, p_section_list_pool->section_count);
504 return NULL;
505 }
506
507 if (trie_dict_set(p_section_list_pool->p_trie_dict_section_by_sid, sid_str, p_section_list_pool->section_count) != 1)
508 {
509 log_error("trie_dict_set(section, %d, %d) error\n", sid, p_section_list_pool->section_count);
510 return NULL;
511 }
512
513 section_list_reset_articles(p_section);
514
515 p_section_list_pool->section_count++;
516
517 return p_section;
518 }
519
520 void section_list_reset_articles(SECTION_LIST *p_section)
521 {
522 p_section->article_count = 0;
523 p_section->topic_count = 0;
524 p_section->visible_article_count = 0;
525 p_section->visible_topic_count = 0;
526 p_section->p_article_head = NULL;
527 p_section->p_article_tail = NULL;
528
529 p_section->page_count = 0;
530 p_section->last_page_visible_article_count = 0;
531 }
532
533 void section_list_cleanup(void)
534 {
535 int shmid;
536
537 if (p_section_list_pool == NULL)
538 {
539 return;
540 }
541
542 if (p_section_list_pool->p_trie_dict_section_by_name != NULL)
543 {
544 trie_dict_destroy(p_section_list_pool->p_trie_dict_section_by_name);
545 p_section_list_pool->p_trie_dict_section_by_name = NULL;
546 }
547
548 if (p_section_list_pool->p_trie_dict_section_by_sid != NULL)
549 {
550 trie_dict_destroy(p_section_list_pool->p_trie_dict_section_by_sid);
551 p_section_list_pool->p_trie_dict_section_by_sid = NULL;
552 }
553
554 if (p_section_list_pool != NULL)
555 {
556 if (semctl(p_section_list_pool->semid, 0, IPC_RMID) == -1)
557 {
558 log_error("semctl(semid = %d, IPC_RMID) error (%d)\n", p_section_list_pool->semid, errno);
559 }
560
561 shmid = p_section_list_pool->shmid;
562
563 if (shmdt(p_section_list_pool) == -1)
564 {
565 log_error("shmdt(shmid = %d) error (%d)\n", shmid, errno);
566 }
567
568 if (shmctl(shmid, IPC_RMID, NULL) == -1)
569 {
570 log_error("shmctl(shmid = %d, IPC_RMID) error (%d)\n", shmid, errno);
571 }
572
573 p_section_list_pool = NULL;
574 }
575 }
576
577 SECTION_LIST *section_list_find_by_name(const char *sname)
578 {
579 int64_t index;
580 int ret;
581
582 if (p_section_list_pool == NULL)
583 {
584 log_error("section_list not initialized\n");
585 return NULL;
586 }
587
588 ret = trie_dict_get(p_section_list_pool->p_trie_dict_section_by_name, sname, &index);
589 if (ret < 0)
590 {
591 log_error("trie_dict_get(section, %s) error\n", sname);
592 return NULL;
593 }
594 else if (ret == 0)
595 {
596 return NULL;
597 }
598
599 return (p_section_list_pool->sections + index);
600 }
601
602 SECTION_LIST *section_list_find_by_sid(int32_t sid)
603 {
604 int64_t index;
605 int ret;
606 char sid_str[SID_STR_LEN];
607
608 if (p_section_list_pool == NULL)
609 {
610 log_error("section_list not initialized\n");
611 return NULL;
612 }
613
614 sid_to_str(sid, sid_str);
615
616 ret = trie_dict_get(p_section_list_pool->p_trie_dict_section_by_sid, sid_str, &index);
617 if (ret < 0)
618 {
619 log_error("trie_dict_get(section, %d) error\n", sid);
620 return NULL;
621 }
622 else if (ret == 0)
623 {
624 return NULL;
625 }
626
627 return (p_section_list_pool->sections + index);
628 }
629
630 int section_list_append_article(SECTION_LIST *p_section, const ARTICLE *p_article_src)
631 {
632 ARTICLE_BLOCK *p_block;
633 int32_t last_aid = 0;
634 ARTICLE *p_article;
635 ARTICLE *p_topic_head;
636 ARTICLE *p_topic_tail;
637
638 if (p_section == NULL || p_article_src == NULL)
639 {
640 log_error("section_list_append_article() NULL pointer error\n");
641 return -1;
642 }
643
644 if (p_article_block_pool == NULL)
645 {
646 log_error("article_block_pool not initialized\n");
647 return -1;
648 }
649
650 if (p_section->sid != p_article_src->sid)
651 {
652 log_error("section_list_append_article() error: section sid %d != article sid %d\n", p_section->sid, p_article_src->sid);
653 return -2;
654 }
655
656 if (p_section->article_count >= BBS_article_limit_per_section)
657 {
658 log_error("section_list_append_article() error: article_count reach limit in section %d\n", p_section->sid);
659 return -2;
660 }
661
662 if (p_article_block_pool->block_count == 0 ||
663 p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->article_count >= ARTICLE_PER_BLOCK)
664 {
665 if ((p_block = pop_free_article_block()) == NULL)
666 {
667 log_error("pop_free_article_block() error\n");
668 return -2;
669 }
670
671 if (p_article_block_pool->block_count > 0)
672 {
673 last_aid = p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->articles[ARTICLE_PER_BLOCK - 1].aid;
674 }
675
676 p_article_block_pool->p_block[p_article_block_pool->block_count] = p_block;
677 p_article_block_pool->block_count++;
678 }
679 else
680 {
681 p_block = p_article_block_pool->p_block[p_article_block_pool->block_count - 1];
682 last_aid = p_block->articles[p_block->article_count - 1].aid;
683 }
684
685 // AID of articles should be strictly ascending
686 if (p_article_src->aid <= last_aid)
687 {
688 log_error("section_list_append_article(aid=%d) error: last_aid=%d\n", p_article_src->aid, last_aid);
689 return -3;
690 }
691
692 p_article = (p_block->articles + p_block->article_count);
693 p_block->article_count++;
694 p_section->article_count++;
695
696 // Copy article data
697 *p_article = *p_article_src;
698
699 if (p_article->visible)
700 {
701 p_section->visible_article_count++;
702 }
703
704 // Link appended article as tail node of topic bi-directional list
705 if (p_article->tid != 0)
706 {
707 p_topic_head = article_block_find_by_aid(p_article->tid);
708 if (p_topic_head == NULL)
709 {
710 log_error("search head of topic (aid=%d) error\n", p_article->tid);
711 return -4;
712 }
713
714 p_topic_tail = p_topic_head->p_topic_prior;
715 if (p_topic_tail == NULL)
716 {
717 log_error("tail of topic (aid=%d) is NULL\n", p_article->tid);
718 return -4;
719 }
720 }
721 else
722 {
723 p_section->topic_count++;
724
725 if (p_article->visible)
726 {
727 p_section->visible_topic_count++;
728 }
729
730 p_topic_head = p_article;
731 p_topic_tail = p_article;
732 }
733
734 p_article->p_topic_prior = p_topic_tail;
735 p_article->p_topic_next = p_topic_head;
736 p_topic_head->p_topic_prior = p_article;
737 p_topic_tail->p_topic_next = p_article;
738
739 // Link appended article as tail node of article bi-directional list
740 if (p_section->p_article_head == NULL)
741 {
742 p_section->p_article_head = p_article;
743 p_section->p_article_tail = p_article;
744 }
745 p_article->p_prior = p_section->p_article_tail;
746 p_article->p_next = p_section->p_article_head;
747 p_section->p_article_head->p_prior = p_article;
748 p_section->p_article_tail->p_next = p_article;
749 p_section->p_article_tail = p_article;
750
751 // Update page
752 if ((p_article->visible && p_section->last_page_visible_article_count % BBS_article_limit_per_page == 0) ||
753 p_section->article_count == 1)
754 {
755 p_section->p_page_first_article[p_section->page_count] = p_article;
756 p_section->page_count++;
757 p_section->last_page_visible_article_count = 0;
758 }
759
760 if (p_article->visible)
761 {
762 p_section->last_page_visible_article_count++;
763 }
764
765 return 0;
766 }
767
768 int section_list_set_article_visible(SECTION_LIST *p_section, int32_t aid, int8_t visible)
769 {
770 ARTICLE *p_article;
771 ARTICLE *p_reply;
772 int affected_count = 0;
773
774 if (p_section == NULL)
775 {
776 log_error("section_list_set_article_visible() NULL pointer error\n");
777 return -2;
778 }
779
780 p_article = article_block_find_by_aid(aid);
781 if (p_article == NULL)
782 {
783 return -1; // Not found
784 }
785
786 if (p_section->sid != p_article->sid)
787 {
788 log_error("section_list_set_article_visible() error: section sid %d != article sid %d\n", p_section->sid, p_article->sid);
789 return -2;
790 }
791
792 if (p_article->visible == visible)
793 {
794 return 0; // Already set
795 }
796
797 if (visible == 0) // 1 -> 0
798 {
799 p_section->visible_article_count--;
800
801 if (p_article->tid == 0)
802 {
803 p_section->visible_topic_count--;
804
805 // Set related visible replies to invisible
806 for (p_reply = p_article->p_topic_next; p_reply->tid != 0; p_reply = p_reply->p_topic_next)
807 {
808 if (p_reply->tid != aid)
809 {
810 log_error("Inconsistent tid = %d found in reply %d of topic %d\n", p_reply->tid, p_reply->aid, aid);
811 continue;
812 }
813
814 if (p_reply->visible == 1)
815 {
816 p_reply->visible = 0;
817 p_section->visible_article_count--;
818 affected_count++;
819 }
820 }
821 }
822 }
823 else // 0 -> 1
824 {
825 p_section->visible_article_count++;
826
827 if (p_article->tid == 0)
828 {
829 p_section->visible_topic_count++;
830 }
831 }
832
833 p_article->visible = visible;
834 affected_count++;
835
836 return affected_count;
837 }
838
839 ARTICLE *section_list_find_article_with_offset(SECTION_LIST *p_section, int32_t aid, int32_t *p_page, int32_t *p_offset, ARTICLE **pp_next)
840 {
841 ARTICLE *p_article;
842 int left;
843 int right;
844 int mid;
845
846 *p_page = -1;
847 *p_offset = -1;
848 *pp_next = NULL;
849
850 if (p_section == NULL)
851 {
852 log_error("section_list_find_article_with_offset() NULL pointer error\n");
853 return NULL;
854 }
855
856 if (p_section->article_count == 0) // empty
857 {
858 *p_page = 0;
859 *p_offset = 0;
860 return NULL;
861 }
862
863 left = 0;
864 right = p_section->page_count;
865
866 // aid in the range [ head aid of pages[left], tail aid of pages[right - 1] ]
867 while (left < right - 1)
868 {
869 // get page id no less than mid value of left page id and right page id
870 mid = (left + right) / 2 + (right - left) % 2;
871
872 if (mid >= p_section->page_count)
873 {
874 log_error("page id (mid = %d) is out of boundary\n", mid);
875 return NULL;
876 }
877
878 if (aid < p_section->p_page_first_article[mid]->aid)
879 {
880 right = mid;
881 }
882 else
883 {
884 left = mid;
885 }
886 }
887
888 *p_page = left;
889
890 p_article = p_section->p_page_first_article[*p_page];
891
892 // p_section->p_page_first_article[*p_page]->aid <= aid < p_section->p_page_first_article[*p_page + 1]->aid
893 right = (*p_page == MAX(0, p_section->page_count - 1) ? INT32_MAX : p_section->p_page_first_article[*p_page + 1]->aid);
894
895 // left will be the offset of article found or offset to insert
896 left = 0;
897
898 while (aid > p_article->aid)
899 {
900 p_article = p_article->p_next;
901 left++;
902
903 if (aid == p_article->aid)
904 {
905 *pp_next = p_article->p_next;
906 break;
907 }
908
909 // over last article in the page
910 if (p_article == p_section->p_article_head || p_article->aid >= right)
911 {
912 *pp_next = (p_article == p_section->p_article_head ? p_section->p_article_head : p_section->p_page_first_article[*p_page + 1]);
913 *p_offset = left;
914 return NULL; // not found
915 }
916 }
917
918 if (aid < p_article->aid)
919 {
920 *pp_next = p_article;
921 p_article = NULL; // not found
922 }
923 else // aid == p_article->aid
924 {
925 *pp_next = p_article->p_next;
926 }
927
928 *p_offset = left;
929
930 return p_article;
931 }
932
933 int section_list_calculate_page(SECTION_LIST *p_section, int32_t start_aid)
934 {
935 ARTICLE *p_article;
936 ARTICLE *p_next;
937 int32_t page;
938 int32_t offset;
939 int visible_article_count;
940 int page_head_set;
941
942 if (p_section == NULL)
943 {
944 log_error("section_list_calculate_page() NULL pointer error\n");
945 return -1;
946 }
947
948 if (p_section->article_count == 0) // empty
949 {
950 p_section->page_count = 0;
951 p_section->last_page_visible_article_count = 0;
952
953 return 0;
954 }
955
956 if (start_aid > 0)
957 {
958 p_article = article_block_find_by_aid(start_aid);
959 if (p_article == NULL)
960 {
961 return -1; // Not found
962 }
963
964 if (p_section->sid != p_article->sid)
965 {
966 log_error("section_list_calculate_page() error: section sid %d != start article sid %d\n", p_section->sid, p_article->sid);
967 return -2;
968 }
969
970 p_article = section_list_find_article_with_offset(p_section, start_aid, &page, &offset, &p_next);
971 if (p_article == NULL)
972 {
973 if (page < 0)
974 {
975 return -1;
976 }
977 log_error("section_list_calculate_page() aid = %d not found in section sid = %d\n",
978 start_aid, p_section->sid);
979 return -2;
980 }
981
982 if (offset > 0)
983 {
984 p_article = p_section->p_page_first_article[page];
985 }
986 }
987 else
988 {
989 p_article = p_section->p_article_head;
990 page = 0;
991 offset = 0;
992 }
993
994 visible_article_count = 0;
995 page_head_set = 0;
996
997 do
998 {
999 if (!page_head_set && visible_article_count == 0)
1000 {
1001 p_section->p_page_first_article[page] = p_article;
1002 page_head_set = 1;
1003 }
1004
1005 if (p_article->visible)
1006 {
1007 visible_article_count++;
1008 }
1009
1010 p_article = p_article->p_next;
1011
1012 // skip remaining invisible articles
1013 while (p_article->visible == 0 && p_article != p_section->p_article_head)
1014 {
1015 p_article = p_article->p_next;
1016 }
1017
1018 if (visible_article_count >= BBS_article_limit_per_page && p_article != p_section->p_article_head)
1019 {
1020 page++;
1021 visible_article_count = 0;
1022 page_head_set = 0;
1023
1024 if (page >= BBS_article_limit_per_section / BBS_article_limit_per_page && p_article != p_section->p_article_head)
1025 {
1026 log_error("Count of page exceed limit in section %d\n", p_section->sid);
1027 break;
1028 }
1029 }
1030 } while (p_article != p_section->p_article_head);
1031
1032 p_section->page_count = page + (visible_article_count > 0 ? 1 : 0);
1033 p_section->last_page_visible_article_count = visible_article_count;
1034
1035 return 0;
1036 }
1037
1038 int32_t article_block_last_aid(void)
1039 {
1040 ARTICLE_BLOCK *p_block = p_article_block_pool->p_block[p_article_block_pool->block_count - 1];
1041 int32_t last_aid = p_block->articles[p_block->article_count - 1].aid;
1042
1043 return last_aid;
1044 }
1045
1046 int article_count_of_topic(int32_t aid)
1047 {
1048 ARTICLE *p_article;
1049 int article_count;
1050
1051 p_article = article_block_find_by_aid(aid);
1052 if (p_article == NULL)
1053 {
1054 return 0; // Not found
1055 }
1056
1057 article_count = 0;
1058
1059 do
1060 {
1061 if (p_article->tid != 0 && p_article->tid != aid)
1062 {
1063 log_error("article_count_of_topic(%d) error: article %d not linked to the topic\n", aid, p_article->aid);
1064 break;
1065 }
1066
1067 article_count++;
1068 p_article = p_article->p_topic_next;
1069 } while (p_article->aid != aid);
1070
1071 return article_count;
1072 }
1073
1074 int section_list_move_topic(SECTION_LIST *p_section_src, SECTION_LIST *p_section_dest, int32_t aid)
1075 {
1076 ARTICLE *p_article;
1077 ARTICLE *p_next;
1078 int32_t page;
1079 int32_t offset;
1080 int32_t move_article_count;
1081 int32_t dest_article_count_old;
1082 int32_t last_unaffected_aid_src;
1083 int32_t first_inserted_aid_dest;
1084 int move_counter;
1085
1086 if (p_section_dest == NULL)
1087 {
1088 log_error("section_list_move_topic() NULL pointer error\n");
1089 return -1;
1090 }
1091
1092 if ((p_article = article_block_find_by_aid(aid)) == NULL)
1093 {
1094 log_error("section_list_move_topic() error: article %d not found in block\n", aid);
1095 return -2;
1096 }
1097
1098 if (p_section_src->sid != p_article->sid)
1099 {
1100 log_error("section_list_move_topic() error: src section sid %d != article %d sid %d\n",
1101 p_section_src->sid, p_article->aid, p_article->sid);
1102 return -2;
1103 }
1104
1105 if (p_article->tid != 0)
1106 {
1107 log_error("section_list_move_topic(aid = %d) error: article is not head of topic, tid = %d\n", aid, p_article->tid);
1108 return -2;
1109 }
1110
1111 last_unaffected_aid_src = (p_article == p_section_src->p_article_head ? 0 : p_article->p_prior->aid);
1112
1113 move_article_count = article_count_of_topic(aid);
1114 if (move_article_count <= 0)
1115 {
1116 log_error("article_count_of_topic(aid = %d) <= 0\n", aid);
1117 return -2;
1118 }
1119
1120 if (p_section_dest->article_count + move_article_count > BBS_article_limit_per_section)
1121 {
1122 log_error("section_list_move_topic() error: article_count %d reach limit in section %d\n",
1123 p_section_dest->article_count + move_article_count, p_section_dest->sid);
1124 return -3;
1125 }
1126
1127 dest_article_count_old = p_section_dest->article_count;
1128 move_counter = 0;
1129 first_inserted_aid_dest = p_article->aid;
1130
1131 do
1132 {
1133 if (p_section_src->sid != p_article->sid)
1134 {
1135 log_error("section_list_move_topic() error: src section sid %d != article %d sid %d\n",
1136 p_section_src->sid, p_article->aid, p_article->sid);
1137 return -2;
1138 }
1139
1140 // Remove from bi-directional article list of src section
1141 if (p_section_src->p_article_head == p_article)
1142 {
1143 p_section_src->p_article_head = p_article->p_next;
1144 }
1145 if (p_section_src->p_article_tail == p_article)
1146 {
1147 p_section_src->p_article_tail = p_article->p_prior;
1148 }
1149 if (p_section_src->p_article_head == p_article) // || p_section_src->p_article_tail == p_article
1150 {
1151 p_section_src->p_article_head = NULL;
1152 p_section_src->p_article_tail = NULL;
1153 }
1154
1155 p_article->p_prior->p_next = p_article->p_next;
1156 p_article->p_next->p_prior = p_article->p_prior;
1157
1158 // Update sid of article
1159 p_article->sid = p_section_dest->sid;
1160
1161 if (section_list_find_article_with_offset(p_section_dest, p_article->aid, &page, &offset, &p_next) != NULL)
1162 {
1163 log_error("section_list_move_topic() error: article %d already in section %d\n", p_article->aid, p_section_dest->sid);
1164 return -4;
1165 }
1166
1167 // Insert into bi-directional article list of dest section
1168 if (p_next == NULL) // empty section
1169 {
1170 p_section_dest->p_article_head = p_article;
1171 p_section_dest->p_article_tail = p_article;
1172 p_article->p_prior = p_article;
1173 p_article->p_next = p_article;
1174 }
1175 else
1176 {
1177 if (p_section_dest->p_article_head == p_next)
1178 {
1179 if (p_article->aid < p_next->aid)
1180 {
1181 p_section_dest->p_article_head = p_article;
1182 }
1183 else // p_article->aid > p_next->aid
1184 {
1185 p_section_dest->p_article_tail = p_article;
1186 }
1187 }
1188
1189 p_article->p_prior = p_next->p_prior;
1190 p_article->p_next = p_next;
1191 p_next->p_prior->p_next = p_article;
1192 p_next->p_prior = p_article;
1193 }
1194
1195 // Update article / topic counter of src / desc section
1196 p_section_src->article_count--;
1197 p_section_dest->article_count++;
1198 if (p_article->tid == 0)
1199 {
1200 p_section_src->topic_count--;
1201 p_section_dest->topic_count++;
1202 }
1203
1204 // Update visible article / topic counter of src / desc section
1205 if (p_article->visible)
1206 {
1207 p_section_src->visible_article_count--;
1208 p_section_dest->visible_article_count++;
1209 if (p_article->tid == 0)
1210 {
1211 p_section_src->visible_topic_count--;
1212 p_section_dest->visible_topic_count++;
1213 }
1214 }
1215
1216 // Update page for empty dest section
1217 if (p_section_dest->article_count == 1)
1218 {
1219 p_section_dest->p_page_first_article[0] = p_article;
1220 p_section_dest->page_count = 1;
1221 p_section_dest->last_page_visible_article_count = (p_article->visible ? 1 : 0);
1222 }
1223
1224 p_article = p_article->p_topic_next;
1225
1226 move_counter++;
1227 if (move_counter % CALCULATE_PAGE_THRESHOLD == 0)
1228 {
1229 // Re-calculate pages of desc section
1230 if (section_list_calculate_page(p_section_dest, first_inserted_aid_dest) < 0)
1231 {
1232 log_error("section_list_calculate_page(dest section = %d, aid = %d) error\n",
1233 p_section_dest->sid, first_inserted_aid_dest);
1234 }
1235
1236 first_inserted_aid_dest = p_article->aid;
1237 }
1238 } while (p_article->aid != aid);
1239
1240 if (p_section_dest->article_count - dest_article_count_old != move_article_count)
1241 {
1242 log_error("section_list_move_topic() error: count of moved articles %d != %d\n",
1243 p_section_dest->article_count - dest_article_count_old, move_article_count);
1244 }
1245
1246 // Re-calculate pages of src section
1247 if (section_list_calculate_page(p_section_src, last_unaffected_aid_src) < 0)
1248 {
1249 log_error("section_list_calculate_page(src section = %d, aid = %d) error at aid = %d\n",
1250 p_section_src->sid, last_unaffected_aid_src, aid);
1251 }
1252
1253 if (move_counter % CALCULATE_PAGE_THRESHOLD != 0)
1254 {
1255 // Re-calculate pages of desc section
1256 if (section_list_calculate_page(p_section_dest, first_inserted_aid_dest) < 0)
1257 {
1258 log_error("section_list_calculate_page(dest section = %d, aid = %d) error\n",
1259 p_section_dest->sid, first_inserted_aid_dest);
1260 }
1261 }
1262
1263 return move_article_count;
1264 }
1265
1266 int get_section_index(SECTION_LIST *p_section)
1267 {
1268 int index;
1269
1270 if (p_section_list_pool == NULL)
1271 {
1272 log_error("get_section_index() error: uninitialized\n");
1273 return -1;
1274 }
1275
1276 if (p_section == NULL)
1277 {
1278 index = BBS_max_section;
1279 }
1280 else
1281 {
1282 index = (int)(p_section - p_section_list_pool->sections);
1283 if (index < 0 || index >= BBS_max_section)
1284 {
1285 log_error("get_section_index(%d) error: index out of range\n", index);
1286 return -2;
1287 }
1288 }
1289
1290 return index;
1291 }
1292
1293 int section_list_try_rd_lock(SECTION_LIST *p_section, int wait_sec)
1294 {
1295 int index;
1296 struct sembuf sops[4];
1297 struct timespec timeout;
1298 int ret;
1299
1300 index = get_section_index(p_section);
1301 if (index < 0)
1302 {
1303 return -2;
1304 }
1305
1306 sops[0].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1307 sops[0].sem_op = 0; // wait until unlocked
1308 sops[0].sem_flg = 0;
1309
1310 sops[1].sem_num = (unsigned short)(index * 2); // r_sem of section index
1311 sops[1].sem_op = 1; // lock
1312 sops[1].sem_flg = SEM_UNDO; // undo on terminate
1313
1314 // Read lock on any specific section will also acquire single read lock on "all section"
1315 // so that write lock on all section only need to acquire single write on on "all section"
1316 // rather than to acquire multiple write locks on all the available sections.
1317 if (index != BBS_max_section)
1318 {
1319 sops[2].sem_num = BBS_max_section * 2 + 1; // w_sem of all section
1320 sops[2].sem_op = 0; // wait until unlocked
1321 sops[2].sem_flg = 0;
1322
1323 sops[3].sem_num = BBS_max_section * 2; // r_sem of all section
1324 sops[3].sem_op = 1; // lock
1325 sops[3].sem_flg = SEM_UNDO; // undo on terminate
1326 }
1327
1328 timeout.tv_sec = wait_sec;
1329 timeout.tv_nsec = 0;
1330
1331 ret = semtimedop(p_section_list_pool->semid, sops, (index == BBS_max_section ? 2 : 4), &timeout);
1332 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1333 {
1334 log_error("semtimedop(index = %d, lock read) error %d\n", index, errno);
1335 }
1336
1337 return ret;
1338 }
1339
1340 int section_list_try_rw_lock(SECTION_LIST *p_section, int wait_sec)
1341 {
1342 int index;
1343 struct sembuf sops[3];
1344 struct timespec timeout;
1345 int ret;
1346
1347 index = get_section_index(p_section);
1348 if (index < 0)
1349 {
1350 return -2;
1351 }
1352
1353 sops[0].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1354 sops[0].sem_op = 0; // wait until unlocked
1355 sops[0].sem_flg = 0;
1356
1357 sops[1].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1358 sops[1].sem_op = 1; // lock
1359 sops[1].sem_flg = SEM_UNDO; // undo on terminate
1360
1361 sops[2].sem_num = (unsigned short)(index * 2); // r_sem of section index
1362 sops[2].sem_op = 0; // wait until unlocked
1363 sops[2].sem_flg = 0;
1364
1365 timeout.tv_sec = wait_sec;
1366 timeout.tv_nsec = 0;
1367
1368 ret = semtimedop(p_section_list_pool->semid, sops, 3, &timeout);
1369 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1370 {
1371 log_error("semtimedop(index = %d, lock write) error %d\n", index, errno);
1372 }
1373
1374 return ret;
1375 }
1376
1377 int section_list_rd_unlock(SECTION_LIST *p_section)
1378 {
1379 int index;
1380 struct sembuf sops[2];
1381 int ret;
1382
1383 index = get_section_index(p_section);
1384 if (index < 0)
1385 {
1386 return -2;
1387 }
1388
1389 sops[0].sem_num = (unsigned short)(index * 2); // r_sem of section index
1390 sops[0].sem_op = -1; // unlock
1391 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
1392
1393 if (index != BBS_max_section)
1394 {
1395 sops[1].sem_num = BBS_max_section * 2; // r_sem of all section
1396 sops[1].sem_op = -1; // unlock
1397 sops[1].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
1398 }
1399
1400 ret = semop(p_section_list_pool->semid, sops, (index == BBS_max_section ? 1 : 2));
1401 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1402 {
1403 log_error("semop(index = %d, unlock read) error %d\n", index, errno);
1404 }
1405
1406 return ret;
1407 }
1408
1409 int section_list_rw_unlock(SECTION_LIST *p_section)
1410 {
1411 int index;
1412 struct sembuf sops[1];
1413 int ret;
1414
1415 index = get_section_index(p_section);
1416 if (index < 0)
1417 {
1418 return -2;
1419 }
1420
1421 sops[0].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1422 sops[0].sem_op = -1; // unlock
1423 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
1424
1425 ret = semop(p_section_list_pool->semid, sops, 1);
1426 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1427 {
1428 log_error("semop(index = %d, unlock write) error %d\n", index, errno);
1429 }
1430
1431 return ret;
1432 }
1433
1434 int section_list_rd_lock(SECTION_LIST *p_section)
1435 {
1436 int timer = 0;
1437 int sid = (p_section == NULL ? 0 : p_section->sid);
1438 int ret = -1;
1439
1440 while (!SYS_server_exit)
1441 {
1442 ret = section_list_try_rd_lock(p_section, SECTION_TRY_LOCK_WAIT_TIME);
1443 if (ret == 0) // success
1444 {
1445 break;
1446 }
1447 else if (errno == EAGAIN || errno == EINTR) // retry
1448 {
1449 timer++;
1450 if (timer % SECTION_TRY_LOCK_TIMES == 0)
1451 {
1452 log_error("section_list_rd_lock() tried %d times on section %d\n", sid, timer);
1453 }
1454 }
1455 else // failed
1456 {
1457 log_error("section_list_rd_lock() failed on section %d\n", sid);
1458 break;
1459 }
1460 }
1461
1462 return ret;
1463 }
1464
1465 int section_list_rw_lock(SECTION_LIST *p_section)
1466 {
1467 int timer = 0;
1468 int sid = (p_section == NULL ? 0 : p_section->sid);
1469 int ret = -1;
1470
1471 while (!SYS_server_exit)
1472 {
1473 ret = section_list_try_rw_lock(p_section, SECTION_TRY_LOCK_WAIT_TIME);
1474 if (ret == 0) // success
1475 {
1476 break;
1477 }
1478 else if (errno == EAGAIN || errno == EINTR) // retry
1479 {
1480 timer++;
1481 if (timer % SECTION_TRY_LOCK_TIMES == 0)
1482 {
1483 log_error("acquire_section_rw_lock() tried %d times on section %d\n", sid, timer);
1484 }
1485 }
1486 else // failed
1487 {
1488 log_error("acquire_section_rw_lock() failed on section %d\n", sid);
1489 break;
1490 }
1491 }
1492
1493 return ret;
1494 }

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