/[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.68 - (show annotations)
Wed Jan 7 14:39:16 2026 UTC (2 months, 1 week ago) by sysadm
Branch: MAIN
CVS Tags: HEAD
Changes since 1.67: +18 -9 lines
Content type: text/x-csrc
Merge pull request [#88](https://github.com/leafok/lbbs/issues/88) from 0xfullex/fix/section-list-overflow
fix(section_list): fix binary search overflow and linked list boundary

Refine code based on suggestion provided in PR [#87](https://github.com/leafok/lbbs/issues/87)

1 /* SPDX-License-Identifier: GPL-3.0-or-later */
2 /*
3 * section_list
4 * - data models and basic operations of section and article
5 *
6 * Copyright (C) 2004-2026 Leaflet <leaflet@leafok.com>
7 */
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include "log.h"
14 #include "section_list.h"
15 #include "trie_dict.h"
16 #include "user_list.h"
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <signal.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <sys/mman.h>
25 #include <sys/param.h>
26 #include <sys/sem.h>
27 #include <sys/stat.h>
28
29 #if defined(_SEM_SEMUN_UNDEFINED) || defined(__CYGWIN__)
30 union semun
31 {
32 int val; /* Value for SETVAL */
33 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
34 unsigned short *array; /* Array for GETALL, SETALL */
35 struct seminfo *__buf; /* Buffer for IPC_INFO
36 (Linux-specific) */
37 };
38 #endif // #if defined(_SEM_SEMUN_UNDEFINED)
39
40 enum _section_list_constant_t
41 {
42 SECTION_TRY_LOCK_WAIT_TIME = 1, // second
43 SECTION_TRY_LOCK_TIMES = 10,
44 SECTION_DEAD_LOCK_TIMEOUT = 15, // second
45
46 ARTICLE_BLOCK_PER_SHM = 1000, // sizeof(ARTICLE_BLOCK) * ARTICLE_BLOCK_PER_SHM is the size of each shm segment to allocate
47 ARTICLE_BLOCK_SHM_COUNT_LIMIT = 80, // limited by length (8-bit) of proj_id in ftok(path, proj_id)
48 ARTICLE_BLOCK_PER_POOL = (ARTICLE_BLOCK_PER_SHM * ARTICLE_BLOCK_SHM_COUNT_LIMIT),
49
50 CALCULATE_PAGE_THRESHOLD = 100, // Adjust to tune performance of moving topic between sections
51
52 SID_STR_LEN = 5, // 32-bit + NULL
53 };
54
55 struct article_block_t
56 {
57 ARTICLE articles[BBS_article_count_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 size_t shm_size;
66 struct
67 {
68 size_t shm_size;
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 char article_block_shm_name[FILE_NAME_LEN];
79 static ARTICLE_BLOCK_POOL *p_article_block_pool = NULL;
80
81 static char section_list_shm_name[FILE_NAME_LEN];
82 SECTION_LIST_POOL *p_section_list_pool = NULL;
83
84 #ifndef HAVE_SYSTEM_V
85 static int section_list_reset_lock(SECTION_LIST *p_section);
86 #endif
87
88 int article_block_init(const char *filename, int block_count)
89 {
90 char filepath[FILE_PATH_LEN];
91 int fd;
92 size_t size;
93 void *p_shm;
94 int i;
95 int block_count_in_shm;
96 ARTICLE_BLOCK *p_block_in_shm;
97 ARTICLE_BLOCK **pp_block_next;
98
99 if (p_article_block_pool != NULL)
100 {
101 log_error("article_block_pool already initialized");
102 return -1;
103 }
104
105 if (block_count <= 0 || block_count > ARTICLE_BLOCK_PER_POOL)
106 {
107 log_error("article_block_count exceed limit %d", ARTICLE_BLOCK_PER_POOL);
108 return -2;
109 }
110
111 strncpy(filepath, filename, sizeof(filepath) - 1);
112 filepath[sizeof(filepath) - 1] = '\0';
113 snprintf(article_block_shm_name, sizeof(article_block_shm_name), "/ARTICLE_BLOCK_SHM_%s", basename(filepath));
114
115 // Allocate shared memory
116 size = sizeof(ARTICLE_BLOCK_POOL);
117 snprintf(filepath, sizeof(filepath), "%s_%d", article_block_shm_name, ARTICLE_BLOCK_SHM_COUNT_LIMIT);
118
119 if (shm_unlink(filepath) == -1 && errno != ENOENT)
120 {
121 log_error("shm_unlink(%s) error (%d)", filepath, errno);
122 return -2;
123 }
124
125 if ((fd = shm_open(filepath, O_CREAT | O_EXCL | O_RDWR, 0600)) == -1)
126 {
127 log_error("shm_open(%s) error (%d)", filepath, errno);
128 return -2;
129 }
130 if (ftruncate(fd, (off_t)size) == -1)
131 {
132 log_error("ftruncate(size=%d) error (%d)", size, errno);
133 close(fd);
134 return -2;
135 }
136
137 p_shm = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0L);
138 if (p_shm == MAP_FAILED)
139 {
140 log_error("mmap() error (%d)", errno);
141 close(fd);
142 return -2;
143 }
144
145 if (close(fd) < 0)
146 {
147 log_error("close(fd) error (%d)", errno);
148 return -1;
149 }
150
151 p_article_block_pool = p_shm;
152 p_article_block_pool->shm_size = size;
153
154 p_article_block_pool->shm_count = 0;
155 pp_block_next = &(p_article_block_pool->p_block_free_list);
156
157 while (block_count > 0)
158 {
159 block_count_in_shm = MIN(block_count, ARTICLE_BLOCK_PER_SHM);
160 block_count -= block_count_in_shm;
161
162 size = sizeof(ARTICLE_BLOCK) * (size_t)block_count_in_shm;
163 snprintf(filepath, sizeof(filepath), "%s_%d", article_block_shm_name, p_article_block_pool->shm_count);
164
165 if (shm_unlink(filepath) == -1 && errno != ENOENT)
166 {
167 log_error("shm_unlink(%s) error (%d)", filepath, errno);
168 return -2;
169 }
170
171 if ((fd = shm_open(filepath, O_CREAT | O_EXCL | O_RDWR, 0600)) == -1)
172 {
173 log_error("shm_open(%s) error (%d)", filepath, errno);
174 return -2;
175 }
176 if (ftruncate(fd, (off_t)size) == -1)
177 {
178 log_error("ftruncate(size=%d) error (%d)", size, errno);
179 close(fd);
180 return -2;
181 }
182
183 p_shm = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0L);
184 if (p_shm == MAP_FAILED)
185 {
186 log_error("mmap() error (%d)", errno);
187 close(fd);
188 return -2;
189 }
190
191 if (close(fd) < 0)
192 {
193 log_error("close(fd) error (%d)", errno);
194 return -1;
195 }
196
197 (p_article_block_pool->shm_pool + p_article_block_pool->shm_count)->shm_size = size;
198 (p_article_block_pool->shm_pool + p_article_block_pool->shm_count)->p_shm = p_shm;
199 p_article_block_pool->shm_count++;
200
201 p_block_in_shm = p_shm;
202 *pp_block_next = p_block_in_shm;
203
204 for (i = 0; i < block_count_in_shm; i++)
205 {
206 if (i < block_count_in_shm - 1)
207 {
208 (p_block_in_shm + i)->p_next_block = (p_block_in_shm + i + 1);
209 }
210 else
211 {
212 (p_block_in_shm + i)->p_next_block = NULL;
213 pp_block_next = &((p_block_in_shm + i)->p_next_block);
214 }
215 }
216 }
217
218 p_article_block_pool->block_count = 0;
219
220 return 0;
221 }
222
223 void article_block_cleanup(void)
224 {
225 char filepath[FILE_PATH_LEN];
226
227 if (p_article_block_pool == NULL)
228 {
229 return;
230 }
231
232 for (int i = 0; i < p_article_block_pool->shm_count; i++)
233 {
234 snprintf(filepath, sizeof(filepath), "%s_%d", article_block_shm_name, i);
235
236 if (shm_unlink(filepath) == -1 && errno != ENOENT)
237 {
238 log_error("shm_unlink(%s) error (%d)", filepath, errno);
239 }
240 }
241
242 snprintf(filepath, sizeof(filepath), "%s_%d", article_block_shm_name, ARTICLE_BLOCK_SHM_COUNT_LIMIT);
243
244 if (shm_unlink(filepath) == -1 && errno != ENOENT)
245 {
246 log_error("shm_unlink(%s) error (%d)", filepath, errno);
247 }
248
249 detach_article_block_shm();
250 }
251
252 int set_article_block_shm_readonly(void)
253 {
254 // int i;
255
256 if (p_article_block_pool == NULL)
257 {
258 log_error("article_block_pool not initialized");
259 return -1;
260 }
261
262 // for (i = 0; i < p_article_block_pool->shm_count; i++)
263 // {
264 // if ((p_article_block_pool->shm_pool + i)->p_shm != NULL &&
265 // mprotect((p_article_block_pool->shm_pool + i)->p_shm, (p_article_block_pool->shm_pool + i)->shm_size, PROT_READ) < 0)
266 // {
267 // log_error("mprotect() error (%d)", errno);
268 // return -2;
269 // }
270 // }
271
272 if (p_article_block_pool != NULL &&
273 mprotect(p_article_block_pool, p_article_block_pool->shm_size, PROT_READ) < 0)
274 {
275 log_error("mprotect() error (%d)", errno);
276 return -3;
277 }
278
279 return 0;
280 }
281
282 int detach_article_block_shm(void)
283 {
284 int shm_count;
285 size_t pool_shm_size;
286
287 if (p_article_block_pool == NULL)
288 {
289 return -1;
290 }
291
292 shm_count = p_article_block_pool->shm_count;
293 pool_shm_size = p_article_block_pool->shm_size;
294
295 for (int i = 0; i < shm_count; i++)
296 {
297 if ((p_article_block_pool->shm_pool + i)->p_shm != NULL &&
298 munmap((p_article_block_pool->shm_pool + i)->p_shm, (p_article_block_pool->shm_pool + i)->shm_size) < 0)
299 {
300 log_error("munmap() error (%d)", errno);
301 return -2;
302 }
303 }
304
305 if (p_article_block_pool != NULL && munmap(p_article_block_pool, pool_shm_size) < 0)
306 {
307 log_error("munmap() error (%d)", errno);
308 return -3;
309 }
310
311 p_article_block_pool = NULL;
312
313 return 0;
314 }
315
316 inline static ARTICLE_BLOCK *pop_free_article_block(void)
317 {
318 ARTICLE_BLOCK *p_block = NULL;
319
320 if (p_article_block_pool->p_block_free_list != NULL)
321 {
322 p_block = p_article_block_pool->p_block_free_list;
323 p_article_block_pool->p_block_free_list = p_block->p_next_block;
324 p_block->p_next_block = NULL;
325 p_block->article_count = 0;
326 }
327
328 return p_block;
329 }
330
331 inline static void push_free_article_block(ARTICLE_BLOCK *p_block)
332 {
333 p_block->p_next_block = p_article_block_pool->p_block_free_list;
334 p_article_block_pool->p_block_free_list = p_block;
335 }
336
337 int article_block_reset(void)
338 {
339 ARTICLE_BLOCK *p_block;
340
341 if (p_article_block_pool == NULL)
342 {
343 log_error("article_block_pool not initialized");
344 return -1;
345 }
346
347 while (p_article_block_pool->block_count > 0)
348 {
349 p_article_block_pool->block_count--;
350 p_block = p_article_block_pool->p_block[p_article_block_pool->block_count];
351 push_free_article_block(p_block);
352 }
353
354 return 0;
355 }
356
357 ARTICLE *article_block_find_by_aid(int32_t aid)
358 {
359 ARTICLE_BLOCK *p_block;
360 int left;
361 int right;
362 int mid;
363
364 if (p_article_block_pool == NULL)
365 {
366 log_error("article_block_pool not initialized");
367 return NULL;
368 }
369
370 if (p_article_block_pool->block_count == 0) // empty
371 {
372 return NULL;
373 }
374
375 left = 0;
376 right = p_article_block_pool->block_count - 1;
377
378 // aid in the range [ head aid of blocks[left], tail aid of blocks[right] ]
379 while (left < right)
380 {
381 // get block offset no less than mid value of left and right block offsets
382 mid = left + (right - left + 1) / 2;
383
384 if (aid < p_article_block_pool->p_block[mid]->articles[0].aid)
385 {
386 right = mid - 1;
387 }
388 else // if (aid >= p_article_block_pool->p_block[mid]->articles[0].aid)
389 {
390 left = mid;
391 }
392 }
393
394 p_block = p_article_block_pool->p_block[left];
395
396 left = 0;
397 right = p_block->article_count - 1;
398
399 // aid in the range [ aid of articles[left], aid of articles[right] ]
400 while (left < right)
401 {
402 mid = left + (right - left) / 2;
403
404 if (aid <= p_block->articles[mid].aid)
405 {
406 right = mid;
407 }
408 else
409 {
410 left = mid + 1;
411 }
412 }
413
414 if (aid != p_block->articles[left].aid) // not found
415 {
416 return NULL;
417 }
418
419 return (p_block->articles + left); // found
420 }
421
422 ARTICLE *article_block_find_by_index(int index)
423 {
424 ARTICLE_BLOCK *p_block;
425
426 if (p_article_block_pool == NULL)
427 {
428 log_error("article_block_pool not initialized");
429 return NULL;
430 }
431
432 if (index < 0 || index / BBS_article_count_per_block >= p_article_block_pool->block_count)
433 {
434 log_error("article_block_find_by_index(%d) is out of boundary of block [0, %d)", index, p_article_block_pool->block_count);
435 return NULL;
436 }
437
438 p_block = p_article_block_pool->p_block[index / BBS_article_count_per_block];
439
440 if (index % BBS_article_count_per_block >= p_block->article_count)
441 {
442 log_error("article_block_find_by_index(%d) is out of boundary of article [0, %d)", index, p_block->article_count);
443 return NULL;
444 }
445
446 return (p_block->articles + (index % BBS_article_count_per_block));
447 }
448
449 extern int section_list_init(const char *filename)
450 {
451 char filepath[FILE_PATH_LEN];
452 int fd;
453 size_t size;
454 void *p_shm;
455 #ifdef HAVE_SYSTEM_V
456 int proj_id;
457 key_t key;
458 int semid;
459 union semun arg;
460 #endif
461 int i;
462
463 if (p_section_list_pool != NULL)
464 {
465 section_list_cleanup();
466 }
467
468 // Allocate shared memory
469 size = sizeof(SECTION_LIST_POOL);
470
471 strncpy(filepath, filename, sizeof(filepath) - 1);
472 filepath[sizeof(filepath) - 1] = '\0';
473 snprintf(section_list_shm_name, sizeof(section_list_shm_name), "/SECTION_LIST_SHM_%s", basename(filepath));
474
475 if (shm_unlink(section_list_shm_name) == -1 && errno != ENOENT)
476 {
477 log_error("shm_unlink(%s) error (%d)", section_list_shm_name, errno);
478 return -2;
479 }
480
481 if ((fd = shm_open(section_list_shm_name, O_CREAT | O_EXCL | O_RDWR, 0600)) == -1)
482 {
483 log_error("shm_open(%s) error (%d)", section_list_shm_name, errno);
484 return -2;
485 }
486 if (ftruncate(fd, (off_t)size) == -1)
487 {
488 log_error("ftruncate(size=%d) error (%d)", size, errno);
489 close(fd);
490 return -2;
491 }
492
493 p_shm = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0L);
494 if (p_shm == MAP_FAILED)
495 {
496 log_error("mmap() error (%d)", errno);
497 close(fd);
498 return -2;
499 }
500
501 if (close(fd) < 0)
502 {
503 log_error("close(fd) error (%d)", errno);
504 return -1;
505 }
506
507 p_section_list_pool = p_shm;
508 p_section_list_pool->shm_size = size;
509 p_section_list_pool->section_count = 0;
510
511 // Allocate semaphore as section locks
512 #ifndef HAVE_SYSTEM_V
513 for (i = 0; i <= BBS_max_section; i++)
514 {
515 if (sem_init(&(p_section_list_pool->sem[i]), 1, 1) == -1)
516 {
517 log_error("sem_init(sem[%d]) error (%d)", i, errno);
518 return -3;
519 }
520
521 p_section_list_pool->read_lock_count[i] = 0;
522 p_section_list_pool->write_lock_count[i] = 0;
523 }
524 #else
525 proj_id = (int)(time(NULL) % getpid());
526 key = ftok(filename, proj_id);
527 if (key == -1)
528 {
529 log_error("ftok(%s, %d) error (%d)", filename, proj_id, errno);
530 return -3;
531 }
532
533 size = 2 * (BBS_max_section + 1); // r_sem and w_sem per section, the last pair for all sections
534 semid = semget(key, (int)size, IPC_CREAT | IPC_EXCL | 0600);
535 if (semid == -1)
536 {
537 log_error("semget(section_list_pool_sem, size = %d) error (%d)", size, errno);
538 return -3;
539 }
540
541 // Initialize sem value to 0
542 arg.val = 0;
543 for (i = 0; i < size; i++)
544 {
545 if (semctl(semid, i, SETVAL, arg) == -1)
546 {
547 log_error("semctl(section_list_pool_sem, SETVAL) error (%d)", errno);
548 return -3;
549 }
550 }
551
552 p_section_list_pool->semid = semid;
553 #endif
554
555 p_section_list_pool->p_trie_dict_section_by_name = trie_dict_create();
556 if (p_section_list_pool->p_trie_dict_section_by_name == NULL)
557 {
558 log_error("trie_dict_create() OOM", BBS_max_section);
559 return -2;
560 }
561
562 p_section_list_pool->p_trie_dict_section_by_sid = trie_dict_create();
563 if (p_section_list_pool->p_trie_dict_section_by_sid == NULL)
564 {
565 log_error("trie_dict_create() OOM", BBS_max_section);
566 return -2;
567 }
568
569 return 0;
570 }
571
572 void section_list_cleanup(void)
573 {
574 if (p_section_list_pool == NULL)
575 {
576 return;
577 }
578
579 if (p_section_list_pool->p_trie_dict_section_by_name != NULL)
580 {
581 trie_dict_destroy(p_section_list_pool->p_trie_dict_section_by_name);
582 p_section_list_pool->p_trie_dict_section_by_name = NULL;
583 }
584
585 if (p_section_list_pool->p_trie_dict_section_by_sid != NULL)
586 {
587 trie_dict_destroy(p_section_list_pool->p_trie_dict_section_by_sid);
588 p_section_list_pool->p_trie_dict_section_by_sid = NULL;
589 }
590
591 #ifdef HAVE_SYSTEM_V
592 if (semctl(p_section_list_pool->semid, 0, IPC_RMID) == -1)
593 {
594 log_error("semctl(semid = %d, IPC_RMID) error (%d)", p_section_list_pool->semid, errno);
595 }
596 #else
597 for (int i = 0; i <= BBS_max_section; i++)
598 {
599 if (sem_destroy(&(p_section_list_pool->sem[i])) == -1)
600 {
601 log_error("sem_destroy(sem[%d]) error (%d)", i, errno);
602 }
603 }
604 #endif
605
606 detach_section_list_shm();
607
608 if (shm_unlink(section_list_shm_name) == -1 && errno != ENOENT)
609 {
610 log_error("shm_unlink(%s) error (%d)", section_list_shm_name, errno);
611 }
612 }
613
614 int set_section_list_shm_readonly(void)
615 {
616 if (p_section_list_pool == NULL)
617 {
618 log_error("p_section_list_pool not initialized");
619 return -1;
620 }
621
622 if (p_section_list_pool != NULL &&
623 mprotect(p_section_list_pool, p_section_list_pool->shm_size, PROT_READ) < 0)
624 {
625 log_error("mprotect() error (%d)", errno);
626 return -2;
627 }
628
629 return 0;
630 }
631
632 int detach_section_list_shm(void)
633 {
634 if (p_section_list_pool != NULL && munmap(p_section_list_pool, p_section_list_pool->shm_size) < 0)
635 {
636 log_error("munmap() error (%d)", errno);
637 return -1;
638 }
639
640 p_section_list_pool = NULL;
641
642 return 0;
643 }
644
645 inline static void sid_to_str(int32_t sid, char *p_sid_str)
646 {
647 uint32_t u_sid;
648 int i;
649
650 u_sid = (uint32_t)sid;
651 for (i = 0; i < SID_STR_LEN - 1; i++)
652 {
653 p_sid_str[i] = (char)(u_sid % 255 + 1);
654 u_sid /= 255;
655 }
656 p_sid_str[i] = '\0';
657 }
658
659 SECTION_LIST *section_list_create(int32_t sid, const char *sname, const char *stitle, const char *master_list)
660 {
661 SECTION_LIST *p_section;
662 char sid_str[SID_STR_LEN];
663
664 if (p_section_list_pool == NULL)
665 {
666 log_error("session_list_pool not initialized");
667 return NULL;
668 }
669
670 if (p_section_list_pool->section_count >= BBS_max_section)
671 {
672 log_error("section_count reach limit %d >= %d", p_section_list_pool->section_count, BBS_max_section);
673 return NULL;
674 }
675
676 sid_to_str(sid, sid_str);
677
678 p_section = p_section_list_pool->sections + p_section_list_pool->section_count;
679
680 p_section->sid = sid;
681 p_section->ex_menu_tm = 0;
682
683 strncpy(p_section->sname, sname, sizeof(p_section->sname) - 1);
684 p_section->sname[sizeof(p_section->sname) - 1] = '\0';
685
686 strncpy(p_section->stitle, stitle, sizeof(p_section->stitle) - 1);
687 p_section->stitle[sizeof(p_section->stitle) - 1] = '\0';
688
689 strncpy(p_section->master_list, master_list, sizeof(p_section->master_list) - 1);
690 p_section->master_list[sizeof(p_section->master_list) - 1] = '\0';
691
692 if (trie_dict_set(p_section_list_pool->p_trie_dict_section_by_name, sname, p_section_list_pool->section_count) != 1)
693 {
694 log_error("trie_dict_set(section, %s, %d) error", sname, p_section_list_pool->section_count);
695 return NULL;
696 }
697
698 if (trie_dict_set(p_section_list_pool->p_trie_dict_section_by_sid, sid_str, p_section_list_pool->section_count) != 1)
699 {
700 log_error("trie_dict_set(section, %d, %d) error", sid, p_section_list_pool->section_count);
701 return NULL;
702 }
703
704 section_list_reset_articles(p_section);
705
706 p_section_list_pool->section_count++;
707
708 return p_section;
709 }
710
711 int section_list_update(SECTION_LIST *p_section, const char *sname, const char *stitle, const char *master_list)
712 {
713 int64_t index;
714
715 if (p_section == NULL || sname == NULL || stitle == NULL || master_list == NULL)
716 {
717 log_error("NULL pointer error");
718 return -1;
719 }
720
721 index = get_section_index(p_section);
722
723 strncpy(p_section->sname, sname, sizeof(p_section->sname) - 1);
724 p_section->sname[sizeof(p_section->sname) - 1] = '\0';
725
726 strncpy(p_section->stitle, stitle, sizeof(p_section->stitle) - 1);
727 p_section->stitle[sizeof(p_section->stitle) - 1] = '\0';
728
729 strncpy(p_section->master_list, master_list, sizeof(p_section->master_list) - 1);
730 p_section->master_list[sizeof(p_section->master_list) - 1] = '\0';
731
732 if (trie_dict_set(p_section_list_pool->p_trie_dict_section_by_name, sname, index) < 0)
733 {
734 log_error("trie_dict_set(section, %s, %d) error", sname, index);
735 return -2;
736 }
737
738 return 0;
739 }
740
741 void section_list_reset_articles(SECTION_LIST *p_section)
742 {
743 p_section->article_count = 0;
744 p_section->topic_count = 0;
745 p_section->visible_article_count = 0;
746 p_section->visible_topic_count = 0;
747 p_section->p_article_head = NULL;
748 p_section->p_article_tail = NULL;
749
750 p_section->page_count = 0;
751 p_section->last_page_visible_article_count = 0;
752
753 p_section->ontop_article_count = 0;
754 }
755
756 SECTION_LIST *section_list_find_by_name(const char *sname)
757 {
758 int64_t index;
759 int ret;
760
761 if (p_section_list_pool == NULL)
762 {
763 log_error("section_list not initialized");
764 return NULL;
765 }
766
767 ret = trie_dict_get(p_section_list_pool->p_trie_dict_section_by_name, sname, &index);
768 if (ret < 0)
769 {
770 log_error("trie_dict_get(section, %s) error", sname);
771 return NULL;
772 }
773 else if (ret == 0)
774 {
775 return NULL;
776 }
777
778 return (p_section_list_pool->sections + index);
779 }
780
781 SECTION_LIST *section_list_find_by_sid(int32_t sid)
782 {
783 int64_t index;
784 int ret;
785 char sid_str[SID_STR_LEN];
786
787 if (p_section_list_pool == NULL)
788 {
789 log_error("section_list not initialized");
790 return NULL;
791 }
792
793 sid_to_str(sid, sid_str);
794
795 ret = trie_dict_get(p_section_list_pool->p_trie_dict_section_by_sid, sid_str, &index);
796 if (ret < 0)
797 {
798 log_error("trie_dict_get(section, %d) error", sid);
799 return NULL;
800 }
801 else if (ret == 0)
802 {
803 return NULL;
804 }
805
806 return (p_section_list_pool->sections + index);
807 }
808
809 int section_list_append_article(SECTION_LIST *p_section, const ARTICLE *p_article_src)
810 {
811 ARTICLE_BLOCK *p_block;
812 int32_t last_aid = 0;
813 ARTICLE *p_article;
814 ARTICLE *p_topic_head;
815 ARTICLE *p_topic_tail;
816
817 if (p_section == NULL || p_article_src == NULL)
818 {
819 log_error("NULL pointer error");
820 return -1;
821 }
822
823 if (p_article_block_pool == NULL)
824 {
825 log_error("article_block_pool not initialized");
826 return -1;
827 }
828
829 if (p_section->sid != p_article_src->sid)
830 {
831 log_error("section_list_append_article() error: section sid %d != article sid %d", p_section->sid, p_article_src->sid);
832 return -2;
833 }
834
835 if (p_section->article_count >= BBS_article_limit_per_section)
836 {
837 log_error("section_list_append_article() error: article_count reach limit in section %d", p_section->sid);
838 return -2;
839 }
840
841 if (p_article_block_pool->block_count == 0 ||
842 p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->article_count >= BBS_article_count_per_block)
843 {
844 if ((p_block = pop_free_article_block()) == NULL)
845 {
846 log_error("pop_free_article_block() error");
847 return -2;
848 }
849
850 if (p_article_block_pool->block_count > 0)
851 {
852 last_aid = p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->articles[BBS_article_count_per_block - 1].aid;
853 }
854
855 p_article_block_pool->p_block[p_article_block_pool->block_count] = p_block;
856 p_article_block_pool->block_count++;
857 }
858 else
859 {
860 p_block = p_article_block_pool->p_block[p_article_block_pool->block_count - 1];
861 last_aid = p_block->articles[p_block->article_count - 1].aid;
862 }
863
864 // AID of articles should be strictly ascending
865 if (p_article_src->aid <= last_aid)
866 {
867 log_error("section_list_append_article(aid=%d) error: last_aid=%d", p_article_src->aid, last_aid);
868 return -3;
869 }
870
871 p_article = (p_block->articles + p_block->article_count);
872 p_block->article_count++;
873 p_section->article_count++;
874
875 // Copy article data
876 *p_article = *p_article_src;
877
878 if (p_article->visible)
879 {
880 p_section->visible_article_count++;
881 }
882
883 // Link appended article as tail node of topic bi-directional list
884 if (p_article->tid != 0)
885 {
886 p_topic_head = article_block_find_by_aid(p_article->tid);
887 if (p_topic_head == NULL)
888 {
889 log_error("search head of topic (aid=%d) error", p_article->tid);
890 return -4;
891 }
892
893 p_topic_tail = p_topic_head->p_topic_prior;
894 if (p_topic_tail == NULL)
895 {
896 log_error("tail of topic (aid=%d) is NULL", p_article->tid);
897 return -4;
898 }
899 }
900 else
901 {
902 p_section->topic_count++;
903
904 if (p_article->visible)
905 {
906 p_section->visible_topic_count++;
907 }
908
909 p_topic_head = p_article;
910 p_topic_tail = p_article;
911 }
912
913 p_article->p_topic_prior = p_topic_tail;
914 p_article->p_topic_next = p_topic_head;
915 p_topic_head->p_topic_prior = p_article;
916 p_topic_tail->p_topic_next = p_article;
917
918 // Link appended article as tail node of article bi-directional list
919 if (p_section->p_article_head == NULL)
920 {
921 p_section->p_article_head = p_article;
922 p_section->p_article_tail = p_article;
923 }
924 p_article->p_prior = p_section->p_article_tail;
925 p_article->p_next = p_section->p_article_head;
926 p_section->p_article_head->p_prior = p_article;
927 p_section->p_article_tail->p_next = p_article;
928 p_section->p_article_tail = p_article;
929
930 // Update page
931 if ((p_article->visible && p_section->last_page_visible_article_count == BBS_article_limit_per_page) ||
932 p_section->page_count == 0)
933 {
934 p_section->p_page_first_article[p_section->page_count] = p_article;
935 p_section->page_count++;
936 p_section->last_page_visible_article_count = 0;
937 }
938
939 if (p_article->visible)
940 {
941 p_section->last_page_visible_article_count++;
942 }
943
944 if (p_article->ontop && section_list_update_article_ontop(p_section, p_article) < 0)
945 {
946 log_error("section_list_update_article_ontop(sid=%d, aid=%d) error",
947 p_section->sid, p_article->aid);
948 return -5;
949 }
950
951 return 0;
952 }
953
954 int section_list_set_article_visible(SECTION_LIST *p_section, int32_t aid, int8_t visible)
955 {
956 ARTICLE *p_article;
957 ARTICLE *p_reply;
958 int affected_count = 0;
959
960 if (p_section == NULL)
961 {
962 log_error("NULL pointer error");
963 return -1;
964 }
965
966 p_article = article_block_find_by_aid(aid);
967 if (p_article == NULL)
968 {
969 return -1; // Not found
970 }
971
972 if (p_section->sid != p_article->sid)
973 {
974 log_error("Inconsistent section sid %d != article sid %d", p_section->sid, p_article->sid);
975 return -2;
976 }
977
978 if (p_article->visible == visible)
979 {
980 return 0; // Already set
981 }
982
983 if (visible == 0) // 1 -> 0
984 {
985 p_section->visible_article_count--;
986
987 if (user_article_cnt_inc(p_article->uid, -1) < 0)
988 {
989 log_error("user_article_cnt_inc(uid=%d, -1) error", p_article->uid);
990 }
991
992 if (p_article->tid == 0)
993 {
994 p_section->visible_topic_count--;
995
996 // Set related visible replies to invisible
997 for (p_reply = p_article->p_topic_next; p_reply->tid != 0; p_reply = p_reply->p_topic_next)
998 {
999 if (p_reply->tid != aid)
1000 {
1001 log_error("Inconsistent tid = %d found in reply %d of topic %d", p_reply->tid, p_reply->aid, aid);
1002 continue;
1003 }
1004
1005 if (p_reply->visible == 1)
1006 {
1007 p_reply->visible = 0;
1008 p_section->visible_article_count--;
1009 affected_count++;
1010
1011 if (user_article_cnt_inc(p_reply->uid, -1) < 0)
1012 {
1013 log_error("user_article_cnt_inc(uid=%d, -1) error", p_reply->uid);
1014 }
1015 }
1016 }
1017 }
1018 }
1019 else // 0 -> 1
1020 {
1021 p_section->visible_article_count++;
1022
1023 if (p_article->tid == 0)
1024 {
1025 p_section->visible_topic_count++;
1026 }
1027
1028 if (user_article_cnt_inc(p_article->uid, 1) < 0)
1029 {
1030 log_error("user_article_cnt_inc(uid=%d, 1) error", p_article->uid);
1031 }
1032 }
1033
1034 p_article->visible = visible;
1035 affected_count++;
1036
1037 return affected_count;
1038 }
1039
1040 int section_list_set_article_excerption(SECTION_LIST *p_section, int32_t aid, int8_t excerption)
1041 {
1042 ARTICLE *p_article;
1043
1044 if (p_section == NULL)
1045 {
1046 log_error("NULL pointer error");
1047 return -1;
1048 }
1049
1050 p_article = article_block_find_by_aid(aid);
1051 if (p_article == NULL)
1052 {
1053 return -1; // Not found
1054 }
1055
1056 if (p_section->sid != p_article->sid)
1057 {
1058 log_error("Inconsistent section sid %d != article sid %d", p_section->sid, p_article->sid);
1059 return -2;
1060 }
1061
1062 if (p_article->excerption == excerption)
1063 {
1064 return 0; // Already set
1065 }
1066
1067 p_article->excerption = excerption;
1068
1069 return 1;
1070 }
1071
1072 int section_list_update_article_ontop(SECTION_LIST *p_section, ARTICLE *p_article)
1073 {
1074 int i;
1075
1076 if (p_section == NULL || p_article == NULL)
1077 {
1078 log_error("NULL pointer error");
1079 return -1;
1080 }
1081
1082 if (p_section->sid != p_article->sid)
1083 {
1084 log_error("Inconsistent section sid %d != article sid %d", p_section->sid, p_article->sid);
1085 return -2;
1086 }
1087
1088 if (p_article->ontop)
1089 {
1090 for (i = 0; i < p_section->ontop_article_count; i++)
1091 {
1092 if (p_section->p_ontop_articles[i]->aid == p_article->aid)
1093 {
1094 log_error("Inconsistent state found: article %d already ontop in section %d", p_article->aid, p_section->sid);
1095 return 0;
1096 }
1097 else if (p_section->p_ontop_articles[i]->aid > p_article->aid)
1098 {
1099 break;
1100 }
1101 }
1102
1103 // Remove the oldest one if the array of ontop articles is full
1104 if (p_section->ontop_article_count >= BBS_ontop_article_limit_per_section)
1105 {
1106 if (i == 0) // p_article is the oldest one
1107 {
1108 return 0;
1109 }
1110 memmove((void *)(p_section->p_ontop_articles),
1111 (void *)(p_section->p_ontop_articles + 1),
1112 sizeof(ARTICLE *) * (size_t)(i - 1));
1113 p_section->ontop_article_count--;
1114 i--;
1115 }
1116 else
1117 {
1118 memmove((void *)(p_section->p_ontop_articles + i + 1),
1119 (void *)(p_section->p_ontop_articles + i),
1120 sizeof(ARTICLE *) * (size_t)(p_section->ontop_article_count - i));
1121 }
1122
1123 p_section->p_ontop_articles[i] = p_article;
1124 p_section->ontop_article_count++;
1125
1126 // TODO: debug
1127 }
1128 else // ontop == 0
1129 {
1130 for (i = 0; i < p_section->ontop_article_count; i++)
1131 {
1132 if (p_section->p_ontop_articles[i]->aid == p_article->aid)
1133 {
1134 break;
1135 }
1136 }
1137 if (i == p_section->ontop_article_count) // not found
1138 {
1139 log_error("Inconsistent state found: article %d not ontop in section %d", p_article->aid, p_section->sid);
1140 return 0;
1141 }
1142
1143 memmove((void *)(p_section->p_ontop_articles + i),
1144 (void *)(p_section->p_ontop_articles + i + 1),
1145 sizeof(ARTICLE *) * (size_t)(p_section->ontop_article_count - i - 1));
1146 p_section->ontop_article_count--;
1147 }
1148
1149 return 0;
1150 }
1151
1152 int section_list_page_count_with_ontop(SECTION_LIST *p_section)
1153 {
1154 int page_count;
1155
1156 if (p_section == NULL)
1157 {
1158 log_error("NULL pointer error");
1159 return -1;
1160 }
1161
1162 page_count = p_section->page_count - 1 +
1163 (p_section->last_page_visible_article_count + p_section->ontop_article_count + BBS_article_limit_per_page - 1) /
1164 BBS_article_limit_per_page;
1165
1166 if (page_count < 0)
1167 {
1168 page_count = 0;
1169 }
1170
1171 return page_count;
1172 }
1173
1174 int section_list_page_article_count_with_ontop(SECTION_LIST *p_section, int32_t page_id)
1175 {
1176 if (p_section == NULL)
1177 {
1178 log_error("NULL pointer error");
1179 return -1;
1180 }
1181
1182 if (page_id < p_section->page_count - 1)
1183 {
1184 return BBS_article_limit_per_page;
1185 }
1186 else // if (page_id >= p_section->page_count - 1)
1187 {
1188 return MIN(MAX(0,
1189 (p_section->last_page_visible_article_count + p_section->ontop_article_count -
1190 BBS_article_limit_per_page * (page_id - p_section->page_count + 1))),
1191 BBS_article_limit_per_page);
1192 }
1193 }
1194
1195 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)
1196 {
1197 ARTICLE *p_article;
1198 int left;
1199 int right;
1200 int mid;
1201
1202 *p_page = -1;
1203 *p_offset = -1;
1204 *pp_next = NULL;
1205
1206 if (p_section == NULL)
1207 {
1208 log_error("NULL pointer error");
1209 return NULL;
1210 }
1211
1212 if (p_section->article_count == 0) // empty
1213 {
1214 *p_page = 0;
1215 *p_offset = 0;
1216 return NULL;
1217 }
1218
1219 left = 0;
1220 right = p_section->page_count - 1;
1221
1222 // aid in the range [ head aid of pages[left], tail aid of pages[right] ]
1223 while (left < right)
1224 {
1225 // get page id no less than mid value of left page id and right page id
1226 mid = left + (right - left + 1) / 2;
1227
1228 if (aid < p_section->p_page_first_article[mid]->aid)
1229 {
1230 right = mid - 1;
1231 }
1232 else // if (aid < p_section->p_page_first_article[mid]->aid)
1233 {
1234 left = mid;
1235 }
1236 }
1237
1238 *p_page = left;
1239
1240 p_article = p_section->p_page_first_article[*p_page];
1241
1242 // p_section->p_page_first_article[*p_page]->aid <= aid < p_section->p_page_first_article[*p_page + 1]->aid
1243 right = (*p_page == MAX(0, p_section->page_count - 1) ? INT32_MAX : p_section->p_page_first_article[*p_page + 1]->aid);
1244
1245 // left will be the offset of article found or offset to insert
1246 left = 0;
1247
1248 while (aid > p_article->aid)
1249 {
1250 p_article = p_article->p_next;
1251 left++;
1252
1253 if (aid == p_article->aid)
1254 {
1255 *pp_next = p_article->p_next;
1256 break;
1257 }
1258
1259 // over last article in the page
1260 if (p_article == p_section->p_article_head || p_article->aid >= right)
1261 {
1262 *pp_next = (p_article == p_section->p_article_head ? p_section->p_article_head : p_section->p_page_first_article[*p_page + 1]);
1263 *p_offset = left;
1264 return NULL; // not found
1265 }
1266 }
1267
1268 if (aid < p_article->aid)
1269 {
1270 *pp_next = p_article;
1271 p_article = NULL; // not found
1272 }
1273 else // aid == p_article->aid
1274 {
1275 *pp_next = p_article->p_next;
1276 }
1277
1278 *p_offset = left;
1279
1280 return p_article;
1281 }
1282
1283 int section_list_calculate_page(SECTION_LIST *p_section, int32_t start_aid)
1284 {
1285 ARTICLE *p_article;
1286 ARTICLE *p_next;
1287 int32_t page;
1288 int32_t offset;
1289 int visible_article_count;
1290 int page_head_set;
1291
1292 if (p_section == NULL)
1293 {
1294 log_error("NULL pointer error");
1295 return -1;
1296 }
1297
1298 if (p_section->article_count == 0) // empty
1299 {
1300 p_section->page_count = 0;
1301 p_section->last_page_visible_article_count = 0;
1302
1303 return 0;
1304 }
1305
1306 if (start_aid > 0)
1307 {
1308 p_article = article_block_find_by_aid(start_aid);
1309 if (p_article == NULL)
1310 {
1311 return -1; // Not found
1312 }
1313
1314 if (p_section->sid != p_article->sid)
1315 {
1316 log_error("section_list_calculate_page() error: section sid %d != start article sid %d", p_section->sid, p_article->sid);
1317 return -2;
1318 }
1319
1320 p_article = section_list_find_article_with_offset(p_section, start_aid, &page, &offset, &p_next);
1321 if (p_article == NULL)
1322 {
1323 if (page < 0)
1324 {
1325 return -1;
1326 }
1327 log_error("section_list_calculate_page() aid = %d not found in section sid = %d",
1328 start_aid, p_section->sid);
1329 return -2;
1330 }
1331
1332 if (offset > 0)
1333 {
1334 p_article = p_section->p_page_first_article[page];
1335 }
1336 }
1337 else
1338 {
1339 p_article = p_section->p_article_head;
1340 page = 0;
1341 offset = 0;
1342 }
1343
1344 visible_article_count = 0;
1345 page_head_set = 0;
1346
1347 do
1348 {
1349 if (!page_head_set && visible_article_count == 0)
1350 {
1351 p_section->p_page_first_article[page] = p_article;
1352 page_head_set = 1;
1353 }
1354
1355 if (p_article->visible)
1356 {
1357 visible_article_count++;
1358 }
1359
1360 p_article = p_article->p_next;
1361
1362 // skip remaining invisible articles
1363 while (p_article->visible == 0 && p_article != p_section->p_article_head)
1364 {
1365 p_article = p_article->p_next;
1366 }
1367
1368 if (visible_article_count >= BBS_article_limit_per_page && p_article != p_section->p_article_head)
1369 {
1370 page++;
1371 visible_article_count = 0;
1372 page_head_set = 0;
1373
1374 if (page >= BBS_article_limit_per_section / BBS_article_limit_per_page && p_article != p_section->p_article_head)
1375 {
1376 log_error("Count of page exceed limit in section %d", p_section->sid);
1377 break;
1378 }
1379 }
1380 } while (p_article != p_section->p_article_head);
1381
1382 p_section->page_count = page + (visible_article_count > 0 ? 1 : 0);
1383 p_section->last_page_visible_article_count = (visible_article_count > 0
1384 ? visible_article_count
1385 : (page > 0 ? BBS_article_limit_per_page : 0));
1386
1387 return 0;
1388 }
1389
1390 int32_t article_block_last_aid(void)
1391 {
1392 ARTICLE_BLOCK *p_block = p_article_block_pool->p_block[p_article_block_pool->block_count - 1];
1393 int32_t last_aid = p_block->articles[p_block->article_count - 1].aid;
1394
1395 return last_aid;
1396 }
1397
1398 int article_block_article_count(void)
1399 {
1400 int ret;
1401
1402 if (p_article_block_pool == NULL || p_article_block_pool->block_count <= 0)
1403 {
1404 return -1;
1405 }
1406
1407 ret = (p_article_block_pool->block_count - 1) * BBS_article_count_per_block +
1408 p_article_block_pool->p_block[p_article_block_pool->block_count - 1]->article_count;
1409
1410 return ret;
1411 }
1412
1413 int article_count_of_topic(int32_t aid)
1414 {
1415 ARTICLE *p_article;
1416 int article_count;
1417
1418 p_article = article_block_find_by_aid(aid);
1419 if (p_article == NULL)
1420 {
1421 return 0; // Not found
1422 }
1423
1424 article_count = 0;
1425
1426 do
1427 {
1428 if (p_article->tid != 0 && p_article->tid != aid)
1429 {
1430 log_error("article_count_of_topic(%d) error: article %d not linked to the topic", aid, p_article->aid);
1431 break;
1432 }
1433
1434 article_count++;
1435 p_article = p_article->p_topic_next;
1436 } while (p_article->aid != aid);
1437
1438 return article_count;
1439 }
1440
1441 int section_list_move_topic(SECTION_LIST *p_section_src, SECTION_LIST *p_section_dest, int32_t aid)
1442 {
1443 ARTICLE *p_article;
1444 ARTICLE *p_next;
1445 int32_t page;
1446 int32_t offset;
1447 int32_t move_article_count;
1448 int32_t dest_article_count_old;
1449 int32_t last_unaffected_aid_src;
1450 int32_t first_inserted_aid_dest;
1451 int move_counter;
1452
1453 if (p_section_src == NULL || p_section_dest == NULL)
1454 {
1455 log_error("NULL pointer error");
1456 return -1;
1457 }
1458
1459 if (p_section_src->sid == p_section_dest->sid)
1460 {
1461 log_error("section_list_move_topic() src and dest section are the same");
1462 return -1;
1463 }
1464
1465 if ((p_article = article_block_find_by_aid(aid)) == NULL)
1466 {
1467 log_error("article_block_find_by_aid(aid = %d) error: article not found", aid);
1468 return -2;
1469 }
1470
1471 if (p_section_src->sid != p_article->sid)
1472 {
1473 log_error("section_list_move_topic() error: src section sid %d != article %d sid %d",
1474 p_section_src->sid, p_article->aid, p_article->sid);
1475 return -2;
1476 }
1477
1478 if (p_article->tid != 0)
1479 {
1480 log_error("section_list_move_topic(aid = %d) error: article is not head of topic, tid = %d", aid, p_article->tid);
1481 return -2;
1482 }
1483
1484 last_unaffected_aid_src = (p_article == p_section_src->p_article_head ? 0 : p_article->p_prior->aid);
1485
1486 move_article_count = article_count_of_topic(aid);
1487 if (move_article_count <= 0)
1488 {
1489 log_error("article_count_of_topic(aid = %d) <= 0", aid);
1490 return -2;
1491 }
1492
1493 if (p_section_dest->article_count + move_article_count > BBS_article_limit_per_section)
1494 {
1495 log_error("section_list_move_topic() error: article_count %d reach limit in section %d",
1496 p_section_dest->article_count + move_article_count, p_section_dest->sid);
1497 return -3;
1498 }
1499
1500 dest_article_count_old = p_section_dest->article_count;
1501 move_counter = 0;
1502 first_inserted_aid_dest = p_article->aid;
1503
1504 do
1505 {
1506 if (p_section_src->sid != p_article->sid)
1507 {
1508 log_error("section_list_move_topic() warning: src section sid %d != article %d sid %d",
1509 p_section_src->sid, p_article->aid, p_article->sid);
1510 p_article = p_article->p_topic_next;
1511 continue;
1512 }
1513
1514 // Remove from bi-directional article list of src section
1515 if (p_section_src->p_article_head == p_article)
1516 {
1517 p_section_src->p_article_head = p_article->p_next;
1518 }
1519 if (p_section_src->p_article_tail == p_article)
1520 {
1521 p_section_src->p_article_tail = p_article->p_prior;
1522 }
1523 if (p_section_src->p_article_head == p_article) // Single element list
1524 {
1525 p_section_src->p_article_head = NULL;
1526 p_section_src->p_article_tail = NULL;
1527 }
1528 else
1529 {
1530 // Only update neighbor pointers if list is not empty after removal
1531 p_article->p_prior->p_next = p_article->p_next;
1532 p_article->p_next->p_prior = p_article->p_prior;
1533 }
1534
1535 // Update sid of article
1536 p_article->sid = p_section_dest->sid;
1537
1538 if (section_list_find_article_with_offset(p_section_dest, p_article->aid, &page, &offset, &p_next) != NULL)
1539 {
1540 log_error("section_list_move_topic() warning: article %d already in section %d", p_article->aid, p_section_dest->sid);
1541 p_article = p_article->p_topic_next;
1542 continue;
1543 }
1544
1545 // Insert into bi-directional article list of dest section
1546 if (p_next == NULL) // empty section
1547 {
1548 p_section_dest->p_article_head = p_article;
1549 p_section_dest->p_article_tail = p_article;
1550 p_article->p_prior = p_article;
1551 p_article->p_next = p_article;
1552 }
1553 else
1554 {
1555 if (p_section_dest->p_article_head == p_next)
1556 {
1557 if (p_article->aid < p_next->aid)
1558 {
1559 p_section_dest->p_article_head = p_article;
1560 }
1561 else // p_article->aid > p_next->aid
1562 {
1563 p_section_dest->p_article_tail = p_article;
1564 }
1565 }
1566
1567 p_article->p_prior = p_next->p_prior;
1568 p_article->p_next = p_next;
1569 p_next->p_prior->p_next = p_article;
1570 p_next->p_prior = p_article;
1571 }
1572
1573 // Update article / topic counter of src / desc section
1574 p_section_src->article_count--;
1575 p_section_dest->article_count++;
1576 if (p_article->tid == 0)
1577 {
1578 p_section_src->topic_count--;
1579 p_section_dest->topic_count++;
1580 }
1581
1582 // Update visible article / topic counter of src / desc section
1583 if (p_article->visible)
1584 {
1585 p_section_src->visible_article_count--;
1586 p_section_dest->visible_article_count++;
1587 if (p_article->tid == 0)
1588 {
1589 p_section_src->visible_topic_count--;
1590 p_section_dest->visible_topic_count++;
1591 }
1592 }
1593
1594 // Update page for empty dest section
1595 if (p_section_dest->article_count == 1)
1596 {
1597 p_section_dest->p_page_first_article[0] = p_article;
1598 p_section_dest->page_count = 1;
1599 p_section_dest->last_page_visible_article_count = (p_article->visible ? 1 : 0);
1600 }
1601
1602 p_article = p_article->p_topic_next;
1603
1604 move_counter++;
1605 if (move_counter % CALCULATE_PAGE_THRESHOLD == 0)
1606 {
1607 // Re-calculate pages of desc section
1608 if (section_list_calculate_page(p_section_dest, first_inserted_aid_dest) < 0)
1609 {
1610 log_error("section_list_calculate_page(dest section = %d, aid = %d) error",
1611 p_section_dest->sid, first_inserted_aid_dest);
1612 }
1613
1614 first_inserted_aid_dest = p_article->aid;
1615 }
1616 } while (p_article->aid != aid);
1617
1618 if (p_section_dest->article_count - dest_article_count_old != move_article_count)
1619 {
1620 log_error("section_list_move_topic() warning: count of moved articles %d != %d",
1621 p_section_dest->article_count - dest_article_count_old, move_article_count);
1622 }
1623
1624 // Re-calculate pages of src section
1625 if (section_list_calculate_page(p_section_src, last_unaffected_aid_src) < 0)
1626 {
1627 log_error("section_list_calculate_page(src section = %d, aid = %d) error at aid = %d",
1628 p_section_src->sid, last_unaffected_aid_src, aid);
1629 }
1630
1631 if (move_counter % CALCULATE_PAGE_THRESHOLD != 0)
1632 {
1633 // Re-calculate pages of desc section
1634 if (section_list_calculate_page(p_section_dest, first_inserted_aid_dest) < 0)
1635 {
1636 log_error("section_list_calculate_page(dest section = %d, aid = %d) error",
1637 p_section_dest->sid, first_inserted_aid_dest);
1638 }
1639 }
1640
1641 return move_article_count;
1642 }
1643
1644 int get_section_index(SECTION_LIST *p_section)
1645 {
1646 int index;
1647
1648 if (p_section_list_pool == NULL)
1649 {
1650 log_error("get_section_index() error: uninitialized");
1651 return -1;
1652 }
1653
1654 if (p_section == NULL)
1655 {
1656 index = BBS_max_section;
1657 }
1658 else
1659 {
1660 index = (int)(p_section - p_section_list_pool->sections);
1661 if (index < 0 || index >= BBS_max_section)
1662 {
1663 log_error("get_section_index(%d) error: index out of range", index);
1664 return -2;
1665 }
1666 }
1667
1668 return index;
1669 }
1670
1671 int get_section_info(SECTION_LIST *p_section, char *sname, char *stitle, char *master_list)
1672 {
1673 if (p_section == NULL)
1674 {
1675 log_error("NULL pointer error");
1676 return -1;
1677 }
1678
1679 if (section_list_rd_lock(p_section) < 0)
1680 {
1681 log_error("section_list_rd_lock(sid=%d) error", p_section->sid);
1682 return -2;
1683 }
1684
1685 if (sname != NULL)
1686 {
1687 memcpy(sname, p_section->sname, sizeof(p_section->sname));
1688 }
1689 if (stitle != NULL)
1690 {
1691 memcpy(stitle, p_section->stitle, sizeof(p_section->stitle));
1692 }
1693 if (master_list != NULL)
1694 {
1695 memcpy(master_list, p_section->master_list, sizeof(p_section->master_list));
1696 }
1697
1698 // release lock of section
1699 if (section_list_rd_unlock(p_section) < 0)
1700 {
1701 log_error("section_list_rd_unlock(sid=%d) error", p_section->sid);
1702 return -2;
1703 }
1704
1705 return 0;
1706 }
1707
1708 int section_list_try_rd_lock(SECTION_LIST *p_section, int wait_sec)
1709 {
1710 int index;
1711 #ifdef HAVE_SYSTEM_V
1712 struct sembuf sops[4];
1713 #endif
1714 struct timespec timeout;
1715 int ret = 0;
1716
1717 index = get_section_index(p_section);
1718 if (index < 0)
1719 {
1720 return -2;
1721 }
1722
1723 timeout.tv_sec = wait_sec;
1724 timeout.tv_nsec = 0;
1725
1726 #ifdef HAVE_SYSTEM_V
1727 sops[0].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1728 sops[0].sem_op = 0; // wait until unlocked
1729 sops[0].sem_flg = 0;
1730
1731 sops[1].sem_num = (unsigned short)(index * 2); // r_sem of section index
1732 sops[1].sem_op = 1; // lock
1733 sops[1].sem_flg = SEM_UNDO; // undo on terminate
1734
1735 // Read lock on any specific section will also acquire single read lock on "all section"
1736 // so that write lock on all section only need to acquire single write on on "all section"
1737 // rather than to acquire multiple write locks on all the available sections.
1738 if (index != BBS_max_section)
1739 {
1740 sops[2].sem_num = BBS_max_section * 2 + 1; // w_sem of all section
1741 sops[2].sem_op = 0; // wait until unlocked
1742 sops[2].sem_flg = 0;
1743
1744 sops[3].sem_num = BBS_max_section * 2; // r_sem of all section
1745 sops[3].sem_op = 1; // lock
1746 sops[3].sem_flg = SEM_UNDO; // undo on terminate
1747 }
1748
1749 ret = semtimedop(p_section_list_pool->semid, sops, (index == BBS_max_section ? 2 : 4), &timeout);
1750 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1751 {
1752 log_error("semop(index = %d, lock read) error %d", index, errno);
1753 }
1754 #else
1755 if (sem_timedwait(&(p_section_list_pool->sem[index]), &timeout) == -1)
1756 {
1757 if (errno != ETIMEDOUT && errno != EAGAIN && errno != EINTR)
1758 {
1759 log_error("sem_timedwait(sem[%d]) error %d", index, errno);
1760 }
1761 return -1;
1762 }
1763
1764 if (index != BBS_max_section)
1765 {
1766 if (sem_timedwait(&(p_section_list_pool->sem[BBS_max_section]), &timeout) == -1)
1767 {
1768 if (errno != ETIMEDOUT && errno != EAGAIN && errno != EINTR)
1769 {
1770 log_error("sem_timedwait(sem[%d]) error %d", BBS_max_section, errno);
1771 }
1772 // release previously acquired lock
1773 if (sem_post(&(p_section_list_pool->sem[index])) == -1)
1774 {
1775 log_error("sem_post(sem[%d]) error %d", index, errno);
1776 }
1777 return -1;
1778 }
1779 }
1780
1781 if (p_section_list_pool->write_lock_count[index] == 0 &&
1782 (index != BBS_max_section && p_section_list_pool->write_lock_count[BBS_max_section] == 0))
1783 {
1784 p_section_list_pool->read_lock_count[index]++;
1785 if (index != BBS_max_section)
1786 {
1787 p_section_list_pool->read_lock_count[BBS_max_section]++;
1788 }
1789 }
1790 else
1791 {
1792 errno = EAGAIN;
1793 ret = -1;
1794 }
1795
1796 if (index != BBS_max_section)
1797 {
1798 // release lock on "all section"
1799 if (sem_post(&(p_section_list_pool->sem[BBS_max_section])) == -1)
1800 {
1801 log_error("sem_post(sem[%d]) error %d", BBS_max_section, errno);
1802 ret = -1;
1803 }
1804 }
1805
1806 if (sem_post(&(p_section_list_pool->sem[index])) == -1)
1807 {
1808 log_error("sem_post(sem[%d]) error %d", index, errno);
1809 return -1;
1810 }
1811 #endif
1812
1813 return ret;
1814 }
1815
1816 int section_list_try_rw_lock(SECTION_LIST *p_section, int wait_sec)
1817 {
1818 int index;
1819 #ifdef HAVE_SYSTEM_V
1820 struct sembuf sops[3];
1821 #endif
1822 struct timespec timeout;
1823 int ret = 0;
1824
1825 index = get_section_index(p_section);
1826 if (index < 0)
1827 {
1828 return -2;
1829 }
1830
1831 timeout.tv_sec = wait_sec;
1832 timeout.tv_nsec = 0;
1833
1834 #ifdef HAVE_SYSTEM_V
1835 sops[0].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1836 sops[0].sem_op = 0; // wait until unlocked
1837 sops[0].sem_flg = 0;
1838
1839 sops[1].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1840 sops[1].sem_op = 1; // lock
1841 sops[1].sem_flg = SEM_UNDO; // undo on terminate
1842
1843 sops[2].sem_num = (unsigned short)(index * 2); // r_sem of section index
1844 sops[2].sem_op = 0; // wait until unlocked
1845 sops[2].sem_flg = 0;
1846
1847 ret = semtimedop(p_section_list_pool->semid, sops, 3, &timeout);
1848 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1849 {
1850 log_error("semop(index = %d, lock write) error %d", index, errno);
1851 }
1852 #else
1853 if (sem_timedwait(&(p_section_list_pool->sem[index]), &timeout) == -1)
1854 {
1855 if (errno != ETIMEDOUT && errno != EAGAIN && errno != EINTR)
1856 {
1857 log_error("sem_timedwait(sem[%d]) error %d", index, errno);
1858 }
1859 return -1;
1860 }
1861
1862 if (p_section_list_pool->read_lock_count[index] == 0 && p_section_list_pool->write_lock_count[index] == 0)
1863 {
1864 p_section_list_pool->write_lock_count[index]++;
1865 }
1866 else
1867 {
1868 errno = EAGAIN;
1869 ret = -1;
1870 }
1871
1872 if (sem_post(&(p_section_list_pool->sem[index])) == -1)
1873 {
1874 log_error("sem_post(sem[%d]) error %d", index, errno);
1875 return -1;
1876 }
1877 #endif
1878
1879 return ret;
1880 }
1881
1882 int section_list_rd_unlock(SECTION_LIST *p_section)
1883 {
1884 int index;
1885 #ifdef HAVE_SYSTEM_V
1886 struct sembuf sops[2];
1887 #endif
1888 int ret = 0;
1889
1890 index = get_section_index(p_section);
1891 if (index < 0)
1892 {
1893 return -2;
1894 }
1895
1896 #ifdef HAVE_SYSTEM_V
1897 sops[0].sem_num = (unsigned short)(index * 2); // r_sem of section index
1898 sops[0].sem_op = -1; // unlock
1899 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
1900
1901 if (index != BBS_max_section)
1902 {
1903 sops[1].sem_num = BBS_max_section * 2; // r_sem of all section
1904 sops[1].sem_op = -1; // unlock
1905 sops[1].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
1906 }
1907
1908 ret = semop(p_section_list_pool->semid, sops, (index == BBS_max_section ? 1 : 2));
1909 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1910 {
1911 log_error("semop(index = %d, unlock read) error %d", index, errno);
1912 }
1913 #else
1914 if (sem_wait(&(p_section_list_pool->sem[index])) == -1)
1915 {
1916 if (errno != ETIMEDOUT && errno != EAGAIN && errno != EINTR)
1917 {
1918 log_error("sem_wait(sem[%d]) error %d", index, errno);
1919 }
1920 return -1;
1921 }
1922
1923 if (index != BBS_max_section)
1924 {
1925 if (sem_wait(&(p_section_list_pool->sem[BBS_max_section])) == -1)
1926 {
1927 if (errno != ETIMEDOUT && errno != EAGAIN && errno != EINTR)
1928 {
1929 log_error("sem_wait(sem[%d]) error %d", BBS_max_section, errno);
1930 }
1931 // release previously acquired lock
1932 if (sem_post(&(p_section_list_pool->sem[index])) == -1)
1933 {
1934 log_error("sem_post(sem[%d]) error %d", index, errno);
1935 }
1936 return -1;
1937 }
1938 }
1939
1940 if (p_section_list_pool->read_lock_count[index] > 0)
1941 {
1942 p_section_list_pool->read_lock_count[index]--;
1943 }
1944 else
1945 {
1946 log_error("read_lock_count[%d] already 0", index);
1947 }
1948
1949 if (index != BBS_max_section && p_section_list_pool->read_lock_count[BBS_max_section] > 0)
1950 {
1951 p_section_list_pool->read_lock_count[BBS_max_section]--;
1952 }
1953 else
1954 {
1955 log_error("read_lock_count[%d] already 0", BBS_max_section);
1956 }
1957
1958 if (index != BBS_max_section)
1959 {
1960 // release lock on "all section"
1961 if (sem_post(&(p_section_list_pool->sem[BBS_max_section])) == -1)
1962 {
1963 log_error("sem_post(sem[%d]) error %d", BBS_max_section, errno);
1964 ret = -1;
1965 }
1966 }
1967
1968 if (sem_post(&(p_section_list_pool->sem[index])) == -1)
1969 {
1970 log_error("sem_post(sem[%d]) error %d", index, errno);
1971 return -1;
1972 }
1973 #endif
1974
1975 return ret;
1976 }
1977
1978 int section_list_rw_unlock(SECTION_LIST *p_section)
1979 {
1980 int index;
1981 #ifdef HAVE_SYSTEM_V
1982 struct sembuf sops[1];
1983 #endif
1984 int ret = 0;
1985
1986 index = get_section_index(p_section);
1987 if (index < 0)
1988 {
1989 return -2;
1990 }
1991
1992 #ifdef HAVE_SYSTEM_V
1993 sops[0].sem_num = (unsigned short)(index * 2 + 1); // w_sem of section index
1994 sops[0].sem_op = -1; // unlock
1995 sops[0].sem_flg = IPC_NOWAIT | SEM_UNDO; // no wait
1996
1997 ret = semop(p_section_list_pool->semid, sops, 1);
1998 if (ret == -1 && errno != EAGAIN && errno != EINTR)
1999 {
2000 log_error("semop(index = %d, unlock write) error %d", index, errno);
2001 }
2002 #else
2003 if (sem_wait(&(p_section_list_pool->sem[index])) == -1)
2004 {
2005 if (errno != ETIMEDOUT && errno != EAGAIN && errno != EINTR)
2006 {
2007 log_error("sem_wait(sem[%d]) error %d", index, errno);
2008 }
2009 return -1;
2010 }
2011
2012 if (p_section_list_pool->write_lock_count[index] > 0)
2013 {
2014 p_section_list_pool->write_lock_count[index]--;
2015 }
2016 else
2017 {
2018 log_error("write_lock_count[%d] already 0", index);
2019 }
2020
2021 if (sem_post(&(p_section_list_pool->sem[index])) == -1)
2022 {
2023 log_error("sem_post(sem[%d]) error %d", index, errno);
2024 return -1;
2025 }
2026 #endif
2027
2028 return ret;
2029 }
2030
2031 int section_list_rd_lock(SECTION_LIST *p_section)
2032 {
2033 int timer = 0;
2034 int sid = (p_section == NULL ? 0 : p_section->sid);
2035 int ret = -1;
2036 time_t tm_first_failure = 0;
2037
2038 while (!SYS_server_exit)
2039 {
2040 ret = section_list_try_rd_lock(p_section, SECTION_TRY_LOCK_WAIT_TIME);
2041 if (ret == 0) // success
2042 {
2043 break;
2044 }
2045 else if (errno == EAGAIN || errno == EINTR) // retry
2046 {
2047 // Dead lock detection
2048 if (tm_first_failure == 0)
2049 {
2050 time(&tm_first_failure);
2051 }
2052
2053 timer++;
2054 if (timer % SECTION_TRY_LOCK_TIMES == 0)
2055 {
2056 log_error("section_list_try_rd_lock() tried %d times on section %d", timer, sid);
2057 if (time(NULL) - tm_first_failure >= SECTION_DEAD_LOCK_TIMEOUT)
2058 {
2059 log_error("Unable to acquire rd_lock for %d seconds", time(NULL) - tm_first_failure);
2060 #ifndef HAVE_SYSTEM_V
2061 section_list_reset_lock(p_section);
2062 log_error("Reset POSIX semaphore to resolve dead lock");
2063 #endif
2064 break;
2065 }
2066 }
2067 usleep(100 * 1000); // 0.1 second
2068 }
2069 else // failed
2070 {
2071 log_error("section_list_try_rd_lock() failed on section %d", sid);
2072 break;
2073 }
2074 }
2075
2076 return ret;
2077 }
2078
2079 int section_list_rw_lock(SECTION_LIST *p_section)
2080 {
2081 int timer = 0;
2082 int sid = (p_section == NULL ? 0 : p_section->sid);
2083 int ret = -1;
2084 time_t tm_first_failure = 0;
2085
2086 while (!SYS_server_exit)
2087 {
2088 ret = section_list_try_rw_lock(p_section, SECTION_TRY_LOCK_WAIT_TIME);
2089 if (ret == 0) // success
2090 {
2091 break;
2092 }
2093 else if (errno == EAGAIN || errno == EINTR) // retry
2094 {
2095 // Dead lock detection
2096 if (tm_first_failure == 0)
2097 {
2098 time(&tm_first_failure);
2099 }
2100
2101 timer++;
2102 if (timer % SECTION_TRY_LOCK_TIMES == 0)
2103 {
2104 log_error("section_list_try_rw_lock() tried %d times on section %d", timer, sid);
2105 if (time(NULL) - tm_first_failure >= SECTION_DEAD_LOCK_TIMEOUT)
2106 {
2107 log_error("Unable to acquire rw_lock for %d seconds", time(NULL) - tm_first_failure);
2108 #ifndef HAVE_SYSTEM_V
2109 section_list_reset_lock(p_section);
2110 log_error("Reset POSIX semaphore to resolve dead lock");
2111 #endif
2112 break;
2113 }
2114 }
2115 usleep(100 * 1000); // 0.1 second
2116 }
2117 else // failed
2118 {
2119 log_error("section_list_try_rw_lock() failed on section %d", sid);
2120 break;
2121 }
2122 }
2123
2124 return ret;
2125 }
2126
2127 #ifndef HAVE_SYSTEM_V
2128 int section_list_reset_lock(SECTION_LIST *p_section)
2129 {
2130 int index;
2131
2132 if (p_section == NULL)
2133 {
2134 log_error("NULL pointer error");
2135 return -1;
2136 }
2137
2138 index = get_section_index(p_section);
2139 if (index < 0)
2140 {
2141 return -2;
2142 }
2143
2144 if (sem_destroy(&(p_section_list_pool->sem[index])) == -1)
2145 {
2146 log_error("sem_destroy(sem[%d]) error (%d)", index, errno);
2147 }
2148
2149 p_section_list_pool->read_lock_count[index] = 0;
2150 p_section_list_pool->write_lock_count[index] = 0;
2151
2152 if (sem_init(&(p_section_list_pool->sem[index]), 1, 1) == -1)
2153 {
2154 log_error("sem_init(sem[%d]) error (%d)", index, errno);
2155 }
2156
2157 if (index != BBS_max_section)
2158 {
2159 if (sem_destroy(&(p_section_list_pool->sem[BBS_max_section])) == -1)
2160 {
2161 log_error("sem_destroy(sem[%d]) error (%d)", BBS_max_section, errno);
2162 }
2163
2164 p_section_list_pool->read_lock_count[BBS_max_section] = 0;
2165 p_section_list_pool->write_lock_count[BBS_max_section] = 0;
2166
2167 if (sem_init(&(p_section_list_pool->sem[BBS_max_section]), 1, 1) == -1)
2168 {
2169 log_error("sem_init(sem[%d]) error (%d)", BBS_max_section, errno);
2170 }
2171 }
2172
2173 return 0;
2174 }
2175 #endif

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